From 3182ca9f0a8f6d17b77376cec458b527816a1265 Mon Sep 17 00:00:00 2001 From: iWw Date: Wed, 10 Aug 2022 17:11:28 +0800 Subject: [PATCH 1/6] fix autoSpacing calculate error. --- Sources/StackKit/HStackView.swift | 39 ++++++++++++++++++++--------- Sources/StackKit/Spacer.swift | 20 +++++++-------- Sources/StackKit/VStackView.swift | 41 +++++++++++++++++++++---------- 3 files changed, 65 insertions(+), 35 deletions(-) diff --git a/Sources/StackKit/HStackView.swift b/Sources/StackKit/HStackView.swift index 5f02e32..1c2f712 100644 --- a/Sources/StackKit/HStackView.swift +++ b/Sources/StackKit/HStackView.swift @@ -78,9 +78,26 @@ open class HStackView: UIView { self.isHidden = effectiveSubviews.isEmpty } + private func tryResizeStackView() { + subviews.compactMap({ $0 as? HStackView }).forEach { + $0.sizeToFit() + } + subviews.compactMap({ $0 as? VStackView }).forEach { + $0.sizeToFit() + } + subviews.compactMap({ $0 as? HStackLayerWrapperView }).forEach { + $0.sizeToFit() + } + subviews.compactMap({ $0 as? VStackLayerWrapperView }).forEach { + $0.sizeToFit() + } + } + open override func layoutSubviews() { super.layoutSubviews() + tryResizeStackView() + switch alignment { case .top: effectiveSubviews.forEach { $0.frame.origin.y = 0 } @@ -174,7 +191,11 @@ extension HStackView { private func autoSpacing() -> CGFloat { let unspacerViews = viewsWithoutSpacer() let spacersCount = spacerViews().map({ isSpacerBetweenViews($0) }).filter({ $0 }).count - return (frame.width - viewsWidth() - spacerSpecifyLength()) / CGFloat(unspacerViews.count - spacersCount - 1) + let number = unspacerViews.count - spacersCount - 1 + if number <= 0 { + return 0 + } + return (frame.width - viewsWidth() - spacerSpecifyLength()) / CGFloat(number) } private func viewsWidth() -> CGFloat { @@ -254,19 +275,13 @@ extension HStackView { return false } - var isPreviousView = false - var isNextView = false - - let previous = index - 1 - if previous > 0, previous < effectiveSubviews.count - 1 { - isPreviousView = true + guard effectiveSubviews.count >= 3 else { + return false } - let next = index + 1 - if next < effectiveSubviews.count - 1 { - isNextView = true - } - return isPreviousView && isNextView + let start: Int = 1 + let end: Int = effectiveSubviews.count - 2 + return (start ... end).contains(index) } private func fillSpecifySpacer() { diff --git a/Sources/StackKit/Spacer.swift b/Sources/StackKit/Spacer.swift index 7d80013..b79e5f6 100644 --- a/Sources/StackKit/Spacer.swift +++ b/Sources/StackKit/Spacer.swift @@ -32,7 +32,7 @@ class SpacerView: UIView, _Spacer { var min: CGFloat = .leastNonzeroMagnitude var max: CGFloat = .greatestFiniteMagnitude - var setLength: CGFloat = -1 + var setLength: CGFloat = 0 required init(length: CGFloat, min: CGFloat, max: CGFloat) { super.init(frame: .zero) @@ -73,15 +73,15 @@ class SpacerView: UIView, _Spacer { switch size { case .width: - frame.size.width = length + frame.size.width = Swift.max(0, length) case .height: - frame.size.height = length + frame.size.height = Swift.max(0, length) } - self.setLength = length + self.setLength = Swift.max(0, length) } var minLength: CGFloat { - if setLength != -1 { + if setLength != 0 { return setLength } if length != .greatestFiniteMagnitude { @@ -107,7 +107,7 @@ class SpacerLayer: CALayer, _Spacer { var min: CGFloat = .leastNonzeroMagnitude var max: CGFloat = .greatestFiniteMagnitude - var setLength: CGFloat = -1 + var setLength: CGFloat = 0 required init(length: CGFloat, min: CGFloat, max: CGFloat) { super.init() @@ -150,15 +150,15 @@ class SpacerLayer: CALayer, _Spacer { switch size { case .width: - frame.size.width = length + frame.size.width = Swift.max(0, length) case .height: - frame.size.height = length + frame.size.height = Swift.max(0, length) } - self.setLength = length + self.setLength = Swift.max(0, length) } var minLength: CGFloat { - if setLength != -1 { + if setLength != 0 { return setLength } if length != .greatestFiniteMagnitude { diff --git a/Sources/StackKit/VStackView.swift b/Sources/StackKit/VStackView.swift index 429671a..00aba7d 100644 --- a/Sources/StackKit/VStackView.swift +++ b/Sources/StackKit/VStackView.swift @@ -78,9 +78,26 @@ open class VStackView: UIView { self.isHidden = effectiveSubviews.isEmpty } + private func tryResizeStackView() { + subviews.compactMap({ $0 as? HStackView }).forEach { + $0.sizeToFit() + } + subviews.compactMap({ $0 as? VStackView }).forEach { + $0.sizeToFit() + } + subviews.compactMap({ $0 as? HStackLayerWrapperView }).forEach { + $0.sizeToFit() + } + subviews.compactMap({ $0 as? VStackLayerWrapperView }).forEach { + $0.sizeToFit() + } + } + open override func layoutSubviews() { super.layoutSubviews() + tryResizeStackView() + switch alignment { case .left: effectiveSubviews.forEach { $0.frame.origin.x = 0 } @@ -174,7 +191,11 @@ extension VStackView { private func autoSpacing() -> CGFloat { let unspacerViews = viewsWithoutSpacer() let spacersCount = spacerViews().map({ isSpacerBetweenViews($0) }).filter({ $0 }).count - return (frame.height - viewsHeight() - spacerSpecifyLength()) / CGFloat(unspacerViews.count - spacersCount - 1) + let number = unspacerViews.count - spacersCount - 1 + if number <= 0 { + return 0 + } + return (frame.height - viewsHeight() - spacerSpecifyLength()) / CGFloat( number) } private func viewsHeight() -> CGFloat { @@ -252,23 +273,17 @@ extension VStackView { } private func isSpacerBetweenViews(_ spacer: SpacerView) -> Bool { - guard let index = subviews.firstIndex(of: spacer) else { + guard let index = effectiveSubviews.firstIndex(of: spacer) else { return false } - var isPreviousView = false - var isNextView = false - - let previous = index - 1 - if previous > 0, previous < subviews.count - 1 { - isPreviousView = true + guard effectiveSubviews.count >= 3 else { + return false } - let next = index + 1 - if next < subviews.count - 1 { - isNextView = true - } - return isPreviousView && isNextView + let start: Int = 1 + let end: Int = effectiveSubviews.count - 2 + return (start ... end).contains(index) } /// 填充 spacer 最小值 From a7ac514fd0ab509d7d7cfbe9f6f849570f4f39dd Mon Sep 17 00:00:00 2001 From: iWw Date: Wed, 10 Aug 2022 18:50:58 +0800 Subject: [PATCH 2/6] add fitSize for UIView --- Sources/StackKit/HStackView.swift | 13 +--- Sources/StackKit/UIKit+FitSize.swift | 87 +++++++++++++++++++++++++ Sources/StackKit/VStackView.swift | 13 +--- Tests/StackKitTests/StackKitTests.swift | 7 ++ 4 files changed, 98 insertions(+), 22 deletions(-) create mode 100644 Sources/StackKit/UIKit+FitSize.swift diff --git a/Sources/StackKit/HStackView.swift b/Sources/StackKit/HStackView.swift index 1c2f712..45d34af 100644 --- a/Sources/StackKit/HStackView.swift +++ b/Sources/StackKit/HStackView.swift @@ -79,17 +79,8 @@ open class HStackView: UIView { } private func tryResizeStackView() { - subviews.compactMap({ $0 as? HStackView }).forEach { - $0.sizeToFit() - } - subviews.compactMap({ $0 as? VStackView }).forEach { - $0.sizeToFit() - } - subviews.compactMap({ $0 as? HStackLayerWrapperView }).forEach { - $0.sizeToFit() - } - subviews.compactMap({ $0 as? VStackLayerWrapperView }).forEach { - $0.sizeToFit() + subviews.forEach { fitSize in + fitSize._fitSize(with: fitSize.stackKitFitType) } } diff --git a/Sources/StackKit/UIKit+FitSize.swift b/Sources/StackKit/UIKit+FitSize.swift new file mode 100644 index 0000000..5a29488 --- /dev/null +++ b/Sources/StackKit/UIKit+FitSize.swift @@ -0,0 +1,87 @@ +import UIKit + +var _FitTypeKey = "_FitTypeKey" + +public enum FitType { + case content + + case widthFixed(_ value: CGFloat) + case heightFixed(_ value: CGFloat) + + case widthFlexible(_ value: CGFloat) + case heightFlexible(_ value: CGFloat) + + case size(_ size: CGSize) +} + +protocol FitSize { + var stackKitFitType: FitType { get set } + func _fitSize(with fitType: FitType) +} + +extension UIView: FitSize { + + public internal(set) var stackKitFitType: FitType { + get { + guard let fitType = objc_getAssociatedObject(self, &_FitTypeKey) as? FitType else { + return .content + } + return fitType + } + set { + objc_setAssociatedObject(self, &_FitTypeKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + + public func fitSize(with fitType: FitType) -> Self { + self.stackKitFitType = fitType + return self + } + + func _fitSize(with fitType: FitType = .content) { + + defer { + layoutSubviews() + } + + switch fitType { + case .content: + sizeToFit() + + case .widthFixed(let value): + let size = CGSize( + width: value, + height: CGFloat.greatestFiniteMagnitude + ) + var fitSize = sizeThatFits(size) + fitSize.width = value + self.frame.size = fitSize + + case .heightFixed(let value): + let size = CGSize( + width: CGFloat.greatestFiniteMagnitude, + height: value + ) + var fitSize = sizeThatFits(size) + fitSize.height = value + self.frame.size = fitSize + + case .widthFlexible(let value): + let size = CGSize( + width: value, + height: CGFloat.greatestFiniteMagnitude + ) + self.frame.size = sizeThatFits(size) + + case .heightFlexible(let value): + let size = CGSize( + width: CGFloat.greatestFiniteMagnitude, + height: value + ) + self.frame.size = sizeThatFits(size) + + case .size(let size): + self.frame.size = size + } + } +} diff --git a/Sources/StackKit/VStackView.swift b/Sources/StackKit/VStackView.swift index 00aba7d..0df8378 100644 --- a/Sources/StackKit/VStackView.swift +++ b/Sources/StackKit/VStackView.swift @@ -79,17 +79,8 @@ open class VStackView: UIView { } private func tryResizeStackView() { - subviews.compactMap({ $0 as? HStackView }).forEach { - $0.sizeToFit() - } - subviews.compactMap({ $0 as? VStackView }).forEach { - $0.sizeToFit() - } - subviews.compactMap({ $0 as? HStackLayerWrapperView }).forEach { - $0.sizeToFit() - } - subviews.compactMap({ $0 as? VStackLayerWrapperView }).forEach { - $0.sizeToFit() + subviews.forEach { fitSize in + fitSize._fitSize(with: fitSize.stackKitFitType) } } diff --git a/Tests/StackKitTests/StackKitTests.swift b/Tests/StackKitTests/StackKitTests.swift index 8726459..3c107fb 100644 --- a/Tests/StackKitTests/StackKitTests.swift +++ b/Tests/StackKitTests/StackKitTests.swift @@ -7,5 +7,12 @@ final class StackKitTests: XCTestCase { // Use XCTAssert and related functions to verify your tests produce the correct // results. + let label = UILabel().fitSize(with: .size(CGSize(width: 100, height: 22))) + label.text = "hello world" + let h = HStackView { + label + }.sizeToFit() + print(label.frame) + print(label.frame.width < 100) } } From fc174e1a24f64d19b3719e4804254d25f15a4d76 Mon Sep 17 00:00:00 2001 From: iWe Date: Thu, 11 Aug 2022 01:14:09 +0800 Subject: [PATCH 3/6] add UIKit+StackKit --- Sources/StackKit/HStackView.swift | 3 +- Sources/StackKit/UIKit+FitSize.swift | 72 +++++++++++++++++++++---- Sources/StackKit/UIKit+StackKit.swift | 44 +++++++++++++++ Sources/StackKit/VStackView.swift | 3 +- Tests/StackKitTests/StackKitTests.swift | 8 --- 5 files changed, 110 insertions(+), 20 deletions(-) create mode 100644 Sources/StackKit/UIKit+StackKit.swift diff --git a/Sources/StackKit/HStackView.swift b/Sources/StackKit/HStackView.swift index 45d34af..a1f6157 100644 --- a/Sources/StackKit/HStackView.swift +++ b/Sources/StackKit/HStackView.swift @@ -131,7 +131,8 @@ open class HStackView: UIView { } open override func sizeThatFits(_ size: CGSize) -> CGSize { - layoutSubviews() + setNeedsLayout() + layoutIfNeeded() var _size = size if size.width == CGFloat.greatestFiniteMagnitude || size.width == 0 { diff --git a/Sources/StackKit/UIKit+FitSize.swift b/Sources/StackKit/UIKit+FitSize.swift index 5a29488..351fe1e 100644 --- a/Sources/StackKit/UIKit+FitSize.swift +++ b/Sources/StackKit/UIKit+FitSize.swift @@ -3,13 +3,19 @@ import UIKit var _FitTypeKey = "_FitTypeKey" public enum FitType { + /// Just only call `.sizeToFit()`. case content + /// The width fixed. case widthFixed(_ value: CGFloat) + /// The height fixed. case heightFixed(_ value: CGFloat) - case widthFlexible(_ value: CGFloat) - case heightFlexible(_ value: CGFloat) + /// The width flexible. + case widthFlexible(_ value: CGFloat? = nil, min: CGFloat? = nil, max: CGFloat? = nil) + + /// The height flexible. + case heightFlexible(_ value: CGFloat? = nil, min: CGFloat? = nil, max: CGFloat? = nil) case size(_ size: CGSize) } @@ -29,7 +35,7 @@ extension UIView: FitSize { return fitType } set { - objc_setAssociatedObject(self, &_FitTypeKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + objc_setAssociatedObject(self, &_FitTypeKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC) } } @@ -41,7 +47,7 @@ extension UIView: FitSize { func _fitSize(with fitType: FitType = .content) { defer { - layoutSubviews() + setNeedsLayout() } switch fitType { @@ -66,22 +72,68 @@ extension UIView: FitSize { fitSize.height = value self.frame.size = fitSize - case .widthFlexible(let value): + case .widthFlexible(let value, let minWidth, let maxWidth): + var fitWidth: CGFloat = superSize()?.width ?? .greatestFiniteMagnitude + if let value = value { + fitWidth = value + } + if let minWidth = minWidth, minWidth > fitWidth { + fitWidth = minWidth + } + if let maxWidth = maxWidth, maxWidth < fitWidth { + fitWidth = maxWidth + } let size = CGSize( - width: value, + width: fitWidth, height: CGFloat.greatestFiniteMagnitude ) - self.frame.size = sizeThatFits(size) + var newSize = sizeThatFits(size) + if let minWidth = minWidth, minWidth > newSize.width { + newSize.width = minWidth + } + if let maxWidth = maxWidth, maxWidth < newSize.width { + newSize.width = maxWidth + } + self.frame.size = newSize - case .heightFlexible(let value): + case .heightFlexible(let value, let minHeight, let maxHeight): + var fitHeight: CGFloat = superSize()?.height ?? .greatestFiniteMagnitude + if let value = value { + fitHeight = value + } + if let minHeight = minHeight, minHeight > fitHeight { + fitHeight = minHeight + } + if let maxHeight = maxHeight, maxHeight < fitHeight { + fitHeight = maxHeight + } let size = CGSize( width: CGFloat.greatestFiniteMagnitude, - height: value + height: fitHeight ) - self.frame.size = sizeThatFits(size) + var newSize = sizeThatFits(size) + if let minHeight = minHeight, minHeight > newSize.height { + newSize.height = minHeight + } + if let maxHeight = maxHeight, maxHeight < newSize.height { + newSize.height = maxHeight + } + self.frame.size = newSize case .size(let size): self.frame.size = size } } + + private func superSize() -> CGSize? { + var spv = superview + while spv != nil { + if spv?.frame.size == .zero { + spv = spv?.superview + continue + } + break + } + return spv?.frame.size + } } diff --git a/Sources/StackKit/UIKit+StackKit.swift b/Sources/StackKit/UIKit+StackKit.swift new file mode 100644 index 0000000..8e66d70 --- /dev/null +++ b/Sources/StackKit/UIKit+StackKit.swift @@ -0,0 +1,44 @@ +import UIKit + +public class StackKitCompatible { + public let view: Base + + init(view: Base) { + self.view = view + } +} + +public protocol StackKitCompatibleProvider { + associatedtype O + var stack: O { get } + static var stack: O { get } +} + +public extension StackKitCompatibleProvider where Self: UIView { + var stack: StackKitCompatible { + return StackKitCompatible(view: self) + } + static var stack: StackKitCompatible { + return StackKitCompatible(view: Self.init()) + } +} + +extension UIView: StackKitCompatibleProvider { } + + +extension StackKitCompatible where Base: UIView { + + func maxWidth() -> Self { + return self + } + func maxHeight() -> Self { + return self + } + func minWidth() -> Self { + return self + } + func minHeight() -> Self { + return self + } + +} diff --git a/Sources/StackKit/VStackView.swift b/Sources/StackKit/VStackView.swift index 0df8378..80de3aa 100644 --- a/Sources/StackKit/VStackView.swift +++ b/Sources/StackKit/VStackView.swift @@ -131,7 +131,8 @@ open class VStackView: UIView { } open override func sizeThatFits(_ size: CGSize) -> CGSize { - layoutSubviews() + setNeedsLayout() + layoutIfNeeded() var _size = size if size.width == CGFloat.greatestFiniteMagnitude || size.width == 0 { diff --git a/Tests/StackKitTests/StackKitTests.swift b/Tests/StackKitTests/StackKitTests.swift index 3c107fb..ba661ae 100644 --- a/Tests/StackKitTests/StackKitTests.swift +++ b/Tests/StackKitTests/StackKitTests.swift @@ -6,13 +6,5 @@ final class StackKitTests: XCTestCase { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct // results. - - let label = UILabel().fitSize(with: .size(CGSize(width: 100, height: 22))) - label.text = "hello world" - let h = HStackView { - label - }.sizeToFit() - print(label.frame) - print(label.frame.width < 100) } } From b4bdb2efe6bfdc30978be4b7b3162e9b0f9103d2 Mon Sep 17 00:00:00 2001 From: iWe Date: Thu, 11 Aug 2022 02:54:13 +0800 Subject: [PATCH 4/6] fix Runtime cgFloat set error, add fittype and stack for UIView --- Sources/StackKit/Runtime.swift | 38 ++++ Sources/StackKit/StackKitResultBuilders.swift | 3 + Sources/StackKit/UIKit+FitSize.swift | 198 ++++++++++-------- Sources/StackKit/UIKit+StackKit.swift | 138 +++++++++++- 4 files changed, 284 insertions(+), 93 deletions(-) create mode 100644 Sources/StackKit/Runtime.swift diff --git a/Sources/StackKit/Runtime.swift b/Sources/StackKit/Runtime.swift new file mode 100644 index 0000000..017b0e1 --- /dev/null +++ b/Sources/StackKit/Runtime.swift @@ -0,0 +1,38 @@ +// +// File.swift +// +// +// Created by i on 2022/8/11. +// + +import UIKit + +struct Runtime { + init() { } +} + +extension Runtime { + + static func getProperty(_ object: Any, key: UnsafeRawPointer) -> Any? { + objc_getAssociatedObject(object, key) + } + + static func setProperty(_ object: Any, key: UnsafeRawPointer, value: Any?, policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) { + objc_setAssociatedObject(object, key, value, policy) + } + + static func getCGFloatProperty(_ object: Any, key: UnsafeRawPointer) -> CGFloat? { + guard let value = getProperty(object, key: key) as? NSNumber else { + return nil + } + return CGFloat(truncating: value) + } + + static func setCGFloatProperty(_ object: Any, key: UnsafeRawPointer, _ value: CGFloat?) { + guard let v = value else { + objc_setAssociatedObject(object, key, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + return + } + objc_setAssociatedObject(object, key, NSNumber(value: Double(v)), .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } +} diff --git a/Sources/StackKit/StackKitResultBuilders.swift b/Sources/StackKit/StackKitResultBuilders.swift index f64aa76..7630c74 100644 --- a/Sources/StackKit/StackKitResultBuilders.swift +++ b/Sources/StackKit/StackKitResultBuilders.swift @@ -21,6 +21,9 @@ extension _StackKitViewContentResultBuilderProvider { public static func buildExpression(_ expression: Void) -> [UIView] { [] } + public static func buildExpression(_ expression: StackKitCompatible) -> [T] where T: UIView { + [expression.view] + } } // MARK: For VStack View diff --git a/Sources/StackKit/UIKit+FitSize.swift b/Sources/StackKit/UIKit+FitSize.swift index 351fe1e..7b1ade2 100644 --- a/Sources/StackKit/UIKit+FitSize.swift +++ b/Sources/StackKit/UIKit+FitSize.swift @@ -6,18 +6,20 @@ public enum FitType { /// Just only call `.sizeToFit()`. case content - /// The width fixed. - case widthFixed(_ value: CGFloat) - /// The height fixed. - case heightFixed(_ value: CGFloat) + case width + case height - /// The width flexible. - case widthFlexible(_ value: CGFloat? = nil, min: CGFloat? = nil, max: CGFloat? = nil) + case widthFlexible + case heightFlexible - /// The height flexible. - case heightFlexible(_ value: CGFloat? = nil, min: CGFloat? = nil, max: CGFloat? = nil) - - case size(_ size: CGSize) + var isFlexible: Bool { + if case .widthFlexible = self { + return true + } else if case .heightFlexible = self { + return true + } + return false + } } protocol FitSize { @@ -27,7 +29,7 @@ protocol FitSize { extension UIView: FitSize { - public internal(set) var stackKitFitType: FitType { + var stackKitFitType: FitType { get { guard let fitType = objc_getAssociatedObject(self, &_FitTypeKey) as? FitType else { return .content @@ -39,7 +41,7 @@ extension UIView: FitSize { } } - public func fitSize(with fitType: FitType) -> Self { + func fitSize(with fitType: FitType) -> Self { self.stackKitFitType = fitType return self } @@ -50,90 +52,112 @@ extension UIView: FitSize { setNeedsLayout() } + if fitType == .content, let w = _width, let h = _height { + self.frame.size = CGSize(width: w, height: h) + return + } + + var fitWidth = CGFloat.greatestFiniteMagnitude + var fitHeight = CGFloat.greatestFiniteMagnitude + var size = resolveSize() + switch fitType { - case .content: - sizeToFit() - - case .widthFixed(let value): - let size = CGSize( - width: value, - height: CGFloat.greatestFiniteMagnitude - ) - var fitSize = sizeThatFits(size) - fitSize.width = value - self.frame.size = fitSize - - case .heightFixed(let value): - let size = CGSize( - width: CGFloat.greatestFiniteMagnitude, - height: value - ) - var fitSize = sizeThatFits(size) - fitSize.height = value - self.frame.size = fitSize - - case .widthFlexible(let value, let minWidth, let maxWidth): - var fitWidth: CGFloat = superSize()?.width ?? .greatestFiniteMagnitude - if let value = value { - fitWidth = value - } - if let minWidth = minWidth, minWidth > fitWidth { - fitWidth = minWidth - } - if let maxWidth = maxWidth, maxWidth < fitWidth { - fitWidth = maxWidth + case .width, .widthFlexible: + if let w = applyMinMax(toWidth: size.width) { + fitWidth = w + } else { + fitWidth = bounds.width } - let size = CGSize( - width: fitWidth, - height: CGFloat.greatestFiniteMagnitude - ) - var newSize = sizeThatFits(size) - if let minWidth = minWidth, minWidth > newSize.width { - newSize.width = minWidth + case .height, .heightFlexible: + if let h = applyMinMax(toHeight: size.height) { + fitHeight = h + } else { + fitHeight = bounds.height } - if let maxWidth = maxWidth, maxWidth < newSize.width { - newSize.width = maxWidth - } - self.frame.size = newSize + case .content: + let fs = bounds.size + fitWidth = fs.width + fitHeight = fs.height + } + + let sizeThatFits = sizeThatFits(CGSize(width: fitWidth, height: fitHeight)) + + switch fitType { + case .content: + size = Size(width: sizeThatFits.width, height: sizeThatFits.height) - case .heightFlexible(let value, let minHeight, let maxHeight): - var fitHeight: CGFloat = superSize()?.height ?? .greatestFiniteMagnitude - if let value = value { - fitHeight = value - } - if let minHeight = minHeight, minHeight > fitHeight { - fitHeight = minHeight - } - if let maxHeight = maxHeight, maxHeight < fitHeight { - fitHeight = maxHeight + case .width, .widthFlexible, .height, .heightFlexible: + if fitWidth != .greatestFiniteMagnitude { + size.width = fitType.isFlexible ? sizeThatFits.width : fitWidth + } else { + size.width = sizeThatFits.width } - let size = CGSize( - width: CGFloat.greatestFiniteMagnitude, - height: fitHeight - ) - var newSize = sizeThatFits(size) - if let minHeight = minHeight, minHeight > newSize.height { - newSize.height = minHeight - } - if let maxHeight = maxHeight, maxHeight < newSize.height { - newSize.height = maxHeight - } - self.frame.size = newSize - case .size(let size): - self.frame.size = size + if fitHeight != .greatestFiniteMagnitude { + size.height = fitType.isFlexible ? sizeThatFits.height : fitHeight + } else { + size.height = sizeThatFits.height + } + } + + size.width = applyMinMax(toWidth: size.width) + size.height = applyMinMax(toHeight: size.height) + + if let w = size.width, let h = size.height { + self.bounds.size = CGSize(width: w, height: h) + } else { + fatalError("Size has some error.") } } +} + +extension UIView { - private func superSize() -> CGSize? { - var spv = superview - while spv != nil { - if spv?.frame.size == .zero { - spv = spv?.superview - continue - } - break + struct Size { + var width: CGFloat? + var height: CGFloat? + } + + func resolveSize() -> Size { + var size = Size() + if let _width = _width { + size.width = _width + } + if let _height = _height { + size.height = _height } - return spv?.frame.size + return size } + + func applyMinMax(toWidth width: CGFloat?) -> CGFloat? { + var result = width + + // Handle minWidth + if let minWidth = _minWidth, minWidth > (result ?? 0) { + result = minWidth + } + + // Handle maxWidth + if let maxWidth = _maxWidth, maxWidth < (result ?? CGFloat.greatestFiniteMagnitude) { + result = maxWidth + } + return result + } + + func applyMinMax(toHeight height: CGFloat?) -> CGFloat? { + var result = height + + // Handle minWidth + if let minWidth = _minHeight, minWidth > (result ?? 0) { + result = minWidth + } + + // Handle maxWidth + if let maxWidth = _maxHeight, maxWidth < (result ?? CGFloat.greatestFiniteMagnitude) { + result = maxWidth + } + + return result + } + } diff --git a/Sources/StackKit/UIKit+StackKit.swift b/Sources/StackKit/UIKit+StackKit.swift index 8e66d70..9c6de2b 100644 --- a/Sources/StackKit/UIKit+StackKit.swift +++ b/Sources/StackKit/UIKit+StackKit.swift @@ -1,8 +1,101 @@ import UIKit -public class StackKitCompatible { - public let view: Base +struct _UIView_StackKitKeys { + static var widthKey = "StackKit_widthKey" + static var heightKey = "StackKit_heightKey" + + static var minWidthKey = "StackKit_minWidthKey" + static var maxWidthKey = "StackKit_maxWidthKey" + + static var minHeightKey = "StackKit_minHeightKey" + static var maxHeightKey = "StackKit_maxHeightKey" +} +protocol _UIView_StackKitProvider { + var _width: CGFloat? { get set } + var _height: CGFloat? { get set } + + var _minWidth: CGFloat? { get set } + var _maxWidth: CGFloat? { get set } + + var _minHeight: CGFloat? { get set } + var _maxHeight: CGFloat? { get set } +} + +extension UIView: _UIView_StackKitProvider { + var _width: CGFloat? { + get { + guard let value = Runtime.getCGFloatProperty(self, key: &_UIView_StackKitKeys.widthKey) else { + return nil + } + return value + } + set { + Runtime.setCGFloatProperty(self, key: &_UIView_StackKitKeys.widthKey, newValue) + } + } + var _height: CGFloat? { + get { + guard let value = Runtime.getCGFloatProperty(self, key: &_UIView_StackKitKeys.heightKey) else { + return nil + } + return value + } + set { + Runtime.setCGFloatProperty(self, key: &_UIView_StackKitKeys.heightKey, newValue) + } + } + var _minWidth: CGFloat? { + get { + guard let value = Runtime.getCGFloatProperty(self, key: &_UIView_StackKitKeys.minWidthKey) else { + return nil + } + return value + } + set { + Runtime.setCGFloatProperty(self, key: &_UIView_StackKitKeys.minWidthKey, newValue) + } + } + + var _maxWidth: CGFloat? { + get { + guard let value = Runtime.getCGFloatProperty(self, key: &_UIView_StackKitKeys.maxWidthKey) else { + return nil + } + return value + } + set { + Runtime.setCGFloatProperty(self, key: &_UIView_StackKitKeys.maxWidthKey, newValue) + } + } + + var _minHeight: CGFloat? { + get { + guard let value = Runtime.getCGFloatProperty(self, key: &_UIView_StackKitKeys.minHeightKey) else { + return nil + } + return value + } + set { + Runtime.setCGFloatProperty(self, key: &_UIView_StackKitKeys.minHeightKey, newValue) + } + } + + var _maxHeight: CGFloat? { + get { + guard let value = Runtime.getCGFloatProperty(self, key: &_UIView_StackKitKeys.maxHeightKey) else { + return nil + } + return value + } + set { + Runtime.setCGFloatProperty(self, key: &_UIView_StackKitKeys.maxHeightKey, newValue) + } + } +} + +public class StackKitCompatible { + let view: Base init(view: Base) { self.view = view } @@ -28,17 +121,50 @@ extension UIView: StackKitCompatibleProvider { } extension StackKitCompatible where Base: UIView { - func maxWidth() -> Self { + public func width(_ value: CGFloat?) -> Self { + view._width = value return self } - func maxHeight() -> Self { + public func height(_ value: CGFloat?) -> Self { + view._height = value + return self + } + + public func maxWidth(_ value: CGFloat?) -> Self { + view._maxWidth = value return self } - func minWidth() -> Self { + public func maxHeight(_ value: CGFloat?) -> Self { + view._maxHeight = value return self } - func minHeight() -> Self { + public func minWidth(_ value: CGFloat?) -> Self { + view._minWidth = value + return self + } + public func minHeight(_ value: CGFloat?) -> Self { + view._minHeight = value return self } + public func size(_ length: CGFloat) -> Self { + view._width = length + view._height = length + return self + } + + public func size(_ width: CGFloat, _ height: CGFloat) -> Self { + view._width = width + view._height = height + return self + } + + public func sizeToFit(_ fitType: FitType = .content) -> Self { + view.stackKitFitType = fitType + return self + } + public func then(_ then: (Base) -> Void) -> Self { + then(self.view) + return self + } } From 9f6dda6c82176e4acc38db44c6d87ab9d35ad943 Mon Sep 17 00:00:00 2001 From: iWw Date: Thu, 11 Aug 2022 11:30:28 +0800 Subject: [PATCH 5/6] fix fitSize --- Sources/StackKit/UIKit+FitSize.swift | 31 +++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/Sources/StackKit/UIKit+FitSize.swift b/Sources/StackKit/UIKit+FitSize.swift index 7b1ade2..ef6268a 100644 --- a/Sources/StackKit/UIKit+FitSize.swift +++ b/Sources/StackKit/UIKit+FitSize.swift @@ -3,7 +3,6 @@ import UIKit var _FitTypeKey = "_FitTypeKey" public enum FitType { - /// Just only call `.sizeToFit()`. case content case width @@ -57,8 +56,9 @@ extension UIView: FitSize { return } - var fitWidth = CGFloat.greatestFiniteMagnitude - var fitHeight = CGFloat.greatestFiniteMagnitude + // 优先从设定的 width 和 height 获取 + var fitWidth = _width ?? CGFloat.greatestFiniteMagnitude + var fitHeight = _height ?? CGFloat.greatestFiniteMagnitude var size = resolveSize() switch fitType { @@ -76,15 +76,19 @@ extension UIView: FitSize { } case .content: let fs = bounds.size - fitWidth = fs.width - fitHeight = fs.height + fitWidth = size.width ?? fs.width + fitHeight = size.height ?? fs.width } + fitWidth = _validateValue(fitWidth) + fitHeight = _validateValue(fitHeight) + let sizeThatFits = sizeThatFits(CGSize(width: fitWidth, height: fitHeight)) switch fitType { case .content: size = Size(width: sizeThatFits.width, height: sizeThatFits.height) + _fixedSize(&size) case .width, .widthFlexible, .height, .heightFlexible: if fitWidth != .greatestFiniteMagnitude { @@ -102,6 +106,7 @@ extension UIView: FitSize { size.width = applyMinMax(toWidth: size.width) size.height = applyMinMax(toHeight: size.height) + _fixedSize(&size) if let w = size.width, let h = size.height { self.bounds.size = CGSize(width: w, height: h) @@ -109,6 +114,22 @@ extension UIView: FitSize { fatalError("Size has some error.") } } + + private func _validateValue(_ value: CGFloat?) -> CGFloat { + guard let value = value, value > 0, value.isFinite else { + return .greatestFiniteMagnitude + } + return value + } + + private func _fixedSize(_ size: inout Size) { + if let w = _width { + size.width = w + } + if let h = _height { + size.height = h + } + } } extension UIView { From ce164b64d8f57a9ab60bfe74b563b9337dd96499 Mon Sep 17 00:00:00 2001 From: iWw Date: Thu, 11 Aug 2022 12:11:07 +0800 Subject: [PATCH 6/6] fix some error --- Sources/StackKit/UIKit+FitSize.swift | 52 +++++++++++++--------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/Sources/StackKit/UIKit+FitSize.swift b/Sources/StackKit/UIKit+FitSize.swift index ef6268a..ce30250 100644 --- a/Sources/StackKit/UIKit+FitSize.swift +++ b/Sources/StackKit/UIKit+FitSize.swift @@ -22,18 +22,15 @@ public enum FitType { } protocol FitSize { - var stackKitFitType: FitType { get set } - func _fitSize(with fitType: FitType) + var stackKitFitType: FitType? { get set } + func _fitSize(with fitType: FitType?) } extension UIView: FitSize { - var stackKitFitType: FitType { + var stackKitFitType: FitType? { get { - guard let fitType = objc_getAssociatedObject(self, &_FitTypeKey) as? FitType else { - return .content - } - return fitType + objc_getAssociatedObject(self, &_FitTypeKey) as? FitType } set { objc_setAssociatedObject(self, &_FitTypeKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC) @@ -45,21 +42,26 @@ extension UIView: FitSize { return self } - func _fitSize(with fitType: FitType = .content) { + func _fitSize(with fitType: FitType? = .content) { defer { setNeedsLayout() } - if fitType == .content, let w = _width, let h = _height { + var size = resolveSize() + + if let w = size.width, let h = size.height { // 指定了 size(width & height) 优先使用 size self.frame.size = CGSize(width: w, height: h) return } - // 优先从设定的 width 和 height 获取 - var fitWidth = _width ?? CGFloat.greatestFiniteMagnitude - var fitHeight = _height ?? CGFloat.greatestFiniteMagnitude - var size = resolveSize() + guard let fitType = fitType else { + self.frame.size = sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: .greatestFiniteMagnitude)) + return + } + + var fitWidth = CGFloat.greatestFiniteMagnitude + var fitHeight = CGFloat.greatestFiniteMagnitude switch fitType { case .width, .widthFlexible: @@ -75,9 +77,8 @@ extension UIView: FitSize { fitHeight = bounds.height } case .content: - let fs = bounds.size - fitWidth = size.width ?? fs.width - fitHeight = size.height ?? fs.width + fitWidth = size.width ?? bounds.width + fitHeight = size.height ?? bounds.height } fitWidth = _validateValue(fitWidth) @@ -86,11 +87,7 @@ extension UIView: FitSize { let sizeThatFits = sizeThatFits(CGSize(width: fitWidth, height: fitHeight)) switch fitType { - case .content: - size = Size(width: sizeThatFits.width, height: sizeThatFits.height) - _fixedSize(&size) - - case .width, .widthFlexible, .height, .heightFlexible: + case .width, .height, .widthFlexible, .heightFlexible: if fitWidth != .greatestFiniteMagnitude { size.width = fitType.isFlexible ? sizeThatFits.width : fitWidth } else { @@ -102,17 +99,14 @@ extension UIView: FitSize { } else { size.height = sizeThatFits.height } + case .content: + size = Size(width: sizeThatFits.width, height: sizeThatFits.height) } size.width = applyMinMax(toWidth: size.width) size.height = applyMinMax(toHeight: size.height) - _fixedSize(&size) - if let w = size.width, let h = size.height { - self.bounds.size = CGSize(width: w, height: h) - } else { - fatalError("Size has some error.") - } + self.frame.size = size.cgSize } private func _validateValue(_ value: CGFloat?) -> CGFloat { @@ -137,6 +131,10 @@ extension UIView { struct Size { var width: CGFloat? var height: CGFloat? + + var cgSize: CGSize { + CGSize(width: width ?? 0, height: height ?? 0) + } } func resolveSize() -> Size {