Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 15 additions & 28 deletions Sources/StackKit/Divider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
}

Expand All @@ -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(
Expand All @@ -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) {
Expand All @@ -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")
}
}
57 changes: 57 additions & 0 deletions Sources/StackKit/DividerResultBuilder.swift
Original file line number Diff line number Diff line change
@@ -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]
}
}
185 changes: 168 additions & 17 deletions Sources/StackKit/HStackLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down Expand Up @@ -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()
Expand All @@ -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
}
}
}

Expand All @@ -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) {
Expand All @@ -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
}
}
}
}
Expand All @@ -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)
}
}
}
Loading