宽窄优行-由【嘉易行】项目成品而来
younger_times
2023-04-06 a1ae6802080a22e6e6ce6d0935e95facb1daca5c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
//
//  ButtonsBarView.swift
//  SwiftEntryKit_Example
//
//  Created by Daniel Huri on 4/28/18.
//  Copyright (c) 2018 huri000@gmail.com. All rights reserved.
//
 
import UIKit
import QuickLayout
 
/**
 Dynamic button bar view
 Buttons are set according to the received content.
 1-2 buttons spread horizontally
 3 or more buttons spread vertically
 */
final public class EKButtonBarView: UIView {
    
    // MARK: - Properties
    
    private var buttonViews: [EKButtonView] = []
    private var separatorViews: [UIView] = []
    
    private let buttonBarContent: EKProperty.ButtonBarContent
    private let spreadAxis: QLAxis
    private let oppositeAxis: QLAxis
    private let relativeEdge: NSLayoutConstraint.Attribute
    
    var bottomCornerRadius: CGFloat = 0 {
        didSet {
            adjustRoundCornersIfNecessary()
        }
    }
    
    private lazy var buttonEdgeRatio: CGFloat = {
        return 1.0 / CGFloat(self.buttonBarContent.content.count)
    }()
    
    private(set) lazy var intrinsicHeight: CGFloat = {
        var height: CGFloat = 0
        switch buttonBarContent.content.count {
        case 0:
            height += 1
        case 1...buttonBarContent.horizontalDistributionThreshold:
            height += buttonBarContent.buttonHeight
        default:
            for _ in 1...buttonBarContent.content.count {
                height += buttonBarContent.buttonHeight
            }
        }
        return height
    }()
    
    private var compressedConstraint: NSLayoutConstraint!
    private lazy var expandedConstraint: NSLayoutConstraint = {
        return set(.height, of: intrinsicHeight, priority: .defaultLow)
    }()
 
    // MARK: Setup
    required public init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    public init(with buttonBarContent: EKProperty.ButtonBarContent) {
        self.buttonBarContent = buttonBarContent
        if buttonBarContent.content.count <= buttonBarContent.horizontalDistributionThreshold {
            spreadAxis = .horizontally
            oppositeAxis = .vertically
            relativeEdge = .width
        } else {
            spreadAxis = .vertically
            oppositeAxis = .horizontally
            relativeEdge = .height
        }
        super.init(frame: .zero)
        setupButtonBarContent()
        setupSeparatorViews()
        
        compressedConstraint = set(.height, of: 1, priority: .must)
    }
    
    public override func layoutSubviews() {
        super.layoutSubviews()
        adjustRoundCornersIfNecessary()
    }
 
    private func setupButtonBarContent() {
        for content in buttonBarContent.content {
            let buttonView = EKButtonView(content: content)
            addSubview(buttonView)
            buttonViews.append(buttonView)
        }
        layoutButtons()
    }
    
    private func layoutButtons() {
        guard !buttonViews.isEmpty else {
            return
        }
        let suffix = Array(buttonViews.dropFirst())
        if !suffix.isEmpty {
            suffix.layout(.height, to: buttonViews.first!)
        }
        buttonViews.layoutToSuperview(axis: oppositeAxis)
        buttonViews.spread(spreadAxis, stretchEdgesToSuperview: true)
        buttonViews.layout(relativeEdge, to: self, ratio: buttonEdgeRatio, priority: .must)
    }
    
    private func setupTopSeperatorView() {
        let topSeparatorView = UIView()
        addSubview(topSeparatorView)
        topSeparatorView.set(.height, of: 1)
        topSeparatorView.layoutToSuperview(.left, .right, .top)
        separatorViews.append(topSeparatorView)
    }
    
    private func setupSeperatorView(after view: UIView) {
        let midSepView = UIView()
        addSubview(midSepView)
        let sepAttribute: NSLayoutConstraint.Attribute
        let buttonAttribute: NSLayoutConstraint.Attribute
        switch oppositeAxis {
        case .vertically:
            sepAttribute = .centerX
            buttonAttribute = .right
        case .horizontally:
            sepAttribute = .centerY
            buttonAttribute = .bottom
        }
        midSepView.layout(sepAttribute, to: buttonAttribute, of: view)
        midSepView.set(relativeEdge, of: 1)
        midSepView.layoutToSuperview(axis: oppositeAxis)
        separatorViews.append(midSepView)
    }
    
    private func setupSeparatorViews() {
        setupTopSeperatorView()
        for button in buttonViews.dropLast() {
            setupSeperatorView(after: button)
        }
        setupInterfaceStyle()
    }
    
    // Amination
    public func expand() {
        let expansion = {
            self.compressedConstraint.priority = .defaultLow
            self.expandedConstraint.priority = .must
            
            /* NOTE: Calling layoutIfNeeded for the whole view hierarchy.
             Sometimes it's easier to just use frames instead of AutoLayout for
             hierarch complexity considerations. Here the animation influences almost the
             entire view hierarchy. */
            SwiftEntryKit.layoutIfNeeded()
        }
        
        alpha = 1
        if buttonBarContent.expandAnimatedly {
            let damping: CGFloat = buttonBarContent.content.count <= 2 ? 0.4 : 0.8
            SwiftEntryKit.layoutIfNeeded()
            UIView.animate(withDuration: 0.8, delay: 0, usingSpringWithDamping: damping, initialSpringVelocity: 0, options: [.beginFromCurrentState, .allowUserInteraction, .layoutSubviews, .allowAnimatedContent], animations: {
                expansion()
            }, completion: nil)
        } else {
            expansion()
        }
    }
    
    public func compress() {
        compressedConstraint.priority = .must
        expandedConstraint.priority = .defaultLow
    }
    
    private func adjustRoundCornersIfNecessary() {
        let size = CGSize(width: bottomCornerRadius, height: bottomCornerRadius)
        let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: .bottom, cornerRadii: size)
        let maskLayer = CAShapeLayer()
        maskLayer.path = path.cgPath
        layer.mask = maskLayer
    }
    
    private func setupInterfaceStyle() {
        for view in separatorViews {
            view.backgroundColor = buttonBarContent.separatorColor(for: traitCollection)
        }
    }
    
    public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        setupInterfaceStyle()
    }
}