diff --git a/Demo/Demo.xcodeproj/project.pbxproj b/Demo/Demo.xcodeproj/project.pbxproj index 91b9570..3be35f9 100644 --- a/Demo/Demo.xcodeproj/project.pbxproj +++ b/Demo/Demo.xcodeproj/project.pbxproj @@ -74,13 +74,13 @@ 08E16CD8289ABA1C0019D7CB /* AppDelegate.swift */, 08E16CDA289ABA1C0019D7CB /* SceneDelegate.swift */, 080677C528BE7D8B00000E16 /* ViewController.swift */, + 0815C092292A2B37001655C6 /* Preview1ViewController.swift */, 08E16CDC289ABA1C0019D7CB /* HVStackDemoViewController.swift */, 080677C728BE7DA400000E16 /* WrapStackDemoViewController.swift */, 08E16CDE289ABA1C0019D7CB /* Main.storyboard */, 08E16CE1289ABA1D0019D7CB /* Assets.xcassets */, 08E16CE3289ABA1D0019D7CB /* LaunchScreen.storyboard */, 08E16CE6289ABA1D0019D7CB /* Info.plist */, - 0815C092292A2B37001655C6 /* Preview1ViewController.swift */, ); path = Demo; sourceTree = ""; diff --git a/Demo/Demo/Base.lproj/Main.storyboard b/Demo/Demo/Base.lproj/Main.storyboard index 4c4cb3f..2705f63 100644 --- a/Demo/Demo/Base.lproj/Main.storyboard +++ b/Demo/Demo/Base.lproj/Main.storyboard @@ -74,7 +74,7 @@ - + diff --git a/Demo/Demo/HVStackDemoViewController.swift b/Demo/Demo/HVStackDemoViewController.swift index 0d69682..5cf8132 100644 --- a/Demo/Demo/HVStackDemoViewController.swift +++ b/Demo/Demo/HVStackDemoViewController.swift @@ -153,35 +153,31 @@ class HVStackDemoViewController: UIViewController { let descriptionContent = VStackView { Spacer(length: 12) - HStackView { - Spacer(length: 12) + VStackView(alignment: .left, distribution: .spacing(24)) { + UILabel().stack.then { + $0.font = .systemFont(ofSize: 20, weight: .semibold) + $0.text = "⚠️ Important" + } + UILabel().stack.maxWidth(UIScreen.main.bounds.width - 48).then { + $0.textColor = .systemPink + $0.font = .systemFont(ofSize: 14, weight: .medium) + $0.numberOfLines = 0 + $0.text = "`Spacer` will ignore the given spacing in H/VStack\nSpacer 会忽略在 H/VStack 中给定的 spacing, 也就是说可以使用 Spacer 自由调整 spacing" + } - VStackView(alignment: .left, distribution: .spacing(24)) { - UILabel().stack.then { - $0.font = .systemFont(ofSize: 20, weight: .semibold) - $0.text = "⚠️ Important" - } - UILabel().stack.maxWidth(UIScreen.main.bounds.width - 48).then { - $0.textColor = .systemPink - $0.font = .systemFont(ofSize: 14, weight: .medium) - $0.numberOfLines = 0 - $0.text = "`Spacer` will ignore the given spacing in H/VStack\nSpacer 会忽略在 H/VStack 中给定的 spacing, 也就是说可以使用 Spacer 自由调整 spacing" - } - - UILabel().stack.then { - $0.textColor = .systemPink - $0.font = .systemFont(ofSize: 14, weight: .medium) - $0.numberOfLines = 0 - $0.text = "Do not support relative layout for the time being\n暂不支持相对布局(正在开发中)" - } - - // specify `.maxWidth` - UILabel().stack.maxWidth(UIScreen.main.bounds.width - 48).then { - $0.textColor = .systemPink - $0.font = .systemFont(ofSize: 14, weight: .medium) - $0.numberOfLines = 0 - $0.text = "The subview may exceed the width/height of the parent view, and you need to specify the maximum width/height manually, add .maxWidth/.maxHeight after `.stack`\n子视图可能超过父视图的宽高,此时需要你手动设定最大宽度或最大高度" - } + UILabel().stack.then { + $0.textColor = .systemPink + $0.font = .systemFont(ofSize: 14, weight: .medium) + $0.numberOfLines = 0 + $0.text = "Do not support relative layout for the time being\n暂不支持相对布局(正在开发中)" + } + + // specify `.maxWidth` + UILabel().stack.maxWidth(UIScreen.main.bounds.width - 48).then { + $0.textColor = .systemPink + $0.font = .systemFont(ofSize: 14, weight: .medium) + $0.numberOfLines = 0 + $0.text = "The subview may exceed the width/height of the parent view, and you need to specify the maximum width/height manually, add .maxWidth/.maxHeight after `.stack`\n子视图可能超过父视图的宽高,此时需要你手动设定最大宽度或最大高度" } } Spacer(length: 12) diff --git a/README.md b/README.md index a692dce..4ed3913 100644 --- a/README.md +++ b/README.md @@ -36,19 +36,24 @@ VStackView(padding: UIEdgeInsets) ### Subview size is fixed ```swift -logoView.stack.size(CGSize) +logoView.stack.size(CGSize?) ``` ### Min or Max width/height ```swift -briefLabel.stack.minWidth(CGFloat).maxWidth(CGFloat) +briefLabel.stack.minWidth(CGFloat?) + .maxWidth(CGFloat?) + .minHeight(CGFloat?) + .maxHeight(CGFloat?) ``` ### Offset ```swift -briefLabel.stack.offset(CGPoint) +briefLabel.stack.offset(CGPoint?) + .offset(x: CGFloat?) + .offset(y: CGFloat?) ``` ### SizeToFit @@ -85,10 +90,10 @@ VStackView { ```swift // update text -briefLabel.text = "Bump version to 1.2.1" +briefLabel.text = "Bump version to 1.2.2" // stackContainer means any instance of HStackView or VStackView -stackContainer.setNeedsLayout() +stackContainer.setNeedsLayout() // or .sizeToFit() ``` # 🤔 diff --git a/Sources/StackKit/HStackView.swift b/Sources/StackKit/HStackView.swift index 8c91cfa..387de92 100644 --- a/Sources/StackKit/HStackView.swift +++ b/Sources/StackKit/HStackView.swift @@ -62,13 +62,11 @@ open class HStackView: UIView, StackView { } public var contentSize: CGSize { - let h = effectiveSubviews.map({ $0.frame }).reduce(CGRect.zero) { result, rect in - result.union(rect) - }.width - let w = effectiveSubviews.map({ $0.bounds }).reduce(CGRect.zero) { result, rect in - result.union(rect) - }.height - return CGSize(width: h + paddingRight, height: w + paddingVertically) + let width = effectiveSubviews.map({ $0.frame }).reduce(CGRect.zero, { $0.union($1) }).width + let height = effectiveSubviews.map({ $0.bounds.height }).max() ?? 0 + + let offsetXLength = effectiveSubviews.map({ $0.frame.minX }).filter({ $0 < 0 }).min() ?? 0 + return CGSize(width: width + paddingRight + offsetXLength, height: height + paddingVertically) } open func hideIfNoEffectiveViews() { @@ -165,11 +163,12 @@ extension HStackView { } } - guard let offset = subview._stackKit_offset else { - continue + if let offsetX = subview._stackKit_offsetX { + subview.frame.origin.x += offsetX + } + if let offsetY = subview._stackKit_offsetY { + subview.frame.origin.y += offsetY } - subview.frame.origin.x += offset.x - subview.frame.origin.y += offset.y } } diff --git a/Sources/StackKit/UIView+StackKit/UIView+FitSize.swift b/Sources/StackKit/UIView+StackKit/UIView+FitSize.swift index da01337..23b2872 100644 --- a/Sources/StackKit/UIView+StackKit/UIView+FitSize.swift +++ b/Sources/StackKit/UIView+StackKit/UIView+FitSize.swift @@ -149,7 +149,7 @@ extension UIView { var height: CGFloat? var cgSize: CGSize { - CGSize(width: width ?? 0, height: height ?? 0) + CGSize(width: ceil(width ?? 0), height: ceil(height ?? 0)) } } diff --git a/Sources/StackKit/UIView+StackKit/UIView+StackKitCompatibleProvider.swift b/Sources/StackKit/UIView+StackKit/UIView+StackKitCompatibleProvider.swift index ea140cd..2df2ec5 100644 --- a/Sources/StackKit/UIView+StackKit/UIView+StackKitCompatibleProvider.swift +++ b/Sources/StackKit/UIView+StackKit/UIView+StackKitCompatibleProvider.swift @@ -15,7 +15,20 @@ extension StackKitCompatible where Base: UIView { @discardableResult public func offset(_ value: CGPoint?) -> Self { - view._stackKit_offset = value + view._stackKit_offsetX = value?.x + view._stackKit_offsetY = value?.y + return self + } + + @discardableResult + public func offset(x value: CGFloat?) -> Self { + view._stackKit_offsetX = value + return self + } + + @discardableResult + public func offset(y value: CGFloat?) -> Self { + view._stackKit_offsetY = value return self } diff --git a/Sources/StackKit/UIView+StackKit/_UIView_StackKitProvider.swift b/Sources/StackKit/UIView+StackKit/_UIView_StackKitProvider.swift index 204d9cd..a6b7065 100644 --- a/Sources/StackKit/UIView+StackKit/_UIView_StackKitProvider.swift +++ b/Sources/StackKit/UIView+StackKit/_UIView_StackKitProvider.swift @@ -1,7 +1,8 @@ import UIKit struct _UIView_StackKitKeys { - static var offsetKey = "StackKit_offsetKey" + static var offsetXKey = "StackKit_offsetXKey" + static var offsetYKey = "StackKit_offsetYKey" static var widthKey = "StackKit_widthKey" static var heightKey = "StackKit_heightKey" @@ -14,7 +15,8 @@ struct _UIView_StackKitKeys { } protocol _UIView_StackKitProvider { - var _stackKit_offset: CGPoint? { get set } + var _stackKit_offsetX: CGFloat? { get set } + var _stackKit_offsetY: CGFloat? { get set } var _stackKit_width: CGFloat? { get set } var _stackKit_height: CGFloat? { get set } @@ -28,71 +30,41 @@ protocol _UIView_StackKitProvider { extension UIView: _UIView_StackKitProvider { - var _stackKit_offset: CGPoint? { - get { - guard let value = Runtime.getProperty(self, key: &_UIView_StackKitKeys.offsetKey) as? NSValue else { - return nil - } - return value.cgPointValue - } - set { - guard let newValue else { - Runtime.setProperty(self, key: &_UIView_StackKitKeys.offsetKey, value: nil, policy: .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - return - } - Runtime.setProperty(self, key: &_UIView_StackKitKeys.offsetKey, value: NSValue(cgPoint: newValue), policy: .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - } + var _stackKit_offsetX: CGFloat? { + get { Runtime.getCGFloatProperty(self, key: &_UIView_StackKitKeys.offsetXKey) } + set { Runtime.setCGFloatProperty(self, key: &_UIView_StackKitKeys.offsetXKey, newValue) } + } + + var _stackKit_offsetY: CGFloat? { + get { Runtime.getCGFloatProperty(self, key: &_UIView_StackKitKeys.offsetYKey) } + set { Runtime.setCGFloatProperty(self, key: &_UIView_StackKitKeys.offsetYKey, newValue) } } var _stackKit_width: CGFloat? { - get { - Runtime.getCGFloatProperty(self, key: &_UIView_StackKitKeys.widthKey) - } - set { - Runtime.setCGFloatProperty(self, key: &_UIView_StackKitKeys.widthKey, newValue) - } + get { Runtime.getCGFloatProperty(self, key: &_UIView_StackKitKeys.widthKey) } + set { Runtime.setCGFloatProperty(self, key: &_UIView_StackKitKeys.widthKey, newValue) } } var _stackKit_height: CGFloat? { - get { - Runtime.getCGFloatProperty(self, key: &_UIView_StackKitKeys.heightKey) - } - set { - Runtime.setCGFloatProperty(self, key: &_UIView_StackKitKeys.heightKey, newValue) - } + get { Runtime.getCGFloatProperty(self, key: &_UIView_StackKitKeys.heightKey) } + set { Runtime.setCGFloatProperty(self, key: &_UIView_StackKitKeys.heightKey, newValue) } } var _stackKit_minWidth: CGFloat? { - get { - Runtime.getCGFloatProperty(self, key: &_UIView_StackKitKeys.minWidthKey) - } - set { - Runtime.setCGFloatProperty(self, key: &_UIView_StackKitKeys.minWidthKey, newValue) - } + get { Runtime.getCGFloatProperty(self, key: &_UIView_StackKitKeys.minWidthKey) } + set { Runtime.setCGFloatProperty(self, key: &_UIView_StackKitKeys.minWidthKey, newValue) } } var _stackKit_maxWidth: CGFloat? { - get { - Runtime.getCGFloatProperty(self, key: &_UIView_StackKitKeys.maxWidthKey) - } - set { - Runtime.setCGFloatProperty(self, key: &_UIView_StackKitKeys.maxWidthKey, newValue) - } + get { Runtime.getCGFloatProperty(self, key: &_UIView_StackKitKeys.maxWidthKey) } + set { Runtime.setCGFloatProperty(self, key: &_UIView_StackKitKeys.maxWidthKey, newValue) } } var _stackKit_minHeight: CGFloat? { - get { - Runtime.getCGFloatProperty(self, key: &_UIView_StackKitKeys.minHeightKey) - } - set { - Runtime.setCGFloatProperty(self, key: &_UIView_StackKitKeys.minHeightKey, newValue) - } + get { Runtime.getCGFloatProperty(self, key: &_UIView_StackKitKeys.minHeightKey) } + set { Runtime.setCGFloatProperty(self, key: &_UIView_StackKitKeys.minHeightKey, newValue) } } var _stackKit_maxHeight: CGFloat? { - get { - Runtime.getCGFloatProperty(self, key: &_UIView_StackKitKeys.maxHeightKey) - } - set { - Runtime.setCGFloatProperty(self, key: &_UIView_StackKitKeys.maxHeightKey, newValue) - } + get { Runtime.getCGFloatProperty(self, key: &_UIView_StackKitKeys.maxHeightKey) } + set { Runtime.setCGFloatProperty(self, key: &_UIView_StackKitKeys.maxHeightKey, newValue) } } } diff --git a/Sources/StackKit/VStackView.swift b/Sources/StackKit/VStackView.swift index 063c206..334dd6a 100644 --- a/Sources/StackKit/VStackView.swift +++ b/Sources/StackKit/VStackView.swift @@ -62,13 +62,11 @@ open class VStackView: UIView, StackView { } public var contentSize: CGSize { - let h = effectiveSubviews.map({ $0.frame }).reduce(CGRect.zero) { result, rect in - result.union(rect) - }.height - let w = effectiveSubviews.map({ $0.bounds }).reduce(CGRect.zero) { result, rect in - result.union(rect) - }.width - return CGSize(width: w + paddingHorizontally, height: h + paddingBottom) + let width = effectiveSubviews.map({ $0.bounds.width }).max() ?? 0 + let height = effectiveSubviews.map({ $0.frame }).reduce(CGRect.zero, { $0.union($1) }).height + + let offsetYLength: CGFloat = effectiveSubviews.map({ $0.frame.minY }).filter({ $0 < 0 }).min() ?? 0 + return CGSize(width: width + paddingHorizontally, height: height + paddingBottom + offsetYLength) } open func hideIfNoEffectiveViews() { @@ -165,11 +163,12 @@ extension VStackView { } } - guard let offset = subview._stackKit_offset else { - continue + if let offsetX = subview._stackKit_offsetX { + subview.frame.origin.x += offsetX + } + if let offsetY = subview._stackKit_offsetY { + subview.frame.origin.y += offsetY } - subview.frame.origin.x += offset.x - subview.frame.origin.y += offset.y } }