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,
dst: UIView,
dstAttr: NSLayoutConstraint.Attribute,
constant: CGFloat = 0) {
constant: CGFloat = 0) -> NSLayoutConstraint {
addConstraint(.init(item: src,
let constraint = NSLayoutConstraint(item: src,
attribute: srcAttr,
relatedBy: .equal,
toItem: dst,
attribute: dstAttr,
multiplier: 1.0,
constant: constant))
constant: constant)
addConstraint(constraint)
return constraint
}
}

View File

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

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
@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,

View File

@ -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 {

View File

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

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