First vision of binding
This commit is contained in:
parent
ad082723d6
commit
b28f77a8ab
16
Sources/YadUI/Extensions/UILabel.swift
Normal file
16
Sources/YadUI/Extensions/UILabel.swift
Normal file
@ -0,0 +1,16 @@
|
||||
//
|
||||
// UILabel.swift
|
||||
//
|
||||
//
|
||||
// Created by Selim Mustafaev on 29.07.2023.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UILabel {
|
||||
|
||||
public func text(_ text: String?) -> Self {
|
||||
self.text = text
|
||||
return self
|
||||
}
|
||||
}
|
||||
17
Sources/YadUI/Extensions/UILayoutPriority.swift
Normal file
17
Sources/YadUI/Extensions/UILayoutPriority.swift
Normal file
@ -0,0 +1,17 @@
|
||||
//
|
||||
// UILayoutPriority.swift
|
||||
//
|
||||
//
|
||||
// Created by Selim Mustafaev on 29.07.2023.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UILayoutPriority {
|
||||
|
||||
public static let defaultLowMinus: UILayoutPriority = .init(UILayoutPriority.defaultLow.rawValue - 1)
|
||||
public static let defaultLowPlus: UILayoutPriority = .init(UILayoutPriority.defaultLow.rawValue + 1)
|
||||
public static let defaultHighMinus: UILayoutPriority = .init(UILayoutPriority.defaultHigh.rawValue - 1)
|
||||
public static let defaultHighPlus: UILayoutPriority = .init(UILayoutPriority.defaultHigh.rawValue + 1)
|
||||
public static let almostRequired: UILayoutPriority = .init(UILayoutPriority.required.rawValue - 1)
|
||||
}
|
||||
@ -27,18 +27,21 @@ extension UIView {
|
||||
])
|
||||
}
|
||||
|
||||
public func constraint(src: UIView,
|
||||
@discardableResult
|
||||
public func addConstraint(src: UIView,
|
||||
srcAttr: NSLayoutConstraint.Attribute,
|
||||
dst: UIView,
|
||||
dstAttr: NSLayoutConstraint.Attribute,
|
||||
constant: CGFloat = 0) {
|
||||
constant: CGFloat = 0) -> NSLayoutConstraint {
|
||||
|
||||
addConstraint(.init(item: src,
|
||||
attribute: srcAttr,
|
||||
relatedBy: .equal,
|
||||
toItem: dst,
|
||||
attribute: dstAttr,
|
||||
multiplier: 1.0,
|
||||
constant: constant))
|
||||
let constraint = NSLayoutConstraint(item: src,
|
||||
attribute: srcAttr,
|
||||
relatedBy: .equal,
|
||||
toItem: dst,
|
||||
attribute: dstAttr,
|
||||
multiplier: 1.0,
|
||||
constant: constant)
|
||||
addConstraint(constraint)
|
||||
return constraint
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,4 +28,31 @@ extension UIView {
|
||||
backgroundColor = color
|
||||
return self
|
||||
}
|
||||
|
||||
public func hugging(_ priority: UILayoutPriority, for axis: NSLayoutConstraint.Axis) -> Self {
|
||||
setContentHuggingPriority(priority, for: axis)
|
||||
return self
|
||||
}
|
||||
|
||||
public func hugging(_ priority: UILayoutPriority) -> Self {
|
||||
setContentHuggingPriority(priority, for: .vertical)
|
||||
setContentHuggingPriority(priority, for: .horizontal)
|
||||
return self
|
||||
}
|
||||
|
||||
public func compressionResistance(_ priority: UILayoutPriority, for axis: NSLayoutConstraint.Axis) -> Self {
|
||||
setContentCompressionResistancePriority(priority, for: axis)
|
||||
return self
|
||||
}
|
||||
|
||||
public func compressionResistance(_ priority: UILayoutPriority) -> Self {
|
||||
setContentCompressionResistancePriority(priority, for: .vertical)
|
||||
setContentCompressionResistancePriority(priority, for: .horizontal)
|
||||
return self
|
||||
}
|
||||
|
||||
public func tag(_ tag: Int) -> Self {
|
||||
self.tag = tag
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
15
Sources/YadUI/Protocols/InitConvertible.swift
Normal file
15
Sources/YadUI/Protocols/InitConvertible.swift
Normal file
@ -0,0 +1,15 @@
|
||||
//
|
||||
// InitConvertible.swift
|
||||
//
|
||||
//
|
||||
// Created by Selim Mustafaev on 30.07.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol InitConvertible {
|
||||
|
||||
associatedtype Param
|
||||
|
||||
init(_ param: Param)
|
||||
}
|
||||
@ -7,6 +7,7 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public class VStack: Stack {
|
||||
|
||||
public init(alignment: Alignment = .fill,
|
||||
@ -26,6 +27,7 @@ public class VStack: Stack {
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public class HStack: Stack {
|
||||
|
||||
public init(alignment: Alignment = .fill,
|
||||
|
||||
@ -7,11 +7,15 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
extension Stack {
|
||||
|
||||
public enum Alignment {
|
||||
|
||||
case fill
|
||||
case center
|
||||
case start
|
||||
case end
|
||||
}
|
||||
|
||||
public enum Distribution {
|
||||
|
||||
@ -6,22 +6,23 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public class Stack: UIView {
|
||||
|
||||
private var arrangedSubviews: [UIView]
|
||||
private var alignment: Alignment
|
||||
private var distribution: Distribution
|
||||
private var axis: Axis
|
||||
private var spacing: CGFloat
|
||||
public var arrangedSubviews: [UIView]
|
||||
public var distribution: Distribution
|
||||
public var axis: Axis
|
||||
public var spacing: CGFloat
|
||||
|
||||
var aStart: NSLayoutConstraint.Attribute {
|
||||
axis == .vertical ? .leading : .top
|
||||
public var alignment: Alignment {
|
||||
didSet { setNeedsUpdateConstraints() }
|
||||
}
|
||||
|
||||
var aEnd: NSLayoutConstraint.Attribute {
|
||||
axis == .vertical ? .trailing : .bottom
|
||||
}
|
||||
private var allConstraints: [NSLayoutConstraint] = []
|
||||
|
||||
private var cancellables: [AnyCancellable] = []
|
||||
|
||||
var dStart: NSLayoutConstraint.Attribute {
|
||||
axis == .vertical ? .top : .leading
|
||||
@ -31,6 +32,20 @@ public class Stack: UIView {
|
||||
axis == .vertical ? .bottom : .trailing
|
||||
}
|
||||
|
||||
var alignmentAttributes: [NSLayoutConstraint.Attribute] {
|
||||
|
||||
switch alignment {
|
||||
case .fill:
|
||||
return axis == .vertical ? [.leading, .trailing] : [.top, .bottom]
|
||||
case .center:
|
||||
return axis == .vertical ? [.centerX] : [.centerY]
|
||||
case .start:
|
||||
return axis == .vertical ? [.leading] : [.top]
|
||||
case .end:
|
||||
return axis == .vertical ? [.trailing] : [.bottom]
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
@ -49,15 +64,30 @@ public class Stack: UIView {
|
||||
super.init(frame: .zero)
|
||||
|
||||
self.arrangedSubviews.forEach(addSubview)
|
||||
setNeedsUpdateConstraints()
|
||||
}
|
||||
|
||||
public override func updateConstraints() {
|
||||
addAlignmentConstraints()
|
||||
addDistributionConstraints()
|
||||
super.updateConstraints()
|
||||
}
|
||||
|
||||
func clearConstraints() {
|
||||
NSLayoutConstraint.deactivate(allConstraints)
|
||||
removeConstraints(allConstraints)
|
||||
allConstraints.removeAll()
|
||||
}
|
||||
|
||||
func addAlignmentConstraints() {
|
||||
|
||||
switch alignment {
|
||||
case .fill:
|
||||
addAlignmentFillConstraints()
|
||||
clearConstraints()
|
||||
|
||||
for view in arrangedSubviews {
|
||||
alignmentAttributes.forEach {
|
||||
let constraint = addConstraint(src: view, srcAttr: $0, dst: self, dstAttr: $0)
|
||||
allConstraints.append(constraint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,20 +99,6 @@ public class Stack: UIView {
|
||||
}
|
||||
}
|
||||
|
||||
func addAlignmentFillConstraints() {
|
||||
guard let firstView = arrangedSubviews.first else { return }
|
||||
|
||||
let otherViews = arrangedSubviews.dropFirst()
|
||||
|
||||
constraint(src: firstView, srcAttr: aStart, dst: self, dstAttr: aStart)
|
||||
constraint(src: firstView, srcAttr: aEnd, dst: self, dstAttr: aEnd)
|
||||
|
||||
for view in otherViews {
|
||||
constraint(src: view, srcAttr: aStart, dst: firstView, dstAttr: aStart)
|
||||
constraint(src: view, srcAttr: aEnd, dst: firstView, dstAttr: aEnd)
|
||||
}
|
||||
}
|
||||
|
||||
func addDistributionFillConstraints() {
|
||||
guard let firstView = arrangedSubviews.first,
|
||||
let lastView = arrangedSubviews.last
|
||||
@ -92,15 +108,32 @@ public class Stack: UIView {
|
||||
|
||||
let middleViews = arrangedSubviews.dropFirst().dropLast()
|
||||
|
||||
constraint(src: firstView, srcAttr: dStart, dst: self, dstAttr: dStart)
|
||||
constraint(src: lastView, srcAttr: dEnd, dst: self, dstAttr: dEnd)
|
||||
addConstraint(src: firstView, srcAttr: dStart, dst: self, dstAttr: dStart)
|
||||
addConstraint(src: lastView, srcAttr: dEnd, dst: self, dstAttr: dEnd)
|
||||
|
||||
var currentView = firstView
|
||||
for view in middleViews {
|
||||
constraint(src: view, srcAttr: dStart, dst: currentView, dstAttr: dEnd, constant: spacing)
|
||||
addConstraint(src: view, srcAttr: dStart, dst: currentView, dstAttr: dEnd, constant: spacing)
|
||||
currentView = view
|
||||
}
|
||||
|
||||
constraint(src: lastView, srcAttr: dStart, dst: currentView, dstAttr: dEnd, constant: spacing)
|
||||
addConstraint(src: lastView, srcAttr: dStart, dst: currentView, dstAttr: dEnd, constant: spacing)
|
||||
}
|
||||
|
||||
@available(iOS 16.0.0, *)
|
||||
public func bind<Src, Dst, U>(_ prop: ReferenceWritableKeyPath<Stack,Src>,
|
||||
to dstTag: Int,
|
||||
dstProp: KeyPath<U,Published<Dst>.Publisher>) -> Self where Src: InitConvertible, Src.Param == Dst {
|
||||
|
||||
// TODO: search relative to root view
|
||||
guard let view = viewWithTag(dstTag) as? U else {
|
||||
return self
|
||||
}
|
||||
|
||||
let pub = view[keyPath: dstProp].map { Src($0) }
|
||||
pub.assign(to: prop, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
30
Sources/YadUI/Views/UIKit/YASegmentedControl.swift
Normal file
30
Sources/YadUI/Views/UIKit/YASegmentedControl.swift
Normal file
@ -0,0 +1,30 @@
|
||||
//
|
||||
// YASegmentedControl.swift
|
||||
//
|
||||
//
|
||||
// Created by Selim Mustafaev on 30.07.2023.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public class YASegmentedControl: UISegmentedControl {
|
||||
|
||||
@Published
|
||||
public var selectedIndex: Int = 0
|
||||
|
||||
public override init(items: [Any]?) {
|
||||
super.init(items: items)
|
||||
addTarget(self, action: #selector(segmentedValueChanged), for: .valueChanged)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc func segmentedValueChanged(_ sender: UISegmentedControl!)
|
||||
{
|
||||
print("Selected Segment Index is : \(sender.selectedSegmentIndex)")
|
||||
selectedIndex = sender.selectedSegmentIndex
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,18 @@
|
||||
import UIKit
|
||||
import YadUI
|
||||
|
||||
extension Stack.Alignment: InitConvertible {
|
||||
|
||||
public init(_ value: Int) {
|
||||
switch value {
|
||||
case 0: self = .start
|
||||
case 1: self = .center
|
||||
case 2: self = .end
|
||||
default: self = .fill
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ViewController: UIViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
@ -15,23 +27,25 @@ class ViewController: UIViewController {
|
||||
|
||||
let child = buildView()
|
||||
view.addSubview(child)
|
||||
child.pin(toSafeArea: view)
|
||||
child.pin(toSafeArea: view, insets: .init(all: 16))
|
||||
}
|
||||
|
||||
@RootViewBuilder func buildView() -> UIView {
|
||||
VStack(spacing: 16) {
|
||||
UIView()
|
||||
.background(.red)
|
||||
//.width(100)
|
||||
.height(100)
|
||||
UIView()
|
||||
.background(.green)
|
||||
//.width(100)
|
||||
.height(100)
|
||||
UIView()
|
||||
VStack(alignment: .start, spacing: 16) {
|
||||
UILabel()
|
||||
.text("qwe")
|
||||
.background(.blue)
|
||||
//.height(100)
|
||||
UILabel()
|
||||
.text("asdf")
|
||||
.background(.blue)
|
||||
UILabel()
|
||||
.text("zxcv")
|
||||
.background(.blue)
|
||||
.hugging(.defaultLowMinus)
|
||||
YASegmentedControl(items: ["Left", "Center", "Right"])
|
||||
.tag(100)
|
||||
}
|
||||
.bind(\.alignment, to: 100, dstProp: \YASegmentedControl.$selectedIndex)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user