From 9e05d37ec6306de9e42f9e0a0fb0e2b79ac06216 Mon Sep 17 00:00:00 2001 From: iWw Date: Tue, 9 Aug 2022 15:08:43 +0800 Subject: [PATCH] update --- Sources/StackKit/Divider.swift | 43 ++-- Sources/StackKit/DividerResultBuilder.swift | 57 ++++++ Sources/StackKit/HStackLayer.swift | 185 ++++++++++++++++-- Sources/StackKit/HStackLayerWrapperView.swift | 21 +- Sources/StackKit/HStackView.swift | 8 +- Sources/StackKit/Spacer.swift | 141 ++++++------- Sources/StackKit/SpacerResultBuilder.swift | 29 +++ Sources/StackKit/VStackLayer.swift | 181 +++++++++++++++-- Sources/StackKit/VStackLayerWrapperView.swift | 21 +- Sources/StackKit/VStackView.swift | 2 +- 10 files changed, 552 insertions(+), 136 deletions(-) create mode 100644 Sources/StackKit/DividerResultBuilder.swift create mode 100644 Sources/StackKit/SpacerResultBuilder.swift diff --git a/Sources/StackKit/Divider.swift b/Sources/StackKit/Divider.swift index 45d7562..b1489a7 100644 --- a/Sources/StackKit/Divider.swift +++ b/Sources/StackKit/Divider.swift @@ -3,12 +3,12 @@ import UIKit public protocol _Divider { var thickness: CGFloat { get } var maxLength: CGFloat { get set } - var backgroundColor: UIColor { get } + var color: UIColor { get } var cornerRadius: CGFloat { get } } extension _Divider { public var thickness: CGFloat { 1 } - public var backgroundColor: UIColor { .gray } + public var color: UIColor { .gray } public var cornerRadius: CGFloat { 0 } } @@ -22,7 +22,7 @@ extension _Divider { public struct Divider: _Divider { public var thickness: CGFloat = 1 public var maxLength: CGFloat = CGFloat.greatestFiniteMagnitude - public var backgroundColor: UIColor = .gray + public var color: UIColor = .gray public var cornerRadius: CGFloat = 0 public init( @@ -33,12 +33,13 @@ public struct Divider: _Divider { ) { self.thickness = thickness self.maxLength = maxLength - self.backgroundColor = backgroundColor + self.color = backgroundColor self.cornerRadius = cornerRadius } } class DividerView: UIView, _Divider { + var maxLength: CGFloat = .greatestFiniteMagnitude override init(frame: CGRect) { @@ -52,30 +53,16 @@ class DividerView: UIView, _Divider { } } -public protocol _StackKitViewDividerResultBuilder { - static func buildExpression(_ expression: Divider) -> [UIView] -} - -// MARK: For HStack -extension _StackKitHStackContentResultBuilder: _StackKitViewDividerResultBuilder { - public static func buildExpression(_ expression: Divider) -> [UIView] { - let view = DividerView() - view.maxLength = expression.maxLength - view.frame.size.width = expression.thickness - view.backgroundColor = expression.backgroundColor - view.layer.cornerRadius = expression.cornerRadius - return [view] +class DividerLayer: CALayer, _Divider { + var maxLength: CGFloat = .greatestFiniteMagnitude + + override init() { + super.init() } -} - -// MARK: For VStack -extension _StackKitVStackContentResultBuilder: _StackKitViewDividerResultBuilder { - public static func buildExpression(_ expression: Divider) -> [UIView] { - let view = DividerView() - view.maxLength = expression.maxLength - view.frame.size.height = expression.thickness - view.backgroundColor = expression.backgroundColor - view.layer.cornerRadius = expression.cornerRadius - return [view] + override init(layer: Any) { + super.init(layer: layer) + } + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") } } diff --git a/Sources/StackKit/DividerResultBuilder.swift b/Sources/StackKit/DividerResultBuilder.swift new file mode 100644 index 0000000..0a4b99e --- /dev/null +++ b/Sources/StackKit/DividerResultBuilder.swift @@ -0,0 +1,57 @@ +import UIKit + +// MARK: - For *StackView +public protocol _StackKitViewDividerResultBuilder { + static func buildExpression(_ expression: Divider) -> [UIView] +} + +// MARK: For HStack +extension _StackKitHStackContentResultBuilder: _StackKitViewDividerResultBuilder { + public static func buildExpression(_ expression: Divider) -> [UIView] { + let view = DividerView() + view.maxLength = expression.maxLength + view.frame.size.width = expression.thickness + view.backgroundColor = expression.color + view.layer.cornerRadius = expression.cornerRadius + return [view] + } +} + +// MARK: For VStack +extension _StackKitVStackContentResultBuilder: _StackKitViewDividerResultBuilder { + public static func buildExpression(_ expression: Divider) -> [UIView] { + let view = DividerView() + view.maxLength = expression.maxLength + view.frame.size.height = expression.thickness + view.backgroundColor = expression.color + view.layer.cornerRadius = expression.cornerRadius + return [view] + } +} + + +// MARK: - For *StackLayerWrapperView +public protocol _StackKitLayerDividerResultBuilder { + static func buildExpression(_ expression: Divider) -> [CALayer] +} +extension _StackKitHStackLayerContentResultBuilder: _StackKitLayerDividerResultBuilder { + public static func buildExpression(_ expression: Divider) -> [CALayer] { + let layer = DividerLayer() + layer.maxLength = expression.maxLength + layer.frame.size.width = expression.thickness + layer.backgroundColor = expression.color.cgColor + layer.cornerRadius = expression.cornerRadius + return [layer] + } +} + +extension _StackKitVStackLayerContentResultBuilder: _StackKitLayerDividerResultBuilder { + public static func buildExpression(_ expression: Divider) -> [CALayer] { + let layer = DividerLayer() + layer.maxLength = expression.maxLength + layer.frame.size.height = expression.thickness + layer.backgroundColor = expression.color.cgColor + layer.cornerRadius = expression.cornerRadius + return [layer] + } +} diff --git a/Sources/StackKit/HStackLayer.swift b/Sources/StackKit/HStackLayer.swift index 0905341..7862e70 100644 --- a/Sources/StackKit/HStackLayer.swift +++ b/Sources/StackKit/HStackLayer.swift @@ -5,16 +5,6 @@ open class HStackLayer: CALayer { public var alignment: HStackAlignment = .center public var distribution: HStackDistribution = .autoSpacing - public var contentSize: CGSize { - effectiveSublayers.map({ $0.frame }).reduce(CGRect.zero) { result, rect in - result.union(rect) - }.size - } - - open override func preferredFrameSize() -> CGSize { - contentSize - } - public required override init() { super.init() } @@ -46,6 +36,16 @@ open class HStackLayer: CALayer { (sublayers ?? []).lazy.filter { $0._isEffectiveLayer } } + public var contentSize: CGSize { + effectiveSublayers.map({ $0.frame }).reduce(CGRect.zero) { result, rect in + result.union(rect) + }.size + } + + open override func preferredFrameSize() -> CGSize { + contentSize + } + open func refreshSublayers() { for v in sublayers ?? [] where v.frame.size == .zero { v.frame.size = v.preferredFrameSize() @@ -68,24 +68,33 @@ open class HStackLayer: CALayer { switch distribution { case .spacing(let spacing): + fillDivider() + fillSpacer() + makeSpacing(spacing) case .autoSpacing: + fillDivider() + fillSpacer() + let spacing = autoSpacing() makeSpacing(spacing) case .fillHeight: // autoSpacing and fill height + fillDivider() + fillSpacer() + let spacing = autoSpacing() makeSpacing(spacing) fillHeight() case .fill: + fillDivider() + fillSpecifySpacer() + fillSpacer() + fillWidth() + makeSpacing(0) fillHeight() - - let w = frame.width / CGFloat(effectiveSublayers.count) - effectiveSublayers.forEach { - $0.frame.size.width = w - } } } @@ -108,10 +117,33 @@ open class HStackLayer: CALayer { } } +extension HStackLayer { + + private func spacerLayers() -> [SpacerLayer] { + effectiveSublayers.compactMap({ $0 as? SpacerLayer }) + } + private func dividerLayers() -> [DividerLayer] { + effectiveSublayers.compactMap({ $0 as? DividerLayer }) + } + + private func viewsWithoutSpacer() -> [CALayer] { + effectiveSublayers.filter({ ($0 as? SpacerLayer) == nil }) + } + private func viewsWithoutSpacerAndDivider() -> [CALayer] { + effectiveSublayers.filter({ ($0 as? SpacerLayer) == nil && ($0 as? DividerLayer) == nil }) + } +} + extension HStackLayer { private func autoSpacing() -> CGFloat { - (frame.width - effectiveSublayers.map({ $0.frame.size.width }).reduce(0, { $0 + $1 })) / CGFloat(effectiveSublayers.count) + let unspacerViews = viewsWithoutSpacer() + let spacersCount = spacerLayers().map({ isSpacerBetweenViews($0) }).filter({ $0 }).count + return (frame.width - viewsWidth() - spacerSpecifyLength()) / CGFloat(unspacerViews.count - spacersCount - 1) + } + + private func viewsWidth() -> CGFloat { + viewsWithoutSpacer().map({ $0.frame.width }).reduce(0, +) } private func makeSpacing(_ spacing: CGFloat) { @@ -120,7 +152,12 @@ extension HStackLayer { sublayer.frame.origin.x = 0 } else { let previousLayer = effectiveSublayers[index - 1] - sublayer.frame.origin.x = previousLayer.frame.maxX + spacing + if (previousLayer as? SpacerLayer) != nil || (sublayer as? SpacerLayer) != nil { + // spacer and view no spacing + sublayer.frame.origin.x = previousLayer.frame.maxX + } else { + sublayer.frame.origin.x = previousLayer.frame.maxX + spacing + } } } } @@ -130,4 +167,118 @@ extension HStackLayer { $0.frame.size.height = frame.height } } + + private func fillWidth() { + let maxW = frame.width - spacerSpecifyLength() - dividerSpecifyLength() + var w = (maxW) / CGFloat(viewsWithoutSpacerAndDivider().count) + + let unspacersView = viewsWithoutSpacerAndDivider() + w = maxW / CGFloat(unspacersView.count) + for subview in unspacersView { + subview.frame.size.width = w + } + } +} + +extension HStackLayer { + + private func dividerSpecifyLength() -> CGFloat { + dividerLayers() + .map({ $0.thickness }) + .reduce(0, +) + } + + private func fillDivider() { + let maxWidth = effectiveSublayers.filter({ ($0 as? DividerLayer) == nil }).map({ $0.frame.size.height }).max() ?? frame.height + for divider in effectiveSublayers.compactMap({ $0 as? DividerLayer }) { + var maxLength = divider.maxLength + if maxLength == .greatestFiniteMagnitude { + // auto + maxLength = maxWidth + } else if maxWidth < maxLength { + maxLength = maxWidth + } + divider.frame.size.height = maxLength + } + } + +} + +// MARK: Spacer +extension HStackLayer { + + // 取出固定 length 的 spacer + private func spacerSpecifyLength() -> CGFloat { + spacerLayers() + .map({ $0.setLength }) + .reduce(0, +) + } + + private func isSpacerBetweenViews(_ spacer: SpacerLayer) -> Bool { + guard let index = effectiveSublayers.firstIndex(of: spacer) else { + return false + } + + var isPreviousView = false + var isNextView = false + + let previous = index - 1 + if previous > 0, previous < effectiveSublayers.count - 1 { + isPreviousView = true + } + + let next = index + 1 + if next < effectiveSublayers.count - 1 { + isNextView = true + } + return isPreviousView && isNextView + } + + private func fillSpecifySpacer() { + let spacers = effectiveSublayers.compactMap({ $0 as? SpacerLayer }) + for spacer in spacers { + spacer.fitSize(value: 0, for: .width) + } + } + + /// 填充 spacer + /// + /// A = viewsWithoutSpacer().widths + /// B = frame.width - A - viewsWithoutSpacer.spacings + private func fillSpacer() { + let unspacerViews = viewsWithoutSpacer() + guard unspacerViews.count != effectiveSublayers.count else { return } + + // 在 view 与 view 之间的 spacer view 数量: 两个 view 夹一个 spacer view + let betweenInViewsCount = spacerLayers().map({ isSpacerBetweenViews($0) }).filter({ $0 }).count + // 非 spacer view 的总宽度 + let unspacerViewsWidth = viewsWidth() + // 排除 spacer view 后的间距 + let unspacerViewsSpacing: CGFloat + + if unspacerViews.count == 1 { + unspacerViewsSpacing = 0 + } else { + switch distribution { + case .spacing(let spacing): + unspacerViewsSpacing = spacing * CGFloat(unspacerViews.count - betweenInViewsCount - 1) // 正常 spacing 数量: (views.count - 1), spacer 左右的视图没有间距,所以需要再排除在 view 之间的 spacer 数量 + + case .autoSpacing, .fillHeight: + unspacerViewsSpacing = autoSpacing() * CGFloat(unspacerViews.count - betweenInViewsCount - 1) + + case .fill: + unspacerViewsSpacing = 0 + } + } + + // 非 spacerView 的所有宽度 + let unspacerViewsMaxWidth = unspacerViewsWidth + unspacerViewsSpacing + let spacersWidth = (frame.width - unspacerViewsMaxWidth) + let spacerWidth = spacersWidth / CGFloat(self.spacerLayers().count) + + let spacerViews = self.spacerLayers() + for spacer in spacerViews { + spacer.fitSize(value: spacerWidth, for: .width) + } + } } diff --git a/Sources/StackKit/HStackLayerWrapperView.swift b/Sources/StackKit/HStackLayerWrapperView.swift index c79a959..d86a5a2 100644 --- a/Sources/StackKit/HStackLayerWrapperView.swift +++ b/Sources/StackKit/HStackLayerWrapperView.swift @@ -55,15 +55,33 @@ open class HStackLayerWrapperView: UIView { } subview._tryFixSize() - // use view.layer if view is UIImageView + // Copy UIImageView if subview is UIImageView { let imageLayer = CALayer() imageLayer.contents = subview.layer.contents imageLayer.bounds = subview.layer.bounds + imageLayer.backgroundColor = subview.layer.backgroundColor layer.addSublayer(imageLayer) return } + // Divider View to Dividerlayer + if let dividerView = subview as? DividerView { + let dividerLayer = DividerLayer() + dividerLayer.maxLength = dividerView.maxLength + dividerLayer.frame.size.width = dividerView.thickness + dividerLayer.backgroundColor = dividerView.backgroundColor?.cgColor + dividerLayer.cornerRadius = dividerView.cornerRadius + layer.addSublayer(dividerLayer) + return + } + + if let spacerView = subview as? SpacerView { + let spacerLayer = spacerView.spacerLayer + layer.addSublayer(spacerLayer) + return + } + // render view to image and create CALayer with it // 如果直接使用 view.layer 可能显示会有问题, 例如: UILabel -> _UILabelLayer 无法正常显示文字 let image = UIGraphicsImageRenderer(bounds: subview.bounds).image { context in @@ -72,6 +90,7 @@ open class HStackLayerWrapperView: UIView { let tempLayer = CALayer() tempLayer.contents = image.cgImage tempLayer.bounds = subview.bounds + tempLayer.backgroundColor = subview.backgroundColor?.cgColor layer.addSublayer(tempLayer) } diff --git a/Sources/StackKit/HStackView.swift b/Sources/StackKit/HStackView.swift index 86c0cfb..a96a953 100644 --- a/Sources/StackKit/HStackView.swift +++ b/Sources/StackKit/HStackView.swift @@ -161,7 +161,7 @@ extension HStackView { private func autoSpacing() -> CGFloat { let unspacerViews = viewsWithoutSpacer() let spacersCount = spacerViews().map({ isSpacerBetweenViews($0) }).filter({ $0 }).count - return viewsWidth() / CGFloat(unspacerViews.count - spacersCount - 1) + return (frame.width - viewsWidth() - spacerSpecifyLength()) / CGFloat(unspacerViews.count - spacersCount - 1) } private func viewsWidth() -> CGFloat { @@ -237,7 +237,7 @@ extension HStackView { } private func isSpacerBetweenViews(_ spacer: SpacerView) -> Bool { - guard let index = subviews.firstIndex(of: spacer) else { + guard let index = effectiveSubviews.firstIndex(of: spacer) else { return false } @@ -245,12 +245,12 @@ extension HStackView { var isNextView = false let previous = index - 1 - if previous > 0, previous < subviews.count - 1 { + if previous > 0, previous < effectiveSubviews.count - 1 { isPreviousView = true } let next = index + 1 - if next < subviews.count - 1 { + if next < effectiveSubviews.count - 1 { isNextView = true } return isPreviousView && isNextView diff --git a/Sources/StackKit/Spacer.swift b/Sources/StackKit/Spacer.swift index 6c735b2..7d80013 100644 --- a/Sources/StackKit/Spacer.swift +++ b/Sources/StackKit/Spacer.swift @@ -95,74 +95,81 @@ class SpacerView: UIView, _Spacer { } return 0 } -} - -public protocol _StackKitViewSpacerResultBuilder { - static func buildExpression(_ expression: Spacer) -> [UIView] -} -extension _StackKitViewSpacerResultBuilder { - public static func buildExpression(_ expression: Spacer) -> [UIView] { - let spacer = SpacerView(length: expression.length, min: expression.min, max: expression.max) - return [spacer] + + var spacerLayer: SpacerLayer { + SpacerLayer(length: length, min: min, max: max) } } -extension _StackKitHStackContentResultBuilder: _StackKitViewSpacerResultBuilder { } -extension _StackKitVStackContentResultBuilder: _StackKitViewSpacerResultBuilder { } - -/** - HStack(distribution: .spacing(8)) { - View1() - Spacer() - View2() - } - - | view1 | --spacer-- | view2 | // spacing 8 is ignore - - HStack(distribution: .spacing(8)) { - View1() - Spacer() - View2() - View3() - } - - | view1 | --spacer -- | view2 | view3 | // spacing 8 only effective between view2 and view3 - - HStack(distribution: .spacing(8)) { - Spacer() - View1() - Spacer() // When two or more Spacers are connected together, only the first one will take effect. - Spacer() // will be ignore - View2() - View3() - Spacer() - } - - | --spacer-- | view1 | --spacer-- | view2 | view3 | --spacer-- | - // spacer width? - // (frame.width - (view1 + view2 + view2 + view1~view2.spacing + view2~view3.spacing).width) / spacers.count = spacer.width - - // 320 - // 50 * 3 + 8 * 2 - // 320 - (50*3+8*2) = 154 - // 154 / 3 - */ -//extension _StackKitHStackContentResultBuilder: _StackKitViewSpacerResultBuilder { -// public static func buildExpression(_ expression: Spacer) -> [UIView] { -// let view = SpacerView() -// view.maxLength = expression.maxLength -// view.frame.size.height = 1 -// return [view] -// } -//} -// -//extension _StackKitVStackContentResultBuilder: _StackKitViewSpacerResultBuilder { -// -// public static func buildExpression(_ expression: Spacer) -> [UIView] { -// let view = SpacerView() -// view.maxLength = expression.maxLength -// view.frame.size.width = 1 -// return [view] -// } -//} +class SpacerLayer: CALayer, _Spacer { + var length: CGFloat = .greatestFiniteMagnitude + var min: CGFloat = .leastNonzeroMagnitude + var max: CGFloat = .greatestFiniteMagnitude + + var setLength: CGFloat = -1 + + required init(length: CGFloat, min: CGFloat, max: CGFloat) { + super.init() + + self.length = length + self.min = min + self.max = max + + self.backgroundColor = UIColor.clear.cgColor + } + override init(layer: Any) { + super.init(layer: layer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // 自定义长度范围 + var isCustomRange: Bool { + (min != .leastNonzeroMagnitude || max != .greatestFiniteMagnitude) && length == .greatestFiniteMagnitude + } + // 自定义固定长度 + var isDefine: Bool { + length != .greatestFiniteMagnitude + } + + enum Size { + case width, height + } + func fitSize(value: CGFloat, for size: Size) { + let length: CGFloat + if isDefine { + length = self.length + } else if isCustomRange { + length = Swift.max(Swift.min(value, max), min) + } else { + length = value + } + + switch size { + case .width: + frame.size.width = length + case .height: + frame.size.height = length + } + self.setLength = length + } + + var minLength: CGFloat { + if setLength != -1 { + return setLength + } + if length != .greatestFiniteMagnitude { + return length + } + if max != .greatestFiniteMagnitude { + return max + } + if min != .leastNonzeroMagnitude { + return min + } + return 0 + } +} diff --git a/Sources/StackKit/SpacerResultBuilder.swift b/Sources/StackKit/SpacerResultBuilder.swift new file mode 100644 index 0000000..188714a --- /dev/null +++ b/Sources/StackKit/SpacerResultBuilder.swift @@ -0,0 +1,29 @@ +import UIKit + +// MARK: - For *StackView +public protocol _StackKitViewSpacerResultBuilder { + static func buildExpression(_ expression: Spacer) -> [UIView] +} +extension _StackKitViewSpacerResultBuilder { + public static func buildExpression(_ expression: Spacer) -> [UIView] { + let spacer = SpacerView(length: expression.length, min: expression.min, max: expression.max) + return [spacer] + } +} + +extension _StackKitHStackContentResultBuilder: _StackKitViewSpacerResultBuilder { } +extension _StackKitVStackContentResultBuilder: _StackKitViewSpacerResultBuilder { } + + +// MARK: - For *StackLayer +public protocol _StackKitLayerSpacerResultBuilder { + static func buildExpression(_ expression: Spacer) -> [CALayer] +} +extension _StackKitLayerSpacerResultBuilder { + public static func buildExpression(_ expression: Spacer) -> [CALayer] { + let spacer = SpacerLayer(length: expression.length, min: expression.min, max: expression.max) + return [spacer] + } +} +extension _StackKitHStackLayerContentResultBuilder: _StackKitLayerSpacerResultBuilder { } +extension _StackKitVStackLayerContentResultBuilder: _StackKitLayerSpacerResultBuilder { } diff --git a/Sources/StackKit/VStackLayer.swift b/Sources/StackKit/VStackLayer.swift index 03318f2..bedcae0 100644 --- a/Sources/StackKit/VStackLayer.swift +++ b/Sources/StackKit/VStackLayer.swift @@ -5,16 +5,6 @@ open class VStackLayer: CALayer { public var alignment: VStackAlignment = .center public var distribution: VStackDistribution = .autoSpacing - public var contentSize: CGSize { - effectiveSublayers.map({ $0.frame }).reduce(CGRect.zero) { result, rect in - result.union(rect) - }.size - } - - open override func preferredFrameSize() -> CGSize { - contentSize - } - public required override init() { super.init() } @@ -46,6 +36,16 @@ open class VStackLayer: CALayer { (sublayers ?? []).lazy.filter { $0._isEffectiveLayer } } + public var contentSize: CGSize { + effectiveSublayers.map({ $0.frame }).reduce(CGRect.zero) { result, rect in + result.union(rect) + }.size + } + + open override func preferredFrameSize() -> CGSize { + contentSize + } + open func refreshSublayers() { for v in sublayers ?? [] where v.frame.size == .zero { v.frame.size = v.preferredFrameSize() @@ -74,24 +74,33 @@ open class VStackLayer: CALayer { switch distribution { case .spacing(let spacing): + fillDivider() + fillSpacer() + makeSpacing(spacing) case .autoSpacing: + fillDivider() + fillSpacer() + let spacing = autoSpacing() makeSpacing(spacing) case .fillWidth: + fillDivider() + fillSpacer() + let spacing = autoSpacing() makeSpacing(spacing) fillWidth() case .fill: + fillDivider() + fillSpecifySpacer() + fillSpacer() + fillHeight() + makeSpacing(0) fillWidth() - - let h = frame.height / CGFloat(effectiveSublayers.count) - effectiveSublayers.forEach { - $0.frame.size.height = h - } } } @@ -114,10 +123,33 @@ open class VStackLayer: CALayer { } } +extension VStackLayer { + + private func spacerLayers() -> [SpacerLayer] { + effectiveSublayers.compactMap({ $0 as? SpacerLayer }) + } + private func dividerLayers() -> [DividerLayer] { + effectiveSublayers.compactMap({ $0 as? DividerLayer }) + } + + private func viewsWithoutSpacer() -> [CALayer] { + effectiveSublayers.filter({ ($0 as? SpacerLayer) == nil }) + } + private func viewsWithoutSpacerAndDivider() -> [CALayer] { + effectiveSublayers.filter({ ($0 as? SpacerLayer) == nil && ($0 as? DividerLayer) == nil }) + } +} + extension VStackLayer { private func autoSpacing() -> CGFloat { - (frame.height - effectiveSublayers.map({ $0.frame.size.height }).reduce(0, { $0 + $1 })) / CGFloat(effectiveSublayers.count) + let unspacerViews = viewsWithoutSpacer() + let spacersCount = spacerLayers().map({ isSpacerBetweenViews($0) }).filter({ $0 }).count + return (frame.height - viewsHeight() - spacerSpecifyLength()) / CGFloat(unspacerViews.count - spacersCount - 1) + } + + private func viewsHeight() -> CGFloat { + viewsWithoutSpacer().map({ $0.frame.height }).reduce(0, +) } private func makeSpacing(_ spacing: CGFloat) { @@ -126,7 +158,12 @@ extension VStackLayer { sublayer.frame.origin.y = 0 } else { let previousLayer = effectiveSublayers[index - 1] - sublayer.frame.origin.y = previousLayer.frame.maxY + spacing + if (previousLayer as? SpacerLayer) != nil || (sublayer as? SpacerLayer) != nil { + // spacer and view no spacing + sublayer.frame.origin.y = previousLayer.frame.maxY + } else { + sublayer.frame.origin.y = previousLayer.frame.maxY + spacing + } } } } @@ -136,4 +173,114 @@ extension VStackLayer { $0.frame.size.width = frame.width } } + + /// + /// 填充高度, 所有视图(排除 spacer)高度一致 + private func fillHeight() { + let maxH = frame.height - spacerSpecifyLength() - dividerSpecifyLength() + var h = (maxH) / CGFloat(viewsWithoutSpacerAndDivider().count) + + let unspacersView = viewsWithoutSpacerAndDivider() + h = maxH / CGFloat(unspacersView.count) + for subview in unspacersView { + subview.frame.size.height = h + } + } +} + +extension VStackLayer { + + private func dividerSpecifyLength() -> CGFloat { + dividerLayers() + .map({ $0.thickness }) + .reduce(0, +) + } + + private func fillDivider() { + let maxWidth = effectiveSublayers.filter({ ($0 as? DividerLayer) == nil }).map({ $0.frame.size.width }).max() ?? frame.width + for divider in effectiveSublayers.compactMap({ $0 as? DividerLayer }) { + var maxLength = divider.maxLength + if maxLength == .greatestFiniteMagnitude { + // auto + maxLength = maxWidth + } else if maxWidth < maxLength { + maxLength = maxWidth + } + divider.frame.size.width = maxLength + } + } + +} + +// MARK: Spacer +extension VStackLayer { + + // 取出固定 length 的 spacer + private func spacerSpecifyLength() -> CGFloat { + spacerLayers() + .map({ $0.setLength }) + .reduce(0, +) + } + + private func isSpacerBetweenViews(_ spacer: SpacerLayer) -> Bool { + guard let index = effectiveSublayers.firstIndex(of: spacer) else { + return false + } + + var isPreviousView = false + var isNextView = false + + let previous = index - 1 + if previous > 0, previous < effectiveSublayers.count - 1 { + isPreviousView = true + } + + let next = index + 1 + if next < effectiveSublayers.count - 1 { + isNextView = true + } + return isPreviousView && isNextView + } + + private func fillSpecifySpacer() { + let spacers = effectiveSublayers.compactMap({ $0 as? SpacerLayer }) + for spacer in spacers { + spacer.fitSize(value: 0, for: .height) + } + } + + /// 填充 spacer + private func fillSpacer() { + let unspacerViews = viewsWithoutSpacer() + guard unspacerViews.count != effectiveSublayers.count else { return } + + let betweenInViewsCount = spacerLayers().map({ isSpacerBetweenViews($0) }).filter({ $0 }).count + let unspacerViewsHeight = viewsHeight() + // 排除 spacer view 后的间距 + let unspacerViewsSpacing: CGFloat + + if unspacerViews.count == 1 { + unspacerViewsSpacing = 0 + } else { + switch distribution { + case .spacing(let spacing): + unspacerViewsSpacing = spacing * CGFloat(unspacerViews.count - betweenInViewsCount - 1) // 正常 spacing 数量: (views.count - 1), spacer 左右的视图没有间距,所以需要再排除在 view 之间的 spacer 数量 + + case .autoSpacing, .fillWidth: + unspacerViewsSpacing = autoSpacing() * CGFloat(unspacerViews.count - betweenInViewsCount - 1) + + case .fill: + unspacerViewsSpacing = 0 + } + } + + let unspacerViewsMaxHeight = unspacerViewsHeight + unspacerViewsSpacing + let spacersHeight = (frame.height - unspacerViewsMaxHeight) + let spacerWidth = spacersHeight / CGFloat(self.spacerLayers().count) + + let spacerViews = self.spacerLayers() + for spacer in spacerViews { + spacer.fitSize(value: spacerWidth, for: .height) + } + } } diff --git a/Sources/StackKit/VStackLayerWrapperView.swift b/Sources/StackKit/VStackLayerWrapperView.swift index 5eead89..baacd09 100644 --- a/Sources/StackKit/VStackLayerWrapperView.swift +++ b/Sources/StackKit/VStackLayerWrapperView.swift @@ -55,15 +55,33 @@ open class VStackLayerWrapperView: UIView { } subview._tryFixSize() - // use view.layer if view is UIImageView + // Copy UIImageView if subview is UIImageView { let imageLayer = CALayer() imageLayer.contents = subview.layer.contents imageLayer.bounds = subview.layer.bounds + imageLayer.backgroundColor = subview.layer.backgroundColor layer.addSublayer(imageLayer) return } + // Divider View to DividerLayer + if let dividerView = subview as? DividerView { + let dividerLayer = DividerLayer() + dividerLayer.maxLength = dividerView.maxLength + dividerLayer.frame.size.height = dividerView.thickness + dividerLayer.backgroundColor = dividerView.backgroundColor?.cgColor + dividerLayer.cornerRadius = dividerView.cornerRadius + layer.addSublayer(dividerLayer) + return + } + + if let spacerView = subview as? SpacerView { + let spacerLayer = spacerView.spacerLayer + layer.addSublayer(spacerLayer) + return + } + // render view to image and create CALayer with it // 如果直接使用 view.layer 可能显示会有问题, 例如: UILabel -> _UILabelLayer 无法正常显示文字 let image = UIGraphicsImageRenderer(bounds: subview.bounds).image { context in @@ -72,6 +90,7 @@ open class VStackLayerWrapperView: UIView { let tempLayer = CALayer() tempLayer.contents = image.cgImage tempLayer.bounds = subview.bounds + tempLayer.backgroundColor = subview.backgroundColor?.cgColor layer.addSublayer(tempLayer) } diff --git a/Sources/StackKit/VStackView.swift b/Sources/StackKit/VStackView.swift index 780dc61..a77531f 100644 --- a/Sources/StackKit/VStackView.swift +++ b/Sources/StackKit/VStackView.swift @@ -161,7 +161,7 @@ extension VStackView { private func autoSpacing() -> CGFloat { let unspacerViews = viewsWithoutSpacer() let spacersCount = spacerViews().map({ isSpacerBetweenViews($0) }).filter({ $0 }).count - return viewsHeight() / CGFloat(unspacerViews.count - spacersCount - 1) + return (frame.height - viewsHeight() - spacerSpecifyLength()) / CGFloat(unspacerViews.count - spacersCount - 1) } private func viewsHeight() -> CGFloat {