@bm-fe/react-native-ui-components 1.1.3 → 1.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ios/DemoProject/NativeDesign/PrimaryButtonViewManager.swift +0 -1
- package/ios/DemoProject/NativeDesign/ReactBridge-Bridging-Header.h +12 -0
- package/ios/DemoProject/NativeDesign/TextButtonViewManager.swift +0 -1
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/Bitmart Card.imageset/Contents.json +21 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/Bitmart Card.imageset/Property 1=Bitmart Card.svg +3 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/Contents.json +6 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/Credit Debit Card.imageset/Contents.json +21 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/Credit Debit Card.imageset/Property 1=Credit Debit Card.svg +3 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/Crypto Prepaid Card.imageset/Contents.json +21 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/Crypto Prepaid Card.imageset/Property 1=Crypto Prepaid Card.svg +3 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/Mobile Recharge.imageset/Contents.json +21 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/Mobile Recharge.imageset/Mobile Recharge.svg +3 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/P2P Trading.imageset/Contents.json +21 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/P2P Trading.imageset/Property 1=P2P Trading.svg +3 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/SEPA Deposit.imageset/Contents.json +21 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/SEPA Deposit.imageset/SEPA Deposit.svg +3 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/Third-Party Payment.imageset/Contents.json +21 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/Third-Party Payment.imageset/Property 1=Third-Party Payment.svg +3 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/Contents.json +6 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Contents.json +6 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/checkmark.imageset/Contents.json +21 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/checkmark.imageset/checkmark.pdf +0 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/close_icon.imageset/Contents.json +22 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/close_icon.imageset/close_icon@2x.png +0 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/close_icon.imageset/close_icon@3x.png +0 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/cross.imageset/Contents.json +21 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/cross.imageset/cross.pdf +0 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/progress.imageset/Contents.json +21 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/progress.imageset/progress.pdf +0 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/progress_circular.imageset/Contents.json +21 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/progress_circular.imageset/progress_circular.pdf +0 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/refresh_footer_dark.imageset/Contents.json +22 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/refresh_footer_dark.imageset/refresh_footer_dark@2x.png +0 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/refresh_footer_dark.imageset/refresh_footer_dark@3x.png +0 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/refresh_footer_light.imageset/Contents.json +22 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/refresh_footer_light.imageset/refresh_footer_light@2x.png +0 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/refresh_footer_light.imageset/refresh_footer_light@3x.png +0 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/search_icon.imageset/Contents.json +21 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/search_icon.imageset/Group 13994.svg +7 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/sheet_dark_chose.imageset/Contents.json +21 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/sheet_dark_chose.imageset/Frame.svg +3 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/sheet_list_arrow.imageset/Contents.json +22 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/sheet_list_arrow.imageset/sheet_list_arrow@2x.png +0 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/sheet_list_arrow.imageset/sheet_list_arrow@3x.png +0 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/sheet_list_cell_checkbox.imageset/Contents.json +21 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/sheet_list_cell_checkbox.imageset/Frame (1).svg +3 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/sheet_list_chose.imageset/Contents.json +21 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/sheet_list_chose.imageset/Frame.png +0 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/slider_bubble.imageset/Contents.json +22 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/slider_bubble.imageset/slider_bubble@2x.png +0 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/slider_bubble.imageset/slider_bubble@3x.png +0 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/spot_second_floor_refresh_arrow.imageset/Contents.json +21 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/spot_second_floor_refresh_arrow.imageset/spot_second_floor_refresh_arrow.svg +8 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Font/Alexandria-Medium.ttf +0 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Font/Alexandria-Regular.ttf +0 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Font/Alexandria-SemiBold.ttf +0 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Classes/BMFont/BMFont.swift +82 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Classes/BMFont/UIFontExtensions.swift +120 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Classes/Components/AlertView/BMComponentAlertController.swift +574 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Classes/Components/Buttons/BMComponentButton+Examples.swift +77 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Classes/Components/Buttons/BMComponentButton.swift +373 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Classes/Components/Buttons/BMComponentButtonConfiguration.swift +181 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Classes/Components/Popup/BMComponentPopupController.swift +312 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Classes/Components/SegmentView/BMComponentSegmentedTitleCell.swift +294 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Classes/Components/SegmentView/BMComponentSegmentedTitleDataSource.swift +49 -0
- package/ios/Modules/BMUIComponents/BMUIComponents/Classes/Components/SegmentView/BMComponentSegmentedView.swift +292 -0
- package/ios/Modules/BMUIComponents/LICENSE +19 -0
- package/ios/Modules/BMUIComponents/README.md +29 -0
- package/package.json +3 -1
- package/react-native-ui-components.podspec +39 -4
package/ios/Modules/BMUIComponents/BMUIComponents/Classes/Components/Buttons/BMComponentButton.swift
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
//
|
|
2
|
+
// BMComponentButton.swift
|
|
3
|
+
// BMComponents
|
|
4
|
+
//
|
|
5
|
+
// Created by james on 2025/12/31.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import UIKit
|
|
9
|
+
import BMTheme
|
|
10
|
+
import BMCore
|
|
11
|
+
import SnapKit
|
|
12
|
+
|
|
13
|
+
/// 组件化 Button - 简化设计,确保文字正常显示
|
|
14
|
+
public class BMComponentButton: UIControl {
|
|
15
|
+
|
|
16
|
+
/// 按钮配置
|
|
17
|
+
public var configuration: BMComponentButtonConfiguration {
|
|
18
|
+
didSet {
|
|
19
|
+
applyConfiguration()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// 按钮点击状态
|
|
24
|
+
private var currentState: BMComponentButtonState = .normal {
|
|
25
|
+
didSet {
|
|
26
|
+
updateStateAppearance()
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// 是否正在加载
|
|
31
|
+
public var isLoading: Bool = false {
|
|
32
|
+
didSet {
|
|
33
|
+
updateState()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// 重写 isEnabled 属性以同步状态
|
|
38
|
+
public override var isEnabled: Bool {
|
|
39
|
+
didSet {
|
|
40
|
+
updateState()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// 标题文本
|
|
45
|
+
public var title: String? {
|
|
46
|
+
didSet {
|
|
47
|
+
titleLabel.text = title
|
|
48
|
+
updateLayout()
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// 图标
|
|
53
|
+
public var icon: UIImage? {
|
|
54
|
+
didSet {
|
|
55
|
+
iconImageView.image = icon
|
|
56
|
+
updateLayout()
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// MARK: - Private Properties
|
|
61
|
+
|
|
62
|
+
private let titleLabel = UILabel()
|
|
63
|
+
private let iconImageView = UIImageView()
|
|
64
|
+
private let loadingIndicator = UIActivityIndicatorView(style: .medium)
|
|
65
|
+
|
|
66
|
+
private var heightConstraint: Constraint?
|
|
67
|
+
private var iconLeadingConstraint: Constraint?
|
|
68
|
+
private var iconTrailingConstraint: Constraint?
|
|
69
|
+
private var titleLeadingConstraint: Constraint?
|
|
70
|
+
private var titleTrailingConstraint: Constraint?
|
|
71
|
+
|
|
72
|
+
// MARK: - Initialization
|
|
73
|
+
|
|
74
|
+
/// 通过配置初始化
|
|
75
|
+
public init(title: String? = nil, icon: UIImage? = nil,configuration: BMComponentButtonConfiguration = .primary()) {
|
|
76
|
+
self.title = title
|
|
77
|
+
self.icon = icon
|
|
78
|
+
self.configuration = configuration
|
|
79
|
+
super.init(frame: .zero)
|
|
80
|
+
setupUI()
|
|
81
|
+
applyConfiguration()
|
|
82
|
+
updateLayout()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
required init?(coder: NSCoder) {
|
|
87
|
+
fatalError("init(coder:) has not been implemented")
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// MARK: - Setup
|
|
91
|
+
|
|
92
|
+
private func setupUI() {
|
|
93
|
+
// 添加子视图
|
|
94
|
+
addSubview(titleLabel)
|
|
95
|
+
addSubview(iconImageView)
|
|
96
|
+
addSubview(loadingIndicator)
|
|
97
|
+
// 配置标题标签
|
|
98
|
+
titleLabel.text = title
|
|
99
|
+
titleLabel.textAlignment = .center
|
|
100
|
+
titleLabel.numberOfLines = 1
|
|
101
|
+
titleLabel.adjustsFontSizeToFitWidth = false
|
|
102
|
+
titleLabel.lineBreakMode = .byTruncatingTail
|
|
103
|
+
|
|
104
|
+
// 配置图标
|
|
105
|
+
iconImageView.image = icon
|
|
106
|
+
iconImageView.contentMode = .scaleAspectFit
|
|
107
|
+
iconImageView.tintColor = .white
|
|
108
|
+
|
|
109
|
+
// 配置加载指示器
|
|
110
|
+
loadingIndicator.hidesWhenStopped = true
|
|
111
|
+
loadingIndicator.color = .white // 默认白色,会在 applyConfiguration 中更新
|
|
112
|
+
|
|
113
|
+
// 设置高度约束
|
|
114
|
+
snp.makeConstraints { make in
|
|
115
|
+
heightConstraint = make.height.equalTo(configuration.size.height).constraint
|
|
116
|
+
}
|
|
117
|
+
// 初始化状态
|
|
118
|
+
updateState()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// MARK: - Configuration
|
|
122
|
+
private func applyConfiguration() {
|
|
123
|
+
// 更新高度
|
|
124
|
+
heightConstraint?.update(offset: configuration.size.height)
|
|
125
|
+
// 更新字体
|
|
126
|
+
if let font = configuration.currentFont {
|
|
127
|
+
titleLabel.font = font
|
|
128
|
+
} else {
|
|
129
|
+
titleLabel.font = configuration.size.sizeFont
|
|
130
|
+
}
|
|
131
|
+
// 更新圆角
|
|
132
|
+
if let cornerRadius = configuration.cornerRadius {
|
|
133
|
+
layer.cornerRadius = cornerRadius
|
|
134
|
+
} else {
|
|
135
|
+
layer.cornerRadius = configuration.size.height / 2
|
|
136
|
+
}
|
|
137
|
+
layer.masksToBounds = true
|
|
138
|
+
// 更新颜色
|
|
139
|
+
updateColors()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// 根据状态更新外观
|
|
143
|
+
private func updateStateAppearance() {
|
|
144
|
+
// 先更新背景色(所有状态都使用相同的背景色)
|
|
145
|
+
updateColors()
|
|
146
|
+
switch currentState {
|
|
147
|
+
case .normal:
|
|
148
|
+
// 正常状态 - 正常效果
|
|
149
|
+
alpha = 1.0
|
|
150
|
+
case .pressed:
|
|
151
|
+
// 点击状态 - 正常背景加上透明度
|
|
152
|
+
alpha = 0.7 // 点击时降低透明度
|
|
153
|
+
case .disable:
|
|
154
|
+
// 禁用状态 - 不可以点击效果
|
|
155
|
+
alpha = configuration.disableAlpha
|
|
156
|
+
case .loading:
|
|
157
|
+
// 加载状态 - 显示加载动画,隐藏文字和图标
|
|
158
|
+
alpha = 1.0
|
|
159
|
+
titleLabel.isHidden = true
|
|
160
|
+
iconImageView.isHidden = true
|
|
161
|
+
loadingIndicator.isHidden = false
|
|
162
|
+
loadingIndicator.startAnimating()
|
|
163
|
+
return // 加载状态需要特殊处理,直接返回
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 非加载状态时,根据是否有文字/图标来决定显示
|
|
167
|
+
if currentState != .loading {
|
|
168
|
+
let hasTitle = title != nil && !title!.isEmpty
|
|
169
|
+
let hasIcon = icon != nil
|
|
170
|
+
titleLabel.isHidden = !hasTitle
|
|
171
|
+
iconImageView.isHidden = !hasIcon
|
|
172
|
+
loadingIndicator.stopAnimating()
|
|
173
|
+
loadingIndicator.isHidden = true
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private func updateColors() {
|
|
178
|
+
if currentState == .disable {
|
|
179
|
+
// 背景色
|
|
180
|
+
cexTheme.backgroundColor = .bgLineColor
|
|
181
|
+
// 文字颜色
|
|
182
|
+
titleLabel.cexTheme.textColor = .thirdColor
|
|
183
|
+
// 图标颜色
|
|
184
|
+
iconImageView.cexTheme.tintColor = .thirdColor
|
|
185
|
+
// 加载指示器颜色(使用前景色)
|
|
186
|
+
// loadingIndicator.color = configuration.type.foregroundColor.currentUIColor()
|
|
187
|
+
} else {
|
|
188
|
+
// 背景色
|
|
189
|
+
cexTheme.backgroundColor = configuration.type.backgroundColor
|
|
190
|
+
// 文字颜色
|
|
191
|
+
titleLabel.cexTheme.textColor = configuration.type.foregroundColor
|
|
192
|
+
// 图标颜色
|
|
193
|
+
iconImageView.cexTheme.tintColor = configuration.type.foregroundColor
|
|
194
|
+
// 加载指示器颜色(使用前景色)
|
|
195
|
+
// loadingIndicator.color = configuration.type.foregroundColor.currentUIColor()
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/// 更新按钮状态
|
|
200
|
+
private func updateState() {
|
|
201
|
+
if isLoading {
|
|
202
|
+
currentState = .loading
|
|
203
|
+
} else if !isEnabled {
|
|
204
|
+
currentState = .disable
|
|
205
|
+
} else {
|
|
206
|
+
// 如果当前不是 pressed 状态,则恢复为 normal
|
|
207
|
+
if currentState != .pressed {
|
|
208
|
+
currentState = .normal
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// MARK: - Layout
|
|
214
|
+
private func updateLayout() {
|
|
215
|
+
let hasTitle = title != nil && !title!.isEmpty
|
|
216
|
+
let hasIcon = icon != nil
|
|
217
|
+
// 只有在非加载状态下才更新文字和图标的显示状态
|
|
218
|
+
// 加载状态下的显示由 updateStateAppearance() 控制
|
|
219
|
+
if currentState != .loading {
|
|
220
|
+
titleLabel.isHidden = !hasTitle
|
|
221
|
+
iconImageView.isHidden = !hasIcon
|
|
222
|
+
}
|
|
223
|
+
// 移除旧约束
|
|
224
|
+
iconLeadingConstraint?.deactivate()
|
|
225
|
+
iconTrailingConstraint?.deactivate()
|
|
226
|
+
titleLeadingConstraint?.deactivate()
|
|
227
|
+
titleTrailingConstraint?.deactivate()
|
|
228
|
+
iconLeadingConstraint = nil
|
|
229
|
+
iconTrailingConstraint = nil
|
|
230
|
+
titleLeadingConstraint = nil
|
|
231
|
+
titleTrailingConstraint = nil
|
|
232
|
+
|
|
233
|
+
if hasIcon && hasTitle {
|
|
234
|
+
// 有图标和文字
|
|
235
|
+
let iconSize = configuration.size.iconSize
|
|
236
|
+
let spacing = configuration.size.iconSpacing
|
|
237
|
+
let padding = configuration.size.horizontalPadding
|
|
238
|
+
if configuration.iconPosition == .leading {
|
|
239
|
+
// 图标在左侧
|
|
240
|
+
iconImageView.snp.remakeConstraints { make in
|
|
241
|
+
iconLeadingConstraint = make.leading.equalToSuperview().offset(padding).constraint
|
|
242
|
+
make.centerY.equalToSuperview()
|
|
243
|
+
make.size.equalTo(iconSize)
|
|
244
|
+
}
|
|
245
|
+
titleLabel.snp.remakeConstraints { make in
|
|
246
|
+
titleLeadingConstraint = make.leading.equalTo(iconImageView.snp.trailing).offset(spacing).constraint
|
|
247
|
+
titleTrailingConstraint = make.trailing.equalToSuperview().offset(-padding).constraint
|
|
248
|
+
make.centerY.equalToSuperview()
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
// 图标在右侧
|
|
252
|
+
titleLabel.snp.remakeConstraints { make in
|
|
253
|
+
titleLeadingConstraint = make.leading.equalToSuperview().offset(padding).constraint
|
|
254
|
+
make.centerY.equalToSuperview()
|
|
255
|
+
}
|
|
256
|
+
iconImageView.snp.remakeConstraints { make in
|
|
257
|
+
iconTrailingConstraint = make.trailing.equalToSuperview().offset(-padding).constraint
|
|
258
|
+
make.leading.equalTo(titleLabel.snp.trailing).offset(spacing)
|
|
259
|
+
make.centerY.equalToSuperview()
|
|
260
|
+
make.size.equalTo(iconSize)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
} else if hasIcon {
|
|
264
|
+
// 只有图标
|
|
265
|
+
let iconSize = configuration.size.iconSize
|
|
266
|
+
iconImageView.snp.remakeConstraints { make in
|
|
267
|
+
make.center.equalToSuperview()
|
|
268
|
+
make.size.equalTo(iconSize)
|
|
269
|
+
}
|
|
270
|
+
} else if hasTitle {
|
|
271
|
+
// 只有文字
|
|
272
|
+
let padding = configuration.size.horizontalPadding
|
|
273
|
+
titleLabel.snp.remakeConstraints { make in
|
|
274
|
+
titleLeadingConstraint = make.leading.equalToSuperview().offset(padding).constraint
|
|
275
|
+
titleTrailingConstraint = make.trailing.equalToSuperview().offset(-padding).constraint
|
|
276
|
+
make.centerY.equalToSuperview()
|
|
277
|
+
make.height.lessThanOrEqualToSuperview()
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
// 既没有文字也没有图标,设置最小宽度
|
|
281
|
+
snp.remakeConstraints { make in
|
|
282
|
+
make.width.greaterThanOrEqualTo(configuration.size.minWidth)
|
|
283
|
+
make.height.equalTo(configuration.size.height)
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// 加载指示器
|
|
287
|
+
loadingIndicator.snp.remakeConstraints { make in
|
|
288
|
+
make.center.equalToSuperview()
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/// 仅文字时用 frame 强制 titleLabel 占满宽度并居中,避免外部通过 frame 设置按钮大小时约束未正确更新导致文字不居中
|
|
293
|
+
public override func layoutSubviews() {
|
|
294
|
+
super.layoutSubviews()
|
|
295
|
+
let hasTitle = title != nil && !title!.isEmpty
|
|
296
|
+
let hasIcon = icon != nil
|
|
297
|
+
if hasTitle && !hasIcon && !isLoading && bounds.width > 0 && bounds.height > 0 {
|
|
298
|
+
let padding = configuration.size.horizontalPadding
|
|
299
|
+
titleLabel.textAlignment = .center
|
|
300
|
+
titleLabel.frame = CGRect(x: padding, y: 0, width: bounds.width - padding * 2, height: bounds.height)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
// MARK: - Touch Events
|
|
306
|
+
@objc private func handleTouchDown() {
|
|
307
|
+
// 只有在启用且非加载状态下才响应点击
|
|
308
|
+
if isEnabled && !isLoading {
|
|
309
|
+
currentState = .pressed
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
@objc private func handleTouchUp() {
|
|
314
|
+
// 触摸结束时恢复状态
|
|
315
|
+
if isEnabled && !isLoading {
|
|
316
|
+
currentState = .normal
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
|
321
|
+
super.touchesBegan(touches, with: event)
|
|
322
|
+
handleTouchDown()
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
|
|
326
|
+
super.touchesMoved(touches, with: event)
|
|
327
|
+
// 如果触摸移出按钮范围,恢复正常状态
|
|
328
|
+
if let touch = touches.first {
|
|
329
|
+
let location = touch.location(in: self)
|
|
330
|
+
if !bounds.contains(location) {
|
|
331
|
+
handleTouchUp()
|
|
332
|
+
} else if isEnabled && !isLoading && currentState != .pressed {
|
|
333
|
+
handleTouchDown()
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
public override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
|
339
|
+
super.touchesEnded(touches, with: event)
|
|
340
|
+
handleTouchUp()
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
public override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
|
|
344
|
+
super.touchesCancelled(touches, with: event)
|
|
345
|
+
handleTouchUp()
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
// MARK: - Intrinsic Content Size
|
|
350
|
+
|
|
351
|
+
public override var intrinsicContentSize: CGSize {
|
|
352
|
+
let hasTitle = title != nil && !title!.isEmpty
|
|
353
|
+
let hasIcon = icon != nil
|
|
354
|
+
var width: CGFloat = 0
|
|
355
|
+
let height = configuration.size.height
|
|
356
|
+
let padding = configuration.size.horizontalPadding * 2
|
|
357
|
+
if hasTitle {
|
|
358
|
+
titleLabel.sizeToFit()
|
|
359
|
+
width += titleLabel.frame.width
|
|
360
|
+
}
|
|
361
|
+
if hasIcon {
|
|
362
|
+
width += configuration.size.iconSize
|
|
363
|
+
if hasTitle {
|
|
364
|
+
width += configuration.size.iconSpacing
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
width += padding
|
|
368
|
+
// 最小宽度
|
|
369
|
+
width = max(width, configuration.size.minWidth)
|
|
370
|
+
return CGSize(width: width, height: height)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
//
|
|
2
|
+
// BMComponentButtonConfiguration.swift
|
|
3
|
+
// BMComponents
|
|
4
|
+
//
|
|
5
|
+
// Created by james on 2025/12/31.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import UIKit
|
|
9
|
+
import BMTheme
|
|
10
|
+
|
|
11
|
+
// MARK: - Button Style Type 类型
|
|
12
|
+
/// 按钮变体(颜色主题)
|
|
13
|
+
public enum BMComponentButtonType {
|
|
14
|
+
/// 主色按钮(实心)
|
|
15
|
+
case primary
|
|
16
|
+
/// 次要按钮(边框)
|
|
17
|
+
case secondary
|
|
18
|
+
/// 成功按钮(绿色)
|
|
19
|
+
case green
|
|
20
|
+
/// 危险按钮(红色)
|
|
21
|
+
case red
|
|
22
|
+
/// 禁用按钮(灰色)
|
|
23
|
+
case white
|
|
24
|
+
|
|
25
|
+
/// 控件的背景色
|
|
26
|
+
var backgroundColor: BMCexThemeColor {
|
|
27
|
+
switch self {
|
|
28
|
+
case .primary: return BMCexThemeColor.brandColor
|
|
29
|
+
case .secondary: return BMCexThemeColor.brandBgColor
|
|
30
|
+
case .green: return BMCexThemeColor.buyColor
|
|
31
|
+
case .red: return BMCexThemeColor.sellColor
|
|
32
|
+
case .white: return BMCexThemeColor.bgLine2Color
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// (文字和图标)颜色
|
|
37
|
+
var foregroundColor: BMCexThemeColor {
|
|
38
|
+
switch self {
|
|
39
|
+
case .primary: return BMCexThemeColor.btnTextColor
|
|
40
|
+
case .secondary: return BMCexThemeColor.brandColor
|
|
41
|
+
case .green: return BMCexThemeColor.btnTextColor
|
|
42
|
+
case .red: return BMCexThemeColor.btnTextColor
|
|
43
|
+
case .white: return BMCexThemeColor.primaryColor
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// MARK: - Button Size
|
|
50
|
+
public enum BMComponentButtonSize {
|
|
51
|
+
|
|
52
|
+
case XLarge
|
|
53
|
+
case Large
|
|
54
|
+
case Medium
|
|
55
|
+
case Small
|
|
56
|
+
case XSmall
|
|
57
|
+
case XXSmall
|
|
58
|
+
|
|
59
|
+
var minWidth: CGFloat {
|
|
60
|
+
switch self {
|
|
61
|
+
case .XLarge: return 64
|
|
62
|
+
case .Large: return 48
|
|
63
|
+
case .Medium: return 40
|
|
64
|
+
case .Small: return 32
|
|
65
|
+
case .XSmall: return 24
|
|
66
|
+
case .XXSmall: return 20
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
var height: CGFloat {
|
|
71
|
+
switch self {
|
|
72
|
+
case .XLarge: return 56
|
|
73
|
+
case .Large: return 48
|
|
74
|
+
case .Medium: return 40
|
|
75
|
+
case .Small: return 36
|
|
76
|
+
case .XSmall: return 32
|
|
77
|
+
case .XXSmall: return 26
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
var horizontalPadding: CGFloat{
|
|
82
|
+
switch self {
|
|
83
|
+
case .XLarge: return 32
|
|
84
|
+
case .Large: return 24
|
|
85
|
+
case .Medium: return 20
|
|
86
|
+
case .Small: return 16
|
|
87
|
+
case .XSmall: return 12
|
|
88
|
+
case .XXSmall: return 10
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// 图标尺寸
|
|
93
|
+
var iconSize: CGFloat {
|
|
94
|
+
switch self {
|
|
95
|
+
case .XLarge: return 20
|
|
96
|
+
case .Large: return 20
|
|
97
|
+
case .Medium: return 16
|
|
98
|
+
case .Small: return 16
|
|
99
|
+
case .XSmall: return 14
|
|
100
|
+
case .XXSmall: return 14
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// 图标与文字间距
|
|
105
|
+
var iconSpacing: CGFloat {
|
|
106
|
+
switch self {
|
|
107
|
+
case .XLarge: return 8
|
|
108
|
+
case .Large: return 8
|
|
109
|
+
case .Medium: return 6
|
|
110
|
+
case .Small: return 6
|
|
111
|
+
case .XSmall: return 6
|
|
112
|
+
case .XXSmall: return 4
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
var sizeFont: UIFont {
|
|
117
|
+
switch self {
|
|
118
|
+
case .XLarge: return .H7Font
|
|
119
|
+
case .Large: return .S1Font
|
|
120
|
+
case .Medium: return .S4Font
|
|
121
|
+
case .Small: return .S4Font
|
|
122
|
+
case .XSmall: return .S4Font
|
|
123
|
+
case .XXSmall: return .S5Font
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// MARK: - State
|
|
130
|
+
/// 按钮状态
|
|
131
|
+
public enum BMComponentButtonState {
|
|
132
|
+
case normal
|
|
133
|
+
case pressed
|
|
134
|
+
case disable
|
|
135
|
+
case loading
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// MARK: - Icon Position
|
|
139
|
+
/// 图标位置
|
|
140
|
+
public enum BMComponentButtonIconPosition {
|
|
141
|
+
case leading // 左侧
|
|
142
|
+
case trailing // 右侧
|
|
143
|
+
case none // 无图标
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// MARK: - Button Configuration
|
|
147
|
+
/// 组件化按钮配置
|
|
148
|
+
public struct BMComponentButtonConfiguration {
|
|
149
|
+
|
|
150
|
+
/// 按钮样式变体(颜色主题)
|
|
151
|
+
public let type: BMComponentButtonType
|
|
152
|
+
|
|
153
|
+
/// 按钮尺寸
|
|
154
|
+
public let size: BMComponentButtonSize
|
|
155
|
+
|
|
156
|
+
/// 图标位置
|
|
157
|
+
public let iconPosition: BMComponentButtonIconPosition
|
|
158
|
+
|
|
159
|
+
/// 点击按钮的透明度(0.0 - 1.0)
|
|
160
|
+
public var disableAlpha: CGFloat = 0.8
|
|
161
|
+
|
|
162
|
+
/// 圆角半径
|
|
163
|
+
public var cornerRadius: CGFloat?
|
|
164
|
+
|
|
165
|
+
/// 字体
|
|
166
|
+
public var currentFont: UIFont?
|
|
167
|
+
|
|
168
|
+
// MARK: - Initialization
|
|
169
|
+
public init(type: BMComponentButtonType = .primary,size: BMComponentButtonSize = .Medium,iconPosition: BMComponentButtonIconPosition = .leading,alpha: CGFloat = 1.0,cornerRadius: CGFloat? = nil) {
|
|
170
|
+
self.type = type
|
|
171
|
+
self.size = size
|
|
172
|
+
self.iconPosition = iconPosition
|
|
173
|
+
self.disableAlpha = alpha
|
|
174
|
+
self.cornerRadius = cornerRadius
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
|