First vision of binding

This commit is contained in:
Selim Mustafaev 2023-07-30 20:03:43 +03:00
parent ad082723d6
commit b28f77a8ab
10 changed files with 213 additions and 52 deletions

View 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
}
}

View 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)
}

View File

@ -27,18 +27,21 @@ extension UIView {
]) ])
} }
public func constraint(src: UIView, @discardableResult
public func addConstraint(src: UIView,
srcAttr: NSLayoutConstraint.Attribute, srcAttr: NSLayoutConstraint.Attribute,
dst: UIView, dst: UIView,
dstAttr: NSLayoutConstraint.Attribute, dstAttr: NSLayoutConstraint.Attribute,
constant: CGFloat = 0) { constant: CGFloat = 0) -> NSLayoutConstraint {
addConstraint(.init(item: src, let constraint = NSLayoutConstraint(item: src,
attribute: srcAttr, attribute: srcAttr,
relatedBy: .equal, relatedBy: .equal,
toItem: dst, toItem: dst,
attribute: dstAttr, attribute: dstAttr,
multiplier: 1.0, multiplier: 1.0,
constant: constant)) constant: constant)
addConstraint(constraint)
return constraint
} }
} }

View File

@ -28,4 +28,31 @@ extension UIView {
backgroundColor = color backgroundColor = color
return self 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
}
} }

View 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)
}

View File

@ -7,6 +7,7 @@
import UIKit import UIKit
@available(iOS 13.0, *)
public class VStack: Stack { public class VStack: Stack {
public init(alignment: Alignment = .fill, public init(alignment: Alignment = .fill,
@ -26,6 +27,7 @@ public class VStack: Stack {
} }
} }
@available(iOS 13.0, *)
public class HStack: Stack { public class HStack: Stack {
public init(alignment: Alignment = .fill, public init(alignment: Alignment = .fill,

View File

@ -7,11 +7,15 @@
import UIKit import UIKit
@available(iOS 13.0, *)
extension Stack { extension Stack {
public enum Alignment { public enum Alignment {
case fill case fill
case center
case start
case end
} }
public enum Distribution { public enum Distribution {

View File

@ -6,22 +6,23 @@
// //
import UIKit import UIKit
import Combine
@available(iOS 13.0, *)
public class Stack: UIView { public class Stack: UIView {
private var arrangedSubviews: [UIView] public var arrangedSubviews: [UIView]
private var alignment: Alignment public var distribution: Distribution
private var distribution: Distribution public var axis: Axis
private var axis: Axis public var spacing: CGFloat
private var spacing: CGFloat
var aStart: NSLayoutConstraint.Attribute { public var alignment: Alignment {
axis == .vertical ? .leading : .top didSet { setNeedsUpdateConstraints() }
} }
var aEnd: NSLayoutConstraint.Attribute { private var allConstraints: [NSLayoutConstraint] = []
axis == .vertical ? .trailing : .bottom
} private var cancellables: [AnyCancellable] = []
var dStart: NSLayoutConstraint.Attribute { var dStart: NSLayoutConstraint.Attribute {
axis == .vertical ? .top : .leading axis == .vertical ? .top : .leading
@ -31,6 +32,20 @@ public class Stack: UIView {
axis == .vertical ? .bottom : .trailing 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) { required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
@ -49,15 +64,30 @@ public class Stack: UIView {
super.init(frame: .zero) super.init(frame: .zero)
self.arrangedSubviews.forEach(addSubview) self.arrangedSubviews.forEach(addSubview)
setNeedsUpdateConstraints()
}
public override func updateConstraints() {
addAlignmentConstraints() addAlignmentConstraints()
addDistributionConstraints() addDistributionConstraints()
super.updateConstraints()
}
func clearConstraints() {
NSLayoutConstraint.deactivate(allConstraints)
removeConstraints(allConstraints)
allConstraints.removeAll()
} }
func addAlignmentConstraints() { func addAlignmentConstraints() {
switch alignment { clearConstraints()
case .fill:
addAlignmentFillConstraints() 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() { func addDistributionFillConstraints() {
guard let firstView = arrangedSubviews.first, guard let firstView = arrangedSubviews.first,
let lastView = arrangedSubviews.last let lastView = arrangedSubviews.last
@ -92,15 +108,32 @@ public class Stack: UIView {
let middleViews = arrangedSubviews.dropFirst().dropLast() let middleViews = arrangedSubviews.dropFirst().dropLast()
constraint(src: firstView, srcAttr: dStart, dst: self, dstAttr: dStart) addConstraint(src: firstView, srcAttr: dStart, dst: self, dstAttr: dStart)
constraint(src: lastView, srcAttr: dEnd, dst: self, dstAttr: dEnd) addConstraint(src: lastView, srcAttr: dEnd, dst: self, dstAttr: dEnd)
var currentView = firstView var currentView = firstView
for view in middleViews { 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 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
} }
} }

View 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
}
}

View File

@ -8,6 +8,18 @@
import UIKit import UIKit
import YadUI 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 { class ViewController: UIViewController {
override func viewDidLoad() { override func viewDidLoad() {
@ -15,23 +27,25 @@ class ViewController: UIViewController {
let child = buildView() let child = buildView()
view.addSubview(child) view.addSubview(child)
child.pin(toSafeArea: view) child.pin(toSafeArea: view, insets: .init(all: 16))
} }
@RootViewBuilder func buildView() -> UIView { @RootViewBuilder func buildView() -> UIView {
VStack(spacing: 16) { VStack(alignment: .start, spacing: 16) {
UIView() UILabel()
.background(.red) .text("qwe")
//.width(100)
.height(100)
UIView()
.background(.green)
//.width(100)
.height(100)
UIView()
.background(.blue) .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)
} }
} }