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,
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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
|
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,
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user