diff --git a/Demo/Demo/HVStackDemoViewController.swift b/Demo/Demo/HVStackDemoViewController.swift index 64e1cdc..0aec251 100644 --- a/Demo/Demo/HVStackDemoViewController.swift +++ b/Demo/Demo/HVStackDemoViewController.swift @@ -42,7 +42,7 @@ class HVStackDemoViewController: UIViewController { Spacer() - VStackView { + VStackView(distribution: .fillWidth(spacing: 10)) { // view.stack.then (Inspired by Then [ https://github.com/devxoul/Then ]) UILabel().stack.then { label in @@ -51,11 +51,11 @@ class HVStackDemoViewController: UIViewController { label.textColor = .red } - Spacer(length: 6) +// Spacer(length: 6) // Support Divider (Inspired by SwiftUI) Divider(color: UIColor.orange) - Spacer(length: 6) +// Spacer(length: 6) UILabel().stack.then { label in label.text = "Version: 1" diff --git a/Demo/Demo/HVStackWrapperViewDemoViewController.swift b/Demo/Demo/HVStackWrapperViewDemoViewController.swift index 9d69b13..5f18f06 100644 --- a/Demo/Demo/HVStackWrapperViewDemoViewController.swift +++ b/Demo/Demo/HVStackWrapperViewDemoViewController.swift @@ -12,9 +12,9 @@ let contentWidth: CGFloat = UIScreen.main.bounds.width - 32 class HVStackWrapperViewDemoViewController: UIViewController { - let content = VStackLayerWrapperView { - HStackLayerWrapperView() { - VStackLayerWrapperView(alignment: .left) { + let content = VStackView { + HStackView() { + VStackView(alignment: .left) { UILabel().stack.then { $0.text = "Good Morning." $0.font = .systemFont(ofSize: 16, weight: .medium) @@ -31,7 +31,7 @@ class HVStackWrapperViewDemoViewController: UIViewController { Spacer() // ICON - HStackLayerWrapperView(distribution: .spacing(10)) { + HStackView(distribution: .spacing(10)) { UIView().stack.size(34).then { $0.backgroundColor = .systemPink $0.layer.cornerRadius = 8 @@ -48,13 +48,13 @@ class HVStackWrapperViewDemoViewController: UIViewController { Spacer(length: 20) - HStackLayerWrapperView { - HStackLayerWrapperView(distribution: .spacing(6)) { + HStackView { + HStackView(distribution: .spacing(6)) { UIView().stack.size(60).then { $0.backgroundColor = .systemPink $0.layer.cornerRadius = 8 } - VStackLayerWrapperView(alignment: .left) { + VStackView(alignment: .left) { UILabel().stack.then { $0.text = "Spending" $0.font = .systemFont(ofSize: 18) @@ -68,12 +68,12 @@ class HVStackWrapperViewDemoViewController: UIViewController { } } Spacer() - HStackLayerWrapperView(distribution: .spacing(6)) { + HStackView(distribution: .spacing(6)) { UIView().stack.size(60).then { $0.backgroundColor = .systemPink $0.layer.cornerRadius = 8 } - VStackLayerWrapperView(alignment: .left) { + VStackView(alignment: .left) { UILabel().stack.then { $0.text = "Income" $0.font = .systemFont(ofSize: 18) @@ -89,7 +89,7 @@ class HVStackWrapperViewDemoViewController: UIViewController { }.stack.width(contentWidth).sizeToFit(.width) Spacer(length: 30) - HStackLayerWrapperView(alignment: .bottom) { + HStackView(alignment: .bottom) { UILabel().stack.then { $0.text = "Transactions" $0.font = .systemFont(ofSize: 24, weight: .bold) @@ -104,15 +104,15 @@ class HVStackWrapperViewDemoViewController: UIViewController { }.stack.width(contentWidth).sizeToFit(.width) Spacer(length: 20) - VStackLayerWrapperView(alignment: .left) { + VStackView(alignment: .left) { - HStackLayerWrapperView { + HStackView { UIView().stack.size(40).then { $0.backgroundColor = .systemPink $0.layer.cornerRadius = 12 } Spacer(length: 10) - VStackLayerWrapperView(alignment: .left) { + VStackView(alignment: .left) { UILabel().stack.then { $0.text = "Freelance Work" $0.textColor = .black @@ -139,6 +139,20 @@ class HVStackWrapperViewDemoViewController: UIViewController { // Do any additional setup after loading the view. + + let textLayer = CATextLayer() + textLayer.string = "This is CATextLayer in VStackLayerWrapperView2" + textLayer.fontSize = 12 + textLayer.foregroundColor = UIColor.black.cgColor + textLayer.contentsScale = UIScreen.main.scale + + content.addContent { + VStackLayerContainerView { + Spacer(length: 20) + textLayer + } + } + self.view.addSubview(content) } diff --git a/Demo/Demo/WrapStackDemoViewController.swift b/Demo/Demo/WrapStackDemoViewController.swift index 290e986..8c1d73d 100644 --- a/Demo/Demo/WrapStackDemoViewController.swift +++ b/Demo/Demo/WrapStackDemoViewController.swift @@ -6,6 +6,7 @@ // import UIKit +import StackKit class WrapStackDemoViewController: UIViewController { @@ -13,8 +14,25 @@ class WrapStackDemoViewController: UIViewController { super.viewDidLoad() // Do any additional setup after loading the view. + + let vStack = VStackView(distribution: .fillWidth(spacing: 10)) { + UILabel().stack.then { + $0.text = "StackKit" + $0.font = .systemFont(ofSize: 18, weight: .semibold) + $0.textColor = .black + } + Divider() + UILabel().stack.then { + $0.text = "Version 1.0.0" + $0.font = .systemFont(ofSize: 14, weight: .regular) + $0.textColor = .gray + } + } + vStack.sizeToFit() + vStack.frame.origin = CGPoint(x: 20, y: 120) + + view.addSubview(vStack) } - /* // MARK: - Navigation diff --git a/Sources/StackKit/Enums.swift b/Sources/StackKit/Enums.swift index e1fea39..843a0fc 100644 --- a/Sources/StackKit/Enums.swift +++ b/Sources/StackKit/Enums.swift @@ -16,7 +16,8 @@ public enum HStackDistribution { case autoSpacing /// The height of subviews are equal. `The height of the VStack needs to be given.` - case fillHeight + /// set `nil` means autoSpacing + case fillHeight(spacing: CGFloat? = nil) /// The width and height of subviews are equal. `The size of the HStack needs to be given.` case fill @@ -39,7 +40,8 @@ public enum VStackDistribution { case autoSpacing /// The widths are all equal. `The width of the VStack needs to be given.` - case fillWidth + /// set `nil` means autoSpacing + case fillWidth(spacing: CGFloat? = nil) /// The widths and heights are all equal. `The size of the V Stack needs to be given.` case fill diff --git a/Sources/StackKit/HStackView.swift b/Sources/StackKit/HStackView.swift index 6172bbc..01107e3 100644 --- a/Sources/StackKit/HStackView.swift +++ b/Sources/StackKit/HStackView.swift @@ -7,7 +7,7 @@ open class HStackView: UIView { public required init( alignment: HStackAlignment = .center, - distribution: HStackDistribution = .autoSpacing, + distribution: HStackDistribution = .spacing(2), @_StackKitHStackContentResultBuilder content: () -> [UIView] = { [] } ) { super.init(frame: .zero) @@ -69,9 +69,13 @@ open class HStackView: UIView { } public var contentSize: CGSize { - effectiveSubviews.map({ $0.frame }).reduce(CGRect.zero) { result, rect in + let h = effectiveSubviews.map({ $0.frame }).reduce(CGRect.zero) { result, rect in result.union(rect) - }.size + }.width + let w = effectiveSubviews.map({ $0.bounds }).reduce(CGRect.zero) { result, rect in + result.union(rect) + }.height + return CGSize(width: h, height: w) } open func hideIfNoEffectiveViews() { @@ -114,12 +118,12 @@ open class HStackView: UIView { let spacing = autoSpacing() makeSpacing(spacing) - case .fillHeight: + case .fillHeight(let spacing): fillDivider() fillSpecifySpacer() fillSpacer() - let spacing = autoSpacing() + let spacing = spacing ?? autoSpacing() makeSpacing(spacing) fillHeight() @@ -206,8 +210,18 @@ extension HStackView { } private func fillHeight() { - effectiveSubviews.forEach { - $0.frame.size.height = frame.height + if frame.height == 0 { + frame.size.height = contentSize.height + } + for subview in effectiveSubviews { + let oldHeight = subview.frame.height + subview.frame.size.height = frame.height + + // fix #https://github.com/iWECon/StackKit/issues/21 + guard alignment == .center else { + continue + } + subview.frame.origin.y -= (frame.height - oldHeight) / 2 } } @@ -299,9 +313,13 @@ extension HStackView { } else { switch distribution { case .spacing(let spacing): - unspacerViewsSpacing = spacing * CGFloat(unspacerViews.count - betweenInViewsCount - 1) // 正常 spacing 数量: (views.count - 1), spacer 左右的视图没有间距,所以需要再排除在 view 之间的 spacer 数量 + // 正常 spacing 数量: (views.count - 1), spacer 左右的视图没有间距,所以需要再排除在 view 之间的 spacer 数量 + unspacerViewsSpacing = spacing * CGFloat(unspacerViews.count - betweenInViewsCount - 1) + + case .fillHeight(let spacing): + unspacerViewsSpacing = (spacing ?? autoSpacing()) * CGFloat(unspacerViews.count - betweenInViewsCount - 1) - case .autoSpacing, .fillHeight: + case .autoSpacing: unspacerViewsSpacing = autoSpacing() * CGFloat(unspacerViews.count - betweenInViewsCount - 1) case .fill: diff --git a/Sources/StackKit/Layer+LayerWraperView/HStackLayerContainerView.swift b/Sources/StackKit/Layer+LayerWraperView/HStackLayerContainerView.swift new file mode 100644 index 0000000..5d31dac --- /dev/null +++ b/Sources/StackKit/Layer+LayerWraperView/HStackLayerContainerView.swift @@ -0,0 +1,74 @@ +// +// HStackLayerContainerView.swift +// +// +// Created by i on 2022/10/9. +// + +import UIKit + +open class HStackLayerContainerView: UIView { + + public var hStackLayer: HStackLayer { + self.layer as! HStackLayer + } + + open var alignment: HStackAlignment { + get { + hStackLayer.alignment + } + set { + hStackLayer.alignment = newValue + } + } + + open var distribution: HStackDistribution { + get { + hStackLayer.distribution + } + set { + hStackLayer.distribution = newValue + } + } + + public required init( + alignment: HStackAlignment = .center, + distribution: HStackDistribution = .autoSpacing, + @_StackKitHStackLayerContentResultBuilder content: () -> [CALayer] = { [] } + ) { + super.init(frame: .zero) + hStackLayer.alignment = alignment + hStackLayer.distribution = distribution + + for v in content() { + hStackLayer.addSublayer(v) + } + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + } + + open override class var layerClass: AnyClass { + HStackLayer.self + } + + open override func sizeThatFits(_ size: CGSize) -> CGSize { + hStackLayer.sizeThatFits(size) + } + + open func addContent(@_StackKitHStackLayerContentResultBuilder _ content: () -> [CALayer]) { + hStackLayer.addContent(content) + } + + open func resetContent(@_StackKitHStackLayerContentResultBuilder _ content: () -> [CALayer]) { + hStackLayer.resetContent(content) + } + + @available(*, deprecated, message: "use `addContent(_:)` or `resetContent(_:)` instead") + open override func addSubview(_ view: UIView) { + // deprecated + } + +} + diff --git a/Sources/StackKit/Layer+LayerWraperView/HStackLayerWrapperView.swift b/Sources/StackKit/Layer+LayerWraperView/HStackLayerWrapperView.swift index c8a0967..15519fd 100644 --- a/Sources/StackKit/Layer+LayerWraperView/HStackLayerWrapperView.swift +++ b/Sources/StackKit/Layer+LayerWraperView/HStackLayerWrapperView.swift @@ -5,6 +5,9 @@ import UIKit /// /// 该类仅作展示使用,所有的 UIView 均会被转换为 CALayer 作为显示 /// +/// There may be performance problems in heavy use. It is recommended to use `VStackView` / `HStackView` when there are many views. +/// 大量使用可能会有性能问题, 视图较多时建议使用 `VStackView` / `HStackView` +/// open class HStackLayerWrapperView: _StackLayerWrapperView { public var hStackLayer: HStackLayer { diff --git a/Sources/StackKit/Layer+LayerWraperView/VStackLayerContainerView.swift b/Sources/StackKit/Layer+LayerWraperView/VStackLayerContainerView.swift new file mode 100644 index 0000000..3e9b466 --- /dev/null +++ b/Sources/StackKit/Layer+LayerWraperView/VStackLayerContainerView.swift @@ -0,0 +1,73 @@ +// +// VStackLayerContainerView.swift +// +// +// Created by i on 2022/10/9. +// + +import UIKit + +open class VStackLayerContainerView: UIView { + + public var vStackLayer: VStackLayer { + self.layer as! VStackLayer + } + + open var alignment: VStackAlignment { + get { + vStackLayer.alignment + } + set { + vStackLayer.alignment = newValue + } + } + + open var distribution: VStackDistribution { + get { + vStackLayer.distribution + } + set { + vStackLayer.distribution = newValue + } + } + + public required init( + alignment: VStackAlignment = .center, + distribution: VStackDistribution = .autoSpacing, + @_StackKitVStackLayerContentResultBuilder content: () -> [CALayer] = { [] } + ) { + super.init(frame: .zero) + vStackLayer.alignment = alignment + vStackLayer.distribution = distribution + + for v in content() { + vStackLayer.addSublayer(v) + } + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + } + + open override class var layerClass: AnyClass { + VStackLayer.self + } + + open override func sizeThatFits(_ size: CGSize) -> CGSize { + vStackLayer.sizeThatFits(size) + } + + open func addContent(@_StackKitVStackLayerContentResultBuilder _ content: () -> [CALayer]) { + vStackLayer.addContent(content) + } + + open func resetContent(@_StackKitVStackLayerContentResultBuilder _ content: () -> [CALayer]) { + vStackLayer.resetContent(content) + } + + @available(*, deprecated, message: "use `addContent(_:)` or `resetContent(_:)` instead") + open override func addSubview(_ view: UIView) { + // deprecated + } + +} diff --git a/Sources/StackKit/Layer+LayerWraperView/VStackLayerWrapperView.swift b/Sources/StackKit/Layer+LayerWraperView/VStackLayerWrapperView.swift index 7a691d2..a1ad927 100644 --- a/Sources/StackKit/Layer+LayerWraperView/VStackLayerWrapperView.swift +++ b/Sources/StackKit/Layer+LayerWraperView/VStackLayerWrapperView.swift @@ -5,6 +5,9 @@ import UIKit /// /// 该类仅作展示使用,所有的 UIView 均会被转换为 CALayer 作为显示 /// +/// There may be performance problems in heavy use. It is recommended to use `VStackView` / `HStackView` when there are many views. +/// 大量使用可能会有性能问题, 视图较多时建议使用 `VStackView` / `HStackView` +/// open class VStackLayerWrapperView: _StackLayerWrapperView { public var vStackLayer: VStackLayer { diff --git a/Sources/StackKit/UIView+StackKit/UIView+.swift b/Sources/StackKit/UIView+StackKit/UIView+.swift deleted file mode 100644 index 6dd8d7a..0000000 --- a/Sources/StackKit/UIView+StackKit/UIView+.swift +++ /dev/null @@ -1,9 +0,0 @@ -import UIKit - -extension UIView { - - public func addSubview(_ compatibleWrapper: StackKitCompatible) where T: UIView { - self.addSubview(compatibleWrapper.view) - } - -} diff --git a/Sources/StackKit/VStackView.swift b/Sources/StackKit/VStackView.swift index 44d0677..f63bcf3 100644 --- a/Sources/StackKit/VStackView.swift +++ b/Sources/StackKit/VStackView.swift @@ -7,7 +7,7 @@ open class VStackView: UIView { public required init( alignment: VStackAlignment = .center, - distribution: VStackDistribution = .autoSpacing, + distribution: VStackDistribution = .spacing(2), @_StackKitVStackContentResultBuilder content: () -> [UIView] = { [] } ) { super.init(frame: .zero) @@ -69,9 +69,13 @@ open class VStackView: UIView { } public var contentSize: CGSize { - effectiveSubviews.map({ $0.frame }).reduce(CGRect.zero) { result, rect in + let h = effectiveSubviews.map({ $0.frame }).reduce(CGRect.zero) { result, rect in result.union(rect) - }.size + }.height + let w = effectiveSubviews.map({ $0.bounds }).reduce(CGRect.zero) { result, rect in + result.union(rect) + }.width + return CGSize(width: w, height: h) } open func hideIfNoEffectiveViews() { @@ -84,11 +88,7 @@ open class VStackView: UIView { } } - open override func layoutSubviews() { - super.layoutSubviews() - - tryResizeStackView() - + private func makeSubviewsAlignment() { switch alignment { case .left: effectiveSubviews.forEach { $0.frame.origin.x = 0 } @@ -97,6 +97,14 @@ open class VStackView: UIView { case .right: effectiveSubviews.forEach { $0.frame.origin.x = frame.width - $0.frame.width } } + } + + open override func layoutSubviews() { + super.layoutSubviews() + + tryResizeStackView() + + makeSubviewsAlignment() switch distribution { case .spacing(let spacing): @@ -114,12 +122,12 @@ open class VStackView: UIView { let spacing = autoSpacing() makeSpacing(spacing) - case .fillWidth: + case .fillWidth(let spacing): fillDivider() fillSpecifySpacer() fillSpacer() - let spacing = autoSpacing() + let spacing = spacing ?? autoSpacing() makeSpacing(spacing) fillWidth() @@ -206,8 +214,18 @@ extension VStackView { } private func fillWidth() { - effectiveSubviews.forEach { - $0.frame.size.width = frame.width + if frame.width == 0 { + frame.size.width = contentSize.width // found subviews.map { $0.frame.width }.max() + } + for subview in effectiveSubviews { + let oldWidth = subview.frame.width + subview.frame.size.width = frame.width + + // fix #https://github.com/iWECon/StackKit/issues/21 + guard alignment == .center else { + continue + } + subview.frame.origin.x -= (frame.width - oldWidth) / 2 } } @@ -298,9 +316,13 @@ extension VStackView { } else { switch distribution { case .spacing(let spacing): - unspacerViewsSpacing = spacing * CGFloat(unspacerViews.count - betweenInViewsCount - 1) // 正常 spacing 数量: (views.count - 1), spacer 左右的视图没有间距,所以需要再排除在 view 之间的 spacer 数量 + // 正常 spacing 数量: (views.count - 1), spacer 左右的视图没有间距,所以需要再排除在 view 之间的 spacer 数量 + unspacerViewsSpacing = spacing * CGFloat(unspacerViews.count - betweenInViewsCount - 1) + + case .fillWidth(let spacing): + unspacerViewsSpacing = (spacing ?? autoSpacing()) * CGFloat(unspacerViews.count - betweenInViewsCount - 1) - case .autoSpacing, .fillWidth: + case .autoSpacing: unspacerViewsSpacing = autoSpacing() * CGFloat(unspacerViews.count - betweenInViewsCount - 1) case .fill: