@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.
Files changed (70) hide show
  1. package/ios/DemoProject/NativeDesign/PrimaryButtonViewManager.swift +0 -1
  2. package/ios/DemoProject/NativeDesign/ReactBridge-Bridging-Header.h +12 -0
  3. package/ios/DemoProject/NativeDesign/TextButtonViewManager.swift +0 -1
  4. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/Bitmart Card.imageset/Contents.json +21 -0
  5. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/Bitmart Card.imageset/Property 1=Bitmart Card.svg +3 -0
  6. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/Contents.json +6 -0
  7. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/Credit Debit Card.imageset/Contents.json +21 -0
  8. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/Credit Debit Card.imageset/Property 1=Credit Debit Card.svg +3 -0
  9. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/Crypto Prepaid Card.imageset/Contents.json +21 -0
  10. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/Crypto Prepaid Card.imageset/Property 1=Crypto Prepaid Card.svg +3 -0
  11. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/Mobile Recharge.imageset/Contents.json +21 -0
  12. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/Mobile Recharge.imageset/Mobile Recharge.svg +3 -0
  13. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/P2P Trading.imageset/Contents.json +21 -0
  14. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/P2P Trading.imageset/Property 1=P2P Trading.svg +3 -0
  15. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/SEPA Deposit.imageset/Contents.json +21 -0
  16. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/SEPA Deposit.imageset/SEPA Deposit.svg +3 -0
  17. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/Third-Party Payment.imageset/Contents.json +21 -0
  18. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/BuyCrypto/Third-Party Payment.imageset/Property 1=Third-Party Payment.svg +3 -0
  19. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Components/Contents.json +6 -0
  20. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/Contents.json +6 -0
  21. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/checkmark.imageset/Contents.json +21 -0
  22. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/checkmark.imageset/checkmark.pdf +0 -0
  23. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/close_icon.imageset/Contents.json +22 -0
  24. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/close_icon.imageset/close_icon@2x.png +0 -0
  25. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/close_icon.imageset/close_icon@3x.png +0 -0
  26. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/cross.imageset/Contents.json +21 -0
  27. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/cross.imageset/cross.pdf +0 -0
  28. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/progress.imageset/Contents.json +21 -0
  29. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/progress.imageset/progress.pdf +0 -0
  30. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/progress_circular.imageset/Contents.json +21 -0
  31. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/progress_circular.imageset/progress_circular.pdf +0 -0
  32. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/refresh_footer_dark.imageset/Contents.json +22 -0
  33. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/refresh_footer_dark.imageset/refresh_footer_dark@2x.png +0 -0
  34. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/refresh_footer_dark.imageset/refresh_footer_dark@3x.png +0 -0
  35. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/refresh_footer_light.imageset/Contents.json +22 -0
  36. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/refresh_footer_light.imageset/refresh_footer_light@2x.png +0 -0
  37. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/refresh_footer_light.imageset/refresh_footer_light@3x.png +0 -0
  38. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/search_icon.imageset/Contents.json +21 -0
  39. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/search_icon.imageset/Group 13994.svg +7 -0
  40. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/sheet_dark_chose.imageset/Contents.json +21 -0
  41. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/sheet_dark_chose.imageset/Frame.svg +3 -0
  42. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/sheet_list_arrow.imageset/Contents.json +22 -0
  43. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/sheet_list_arrow.imageset/sheet_list_arrow@2x.png +0 -0
  44. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/sheet_list_arrow.imageset/sheet_list_arrow@3x.png +0 -0
  45. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/sheet_list_cell_checkbox.imageset/Contents.json +21 -0
  46. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/sheet_list_cell_checkbox.imageset/Frame (1).svg +3 -0
  47. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/sheet_list_chose.imageset/Contents.json +21 -0
  48. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/sheet_list_chose.imageset/Frame.png +0 -0
  49. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/slider_bubble.imageset/Contents.json +22 -0
  50. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/slider_bubble.imageset/slider_bubble@2x.png +0 -0
  51. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/slider_bubble.imageset/slider_bubble@3x.png +0 -0
  52. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/spot_second_floor_refresh_arrow.imageset/Contents.json +21 -0
  53. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Assets.xcassets/spot_second_floor_refresh_arrow.imageset/spot_second_floor_refresh_arrow.svg +8 -0
  54. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Font/Alexandria-Medium.ttf +0 -0
  55. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Font/Alexandria-Regular.ttf +0 -0
  56. package/ios/Modules/BMUIComponents/BMUIComponents/Assets/Font/Alexandria-SemiBold.ttf +0 -0
  57. package/ios/Modules/BMUIComponents/BMUIComponents/Classes/BMFont/BMFont.swift +82 -0
  58. package/ios/Modules/BMUIComponents/BMUIComponents/Classes/BMFont/UIFontExtensions.swift +120 -0
  59. package/ios/Modules/BMUIComponents/BMUIComponents/Classes/Components/AlertView/BMComponentAlertController.swift +574 -0
  60. package/ios/Modules/BMUIComponents/BMUIComponents/Classes/Components/Buttons/BMComponentButton+Examples.swift +77 -0
  61. package/ios/Modules/BMUIComponents/BMUIComponents/Classes/Components/Buttons/BMComponentButton.swift +373 -0
  62. package/ios/Modules/BMUIComponents/BMUIComponents/Classes/Components/Buttons/BMComponentButtonConfiguration.swift +181 -0
  63. package/ios/Modules/BMUIComponents/BMUIComponents/Classes/Components/Popup/BMComponentPopupController.swift +312 -0
  64. package/ios/Modules/BMUIComponents/BMUIComponents/Classes/Components/SegmentView/BMComponentSegmentedTitleCell.swift +294 -0
  65. package/ios/Modules/BMUIComponents/BMUIComponents/Classes/Components/SegmentView/BMComponentSegmentedTitleDataSource.swift +49 -0
  66. package/ios/Modules/BMUIComponents/BMUIComponents/Classes/Components/SegmentView/BMComponentSegmentedView.swift +292 -0
  67. package/ios/Modules/BMUIComponents/LICENSE +19 -0
  68. package/ios/Modules/BMUIComponents/README.md +29 -0
  69. package/package.json +3 -1
  70. package/react-native-ui-components.podspec +39 -4
@@ -0,0 +1,312 @@
1
+ //
2
+ // BMComponentPopupController.swift
3
+ // Pods
4
+ //
5
+ // Created by james on 2026/1/13.
6
+ //
7
+
8
+ import UIKit
9
+ import BMTheme
10
+ import BMCore
11
+ import SnapKit
12
+
13
+ /// 从底部弹出的组件控制器
14
+ /// 支持自定义内容视图,可选的标题和关闭按钮
15
+ //public class BMComponentPopupController: UIViewController {
16
+ //
17
+ // // MARK: - Public Properties
18
+ //
19
+ // /// 是否允许点击背景关闭弹窗,默认为 true
20
+ // public var enableClickBackgroundToDismiss: Bool = true
21
+ //
22
+ // /// 是否显示关闭按钮,默认为 true
23
+ // public var showCloseButton: Bool = true
24
+ //
25
+ // /// 是否显示标题栏,默认为 true
26
+ // public var showTitleBar: Bool = true
27
+ //
28
+ // /// 关闭回调
29
+ // public var onDismiss: (() -> Void)?
30
+ //
31
+ // // MARK: - Private Properties
32
+ //
33
+ // private var presentTransitionDelegate: BMPresentTransitionDelgate?
34
+ // private var customContentView: UIView?
35
+ // private var contentHeight: CGFloat = BMSize.screenHeight * 0.6
36
+ // private var hasCalledDismissCallback: Bool = false
37
+ // private var presentTransition: BMPresentTransition?
38
+ // private var hasUpdatedHeight: Bool = false // 标记是否已更新过高度
39
+ //
40
+ // // MARK: - Constants
41
+ //
42
+ // /// 标题栏高度(包括顶部间距和标题与内容的间距)
43
+ // private var titleBarHeight: CGFloat {
44
+ // return showTitleBar ? (24 + 26 + 20) : 20 // 顶部间距24 + 标题栏26 + 标题和内容间距20,或者只有顶部间距20
45
+ // }
46
+ //
47
+ // /// 底部间距(包括底部内边距和 safeArea)
48
+ // private func bottomPadding() -> CGFloat {
49
+ // return 20 + (view.safeAreaInsets.bottom > 0 ? view.safeAreaInsets.bottom : 34) // 底部间距20 + safeArea底部间距(默认34)
50
+ // }
51
+ //
52
+ // /// 计算后的整体弹窗高度
53
+ // private func calculateControllerHeight() -> CGFloat {
54
+ // return contentHeight + titleBarHeight + bottomPadding()
55
+ // }
56
+ //
57
+ // // MARK: - Initializers
58
+ //
59
+ // /// 使用自定义视图初始化
60
+ // /// - Parameters:
61
+ // /// - title: 标题,如果为 nil 则不显示标题栏
62
+ // /// - contentView: 自定义内容视图(可以直接传入,不需要使用 contentBuilder)
63
+ // /// - height: 内容区域的高度,控制器会自动添加标题栏和底部间距的高度。默认为屏幕高度的 60%(减去标题和底部间距)
64
+ // /// - showCloseButton: 是否显示关闭按钮
65
+ // /// - enableClickBackgroundToDismiss: 是否允许点击背景关闭
66
+ // /// - onDismiss: 关闭回调
67
+ // public init(title: String? = nil,
68
+ // contentView: UIView,
69
+ // height: CGFloat? = nil,
70
+ // showCloseButton: Bool = true,
71
+ // enableClickBackgroundToDismiss: Bool = true,
72
+ // onDismiss: (() -> Void)? = nil) {
73
+ // super.init(nibName: nil, bundle: nil)
74
+ //
75
+ // self.customContentView = contentView
76
+ // self.showCloseButton = showCloseButton
77
+ // self.enableClickBackgroundToDismiss = enableClickBackgroundToDismiss
78
+ // self.onDismiss = onDismiss
79
+ // self.showTitleBar = title != nil
80
+ //
81
+ // // 估算标题栏和底部间距的高度(用于初始计算)
82
+ // let estimatedTitleBarHeight: CGFloat = title != nil ? (24 + 26 + 20) : 20
83
+ // let estimatedBottomPadding: CGFloat = 20 + BMSize.bottomSafeAreaHeight
84
+ //
85
+ // if let height = height {
86
+ // self.contentHeight = height
87
+ // } else {
88
+ // // 默认高度:屏幕高度的60%,但需要减去标题和底部间距
89
+ // let defaultTotalHeight = BMSize.screenHeight * 0.6
90
+ // self.contentHeight = defaultTotalHeight - estimatedTitleBarHeight - estimatedBottomPadding
91
+ // }
92
+ //
93
+ // // 限制最大高度:如果整体高度超过屏幕高度的80%,则限制内容高度
94
+ // let maxTotalHeight = BMSize.screenHeight * 0.8
95
+ // let estimatedTotalHeight = self.contentHeight + estimatedTitleBarHeight + estimatedBottomPadding
96
+ // if estimatedTotalHeight > maxTotalHeight {
97
+ // // 限制内容高度 = 最大总高度 - 标题栏高度 - 底部间距
98
+ // self.contentHeight = maxTotalHeight - estimatedTitleBarHeight - estimatedBottomPadding
99
+ // }
100
+ //
101
+ // if let title = title {
102
+ // self.titleLabel.text = title
103
+ // }
104
+ //
105
+ // setupPresentation()
106
+ // setupUI()
107
+ // }
108
+ //
109
+ //
110
+ // required init?(coder: NSCoder) {
111
+ // fatalError("init(coder:) has not been implemented")
112
+ // }
113
+ //
114
+ // deinit {
115
+ // debugPrint("\(type(of: self)) deinit")
116
+ // }
117
+ //
118
+ // // MARK: - Setup Methods
119
+ //
120
+ // private func setupPresentation() {
121
+ // // 使用估算值初始化,实际高度会在 viewDidLayoutSubviews 中更新
122
+ // let estimatedHeight = contentHeight + titleBarHeight + 20 + BMSize.bottomSafeAreaHeight // 使用估算的 safeArea 底部间距
123
+ // presentTransitionDelegate = BMPresentTransitionDelgate(controllerHeight: estimatedHeight)
124
+ // modalPresentationStyle = .custom
125
+ // transitioningDelegate = presentTransitionDelegate
126
+ // }
127
+ //
128
+ // private func setupUI() {
129
+ // view.backgroundColor = .clear
130
+ //
131
+ // // 添加背景按钮
132
+ // view.addSubview(backgroundButton)
133
+ // backgroundButton.snp.makeConstraints { make in
134
+ // make.edges.equalToSuperview()
135
+ // }
136
+ //
137
+ // // 添加容器视图
138
+ // view.addSubview(containerView)
139
+ // containerView.snp.makeConstraints { make in
140
+ // make.leading.trailing.bottom.equalToSuperview()
141
+ // }
142
+ //
143
+ // // 添加标题栏(如果显示)
144
+ // if showTitleBar {
145
+ // containerView.addSubview(titleContainerView)
146
+ // titleContainerView.addSubview(titleLabel)
147
+ //
148
+ // if showCloseButton {
149
+ // titleContainerView.addSubview(closeButton)
150
+ // }
151
+ //
152
+ // titleContainerView.snp.makeConstraints { make in
153
+ // make.top.equalToSuperview().offset(24)
154
+ // make.leading.trailing.equalToSuperview()
155
+ // make.height.equalTo(26)
156
+ // }
157
+ //
158
+ // titleLabel.snp.makeConstraints { make in
159
+ // make.leading.equalTo(16)
160
+ // make.centerY.equalToSuperview()
161
+ // }
162
+ //
163
+ // if showCloseButton {
164
+ // closeButton.snp.makeConstraints { make in
165
+ // make.trailing.equalTo(-16)
166
+ // make.centerY.equalToSuperview()
167
+ // make.size.equalTo(CGSize(width: 20, height: 20))
168
+ // }
169
+ // }
170
+ // }
171
+ //
172
+ // // 添加内容视图
173
+ // if let contentView = customContentView {
174
+ // containerView.addSubview(contentView)
175
+ //
176
+ // if showTitleBar {
177
+ // contentView.snp.makeConstraints { make in
178
+ // make.top.equalTo(titleContainerView.snp.bottom).offset(20)
179
+ // make.leading.trailing.equalToSuperview()
180
+ // make.height.equalTo(contentHeight) // 使用指定的内容高度
181
+ // }
182
+ // } else {
183
+ // contentView.snp.makeConstraints { make in
184
+ // make.top.equalToSuperview().offset(20)
185
+ // make.leading.trailing.equalToSuperview()
186
+ // make.height.equalTo(contentHeight) // 使用指定的内容高度
187
+ // }
188
+ // }
189
+ // }
190
+ //
191
+ // // 设置容器视图的高度约束(使用估算值,会在 viewDidLayoutSubviews 中更新)
192
+ // containerView.snp.makeConstraints { make in
193
+ // make.top.equalToSuperview()
194
+ // make.height.equalTo(contentHeight + titleBarHeight + 20 + BMSize.bottomSafeAreaHeight)
195
+ // }
196
+ // }
197
+ //
198
+ // // MARK: - Actions
199
+ //
200
+ // @objc private func backgroundButtonTapped() {
201
+ // if enableClickBackgroundToDismiss {
202
+ // dismissPopup()
203
+ // }
204
+ // }
205
+ //
206
+ // @objc private func closeButtonTapped() {
207
+ // dismissPopup()
208
+ // }
209
+ //
210
+ // /// 关闭弹窗
211
+ // public func dismissPopup() {
212
+ // guard !hasCalledDismissCallback else { return }
213
+ // hasCalledDismissCallback = true
214
+ // dismiss(animated: true) { [weak self] in
215
+ // self?.onDismiss?()
216
+ // }
217
+ // }
218
+ //
219
+ // // MARK: - Override Methods
220
+ //
221
+ // public override func viewDidLayoutSubviews() {
222
+ // super.viewDidLayoutSubviews()
223
+ // // 设置顶部圆角
224
+ // containerView.bmCornerRadius(corners: [.topLeft, .topRight], radius: 16)
225
+ //
226
+ // // 只在第一次布局时更新高度,避免重复更新导致崩溃
227
+ // guard !hasUpdatedHeight else { return }
228
+ //
229
+ // // 计算实际高度(使用实际的 safeAreaInsets)
230
+ // var actualHeight = calculateControllerHeight()
231
+ //
232
+ // // 再次检查最大高度限制(使用实际的 bottomPadding)
233
+ // let maxTotalHeight = BMSize.screenHeight * 0.8
234
+ // if actualHeight > maxTotalHeight {
235
+ // // 如果超过限制,重新计算内容高度
236
+ // let actualTitleBarHeight = titleBarHeight
237
+ // let actualBottomPadding = bottomPadding()
238
+ // let maxContentHeight = maxTotalHeight - actualTitleBarHeight - actualBottomPadding
239
+ // // 更新内容视图的高度约束
240
+ // if let contentView = customContentView {
241
+ // contentView.snp.updateConstraints { make in
242
+ // make.height.equalTo(maxContentHeight)
243
+ // }
244
+ // }
245
+ // // 更新实际高度
246
+ // actualHeight = maxTotalHeight
247
+ // // 更新 contentHeight 以便后续使用
248
+ // self.contentHeight = maxContentHeight
249
+ // }
250
+ //
251
+ // // 更新 BMPresentTransition 的高度
252
+ // if presentTransition == nil {
253
+ // // 通过 presentationController 属性获取
254
+ // presentTransition = presentationController as? BMPresentTransition
255
+ // }
256
+ //
257
+ // presentTransition?.controllerHeight = actualHeight
258
+ //
259
+ // // 更新容器视图的高度约束(使用 updateConstraints 方法)
260
+ // containerView.snp.updateConstraints { make in
261
+ // make.height.equalTo(actualHeight)
262
+ // }
263
+ //
264
+ // hasUpdatedHeight = true
265
+ // }
266
+ //
267
+ // public override func viewWillDisappear(_ animated: Bool) {
268
+ // super.viewWillDisappear(animated)
269
+ // // 处理通过背景点击或其他方式关闭的情况(如 BMPresentTransition 的 blackView 点击)
270
+ // if isBeingDismissed && !hasCalledDismissCallback {
271
+ // hasCalledDismissCallback = true
272
+ // onDismiss?()
273
+ // }
274
+ // }
275
+ //
276
+ // // MARK: - Lazy Properties
277
+ //
278
+ // private lazy var backgroundButton: UIButton = {
279
+ // let button = UIButton(type: .custom)
280
+ // button.backgroundColor = .clear
281
+ // button.addTarget(self, action: #selector(backgroundButtonTapped), for: .touchUpInside)
282
+ // return button
283
+ // }()
284
+ //
285
+ // private lazy var containerView: UIView = {
286
+ // let view = UIView()
287
+ // view.theme.backgroundColor = .bgColor
288
+ // return view
289
+ // }()
290
+ //
291
+ // private lazy var titleContainerView: UIView = {
292
+ // let view = UIView()
293
+ // view.theme.backgroundColor = .bgColor
294
+ // return view
295
+ // }()
296
+ //
297
+ // private lazy var titleLabel: UILabel = {
298
+ // let label = UILabel()
299
+ // label.font = UIFont.H6Font
300
+ // label.cexTheme.textColor = .primaryColor
301
+ // return label
302
+ // }()
303
+ //
304
+ // private lazy var closeButton: UIButton = {
305
+ // let button = UIButton(type: .custom)
306
+ // button.setImage(UIImage.iconFont(with: .app_icon_close, size: CGSize(width: 20, height: 20)).withRenderingMode(.alwaysTemplate), for: .normal)
307
+ // button.cexTheme.tintColor = .secondaryColor
308
+ // button.addTarget(self, action: #selector(closeButtonTapped), for: .touchUpInside)
309
+ // button.expandSize(edge: UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8))
310
+ // return button
311
+ // }()
312
+ //}
@@ -0,0 +1,294 @@
1
+ //
2
+ // BMComponentSegmentedTitleCell.swift
3
+ // BMComponents
4
+ //
5
+ // Created by james on 2026/1/13.
6
+ //
7
+
8
+ import UIKit
9
+ import SnapKit
10
+
11
+ //open class BMComponentSegmentedTitleCell: JXSegmentedTitleCell {
12
+ // // MARK: - UI Components
13
+ // private let iconImageView = UIImageView()
14
+ // private let badgeContainer = UIView()
15
+ // private let badgeLabel = UILabel()
16
+ // private let badgeDot = UIView()
17
+ //
18
+ // // MARK: - Lifecycle
19
+ // open override func commonInit() {
20
+ // super.commonInit()
21
+ // // 图标
22
+ // iconImageView.contentMode = .scaleAspectFit
23
+ // contentView.addSubview(iconImageView)
24
+ // // 徽章容器
25
+ // badgeContainer.isHidden = true
26
+ // contentView.addSubview(badgeContainer)
27
+ // // 文字徽章
28
+ // badgeLabel.textAlignment = .center
29
+ // badgeLabel.font = UIFont.systemFont(ofSize: 10, weight: .medium)
30
+ // badgeLabel.textColor = .white
31
+ // badgeLabel.backgroundColor = UIColor(red: 0.2, green: 0.6, blue: 1.0, alpha: 1.0)
32
+ // badgeLabel.layer.cornerRadius = 6 // 减小圆角,使其更紧凑
33
+ // badgeLabel.clipsToBounds = true
34
+ // badgeLabel.isHidden = true
35
+ // badgeContainer.addSubview(badgeLabel)
36
+ //
37
+ // // 红点徽章
38
+ // badgeDot.backgroundColor = .red
39
+ // badgeDot.clipsToBounds = true
40
+ // badgeDot.isHidden = true
41
+ // badgeContainer.addSubview(badgeDot)
42
+ // }
43
+ //
44
+ // open override func layoutSubviews() {
45
+ // // 不调用 super.layoutSubviews(),完全自定义布局
46
+ // guard let itemModel = itemModel as? BMComponentSegmentedTitleItemModel else {
47
+ // super.layoutSubviews()
48
+ // return
49
+ // }
50
+ //
51
+ // let iconSize: CGFloat = 16
52
+ // let iconSpacing: CGFloat = 4
53
+ // let badgeSpacing: CGFloat = 1 // badge 与文字/图标的间距,最小间距使其紧贴
54
+ // let contentCenterX = contentView.bounds.width / 2
55
+ // let contentCenterY = contentView.bounds.height / 2
56
+ //
57
+ // // 计算标题尺寸
58
+ // let titleSize = titleLabel.sizeThatFits(contentView.bounds.size)
59
+ // titleLabel.bounds = CGRect(x: 0, y: 0, width: titleSize.width, height: titleSize.height)
60
+ //
61
+ // // 计算 badge 尺寸
62
+ // let badgeInfo = calculateBadgeInfo(itemModel: itemModel)
63
+ //
64
+ // // 图标布局
65
+ // if let icon = itemModel.icon {
66
+ // iconImageView.isHidden = false
67
+ // iconImageView.image = icon
68
+ // iconImageView.tintColor = titleLabel.textColor
69
+ // iconImageView.frame = CGRect(x: 0, y: 0, width: iconSize, height: iconSize)
70
+ // switch itemModel.iconPosition {
71
+ // case .trailing:
72
+ // // 图标在文字右边:文字 | 图标 | 徽章(text badge)或 文字右上角(dot badge)
73
+ // layoutHorizontalWithIcon(
74
+ // titleSize: titleSize,
75
+ // iconSize: iconSize,
76
+ // iconSpacing: iconSpacing,
77
+ // badgeSpacing: badgeSpacing,
78
+ // badgeInfo: badgeInfo,
79
+ // centerX: contentCenterX,
80
+ // centerY: contentCenterY,
81
+ // itemModel: itemModel
82
+ // )
83
+ // case .top:
84
+ // // 图标在文字上面:图标在上,文字在下,徽章在文字右边(text)或右上角(dot)
85
+ // layoutVerticalWithIcon(
86
+ // titleSize: titleSize,
87
+ // iconSize: iconSize,
88
+ // iconSpacing: iconSpacing,
89
+ // badgeSpacing: badgeSpacing,
90
+ // badgeInfo: badgeInfo,
91
+ // centerX: contentCenterX,
92
+ // centerY: contentCenterY,
93
+ // itemModel: itemModel
94
+ // )
95
+ // }
96
+ // } else {
97
+ // iconImageView.isHidden = true
98
+ // // 没有图标时,只有文字和徽章
99
+ // layoutHorizontalWithoutIcon(
100
+ // titleSize: titleSize,
101
+ // badgeSpacing: badgeSpacing,
102
+ // badgeInfo: badgeInfo,
103
+ // centerX: contentCenterX,
104
+ // centerY: contentCenterY,
105
+ // itemModel: itemModel
106
+ // )
107
+ // }
108
+ //
109
+ // // 更新 maskTitleLabel 位置(用于遮罩效果)
110
+ // maskTitleLabel.bounds = titleLabel.bounds
111
+ // maskTitleLabel.center = titleLabel.center
112
+ // }
113
+ //
114
+ // private func layoutHorizontalWithIcon(
115
+ // titleSize: CGSize,
116
+ // iconSize: CGFloat,
117
+ // iconSpacing: CGFloat,
118
+ // badgeSpacing: CGFloat,
119
+ // badgeInfo: (width: CGFloat, height: CGFloat, isText: Bool),
120
+ // centerX: CGFloat,
121
+ // centerY: CGFloat,
122
+ // itemModel: BMComponentSegmentedTitleItemModel
123
+ // ) {
124
+ // // 计算总宽度(不包括 dot badge,因为它在右上角)
125
+ // var totalWidth = titleSize.width + iconSize + iconSpacing
126
+ // if badgeInfo.isText {
127
+ // totalWidth += badgeInfo.width + badgeSpacing
128
+ // }
129
+ //
130
+ // var currentX = centerX - totalWidth / 2
131
+ //
132
+ // // 标题
133
+ // titleLabel.center = CGPoint(x: currentX + titleSize.width / 2, y: centerY)
134
+ // currentX += titleSize.width + iconSpacing
135
+ //
136
+ // // 图标
137
+ // iconImageView.center = CGPoint(x: currentX + iconSize / 2, y: centerY)
138
+ // currentX += iconSize
139
+ //
140
+ // // 徽章
141
+ // if badgeInfo.isText {
142
+ // // text badge 紧接在图标后面
143
+ // currentX += badgeSpacing
144
+ // layoutTextBadge(at: currentX + badgeInfo.width / 2, centerY: centerY, itemModel: itemModel, width: badgeInfo.width, height: badgeInfo.height)
145
+ // } else if itemModel.badge != nil {
146
+ // // dot badge 在文字右上角
147
+ // let textRightX = titleLabel.center.x + titleSize.width / 2
148
+ // let textTopY = titleLabel.center.y - titleSize.height / 2
149
+ // layoutDotBadge(at: textRightX, topY: textTopY, itemModel: itemModel, size: badgeInfo.width)
150
+ // }
151
+ // }
152
+ //
153
+ // private func layoutVerticalWithIcon(
154
+ // titleSize: CGSize,
155
+ // iconSize: CGFloat,
156
+ // iconSpacing: CGFloat,
157
+ // badgeSpacing: CGFloat,
158
+ // badgeInfo: (width: CGFloat, height: CGFloat, isText: Bool),
159
+ // centerX: CGFloat,
160
+ // centerY: CGFloat,
161
+ // itemModel: BMComponentSegmentedTitleItemModel
162
+ // ) {
163
+ // let totalHeight = iconSize + iconSpacing + titleSize.height
164
+ // let startY = centerY - totalHeight / 2
165
+ //
166
+ // // 图标(居中)
167
+ // iconImageView.center = CGPoint(x: centerX, y: startY + iconSize / 2)
168
+ //
169
+ // // 标题(在图标下方)
170
+ // titleLabel.center = CGPoint(x: centerX, y: startY + iconSize + iconSpacing + titleSize.height / 2)
171
+ //
172
+ // // 徽章
173
+ // if badgeInfo.isText {
174
+ // // text badge 在文字右边
175
+ // let badgeX = centerX + titleSize.width / 2 + badgeSpacing
176
+ // layoutTextBadge(at: badgeX + badgeInfo.width / 2, centerY: titleLabel.center.y, itemModel: itemModel, width: badgeInfo.width, height: badgeInfo.height)
177
+ // } else if itemModel.badge != nil {
178
+ // // dot badge 在文字右上角
179
+ // let textRightX = titleLabel.center.x + titleSize.width / 2
180
+ // let textTopY = titleLabel.center.y - titleSize.height / 2
181
+ // layoutDotBadge(at: textRightX, topY: textTopY, itemModel: itemModel, size: badgeInfo.width)
182
+ // }
183
+ // }
184
+ //
185
+ // private func layoutHorizontalWithoutIcon(
186
+ // titleSize: CGSize,
187
+ // badgeSpacing: CGFloat,
188
+ // badgeInfo: (width: CGFloat, height: CGFloat, isText: Bool),
189
+ // centerX: CGFloat,
190
+ // centerY: CGFloat,
191
+ // itemModel: BMComponentSegmentedTitleItemModel) {
192
+ // // 计算总宽度(不包括 dot badge)
193
+ // var totalWidth = titleSize.width
194
+ // if badgeInfo.isText {
195
+ // totalWidth += badgeInfo.width + badgeSpacing
196
+ // }
197
+ //
198
+ // var currentX = centerX - totalWidth / 2
199
+ //
200
+ // // 标题
201
+ // titleLabel.center = CGPoint(x: currentX + titleSize.width / 2, y: centerY)
202
+ // currentX += titleSize.width
203
+ //
204
+ // // 徽章
205
+ // if badgeInfo.isText {
206
+ // // text badge 紧接在文字后面
207
+ // currentX += badgeSpacing
208
+ // layoutTextBadge(at: currentX + badgeInfo.width / 2, centerY: centerY, itemModel: itemModel, width: badgeInfo.width, height: badgeInfo.height)
209
+ // } else if itemModel.badge != nil {
210
+ // // dot badge 在文字右上角
211
+ // let textRightX = titleLabel.center.x + titleSize.width / 2
212
+ // let textTopY = titleLabel.center.y - titleSize.height / 2
213
+ // layoutDotBadge(at: textRightX, topY: textTopY, itemModel: itemModel, size: badgeInfo.width)
214
+ // }
215
+ // }
216
+ //
217
+ // private func calculateBadgeInfo(itemModel: BMComponentSegmentedTitleItemModel) -> (width: CGFloat, height: CGFloat, isText: Bool) {
218
+ // guard let badge = itemModel.badge else {
219
+ // return (0, 0, false)
220
+ // }
221
+ //
222
+ // switch badge {
223
+ // case .text(let text, _):
224
+ // let tempLabel = UILabel()
225
+ // tempLabel.text = text
226
+ // tempLabel.font = UIFont.systemFont(ofSize: 10, weight: .medium)
227
+ // let size = tempLabel.sizeThatFits(CGSize(width: 100, height: 14))
228
+ // // 减小内边距,使 badge 更紧凑:左右各3pt,总高度14pt
229
+ // return (max(size.width + 6, 16), 14, true)
230
+ // case .dot(_, let size):
231
+ // return (max(size, 8), max(size, 8), false)
232
+ // }
233
+ // }
234
+ //
235
+ // private func layoutTextBadge(at centerX: CGFloat, centerY: CGFloat, itemModel: BMComponentSegmentedTitleItemModel, width: CGFloat, height: CGFloat) {
236
+ // guard case .text(let text, let bgColor) = itemModel.badge else {
237
+ // badgeContainer.isHidden = true
238
+ // return
239
+ // }
240
+ //
241
+ // badgeContainer.isHidden = false
242
+ // badgeLabel.isHidden = false
243
+ // badgeDot.isHidden = true
244
+ //
245
+ // badgeLabel.text = text
246
+ // badgeLabel.backgroundColor = bgColor
247
+ // badgeLabel.layer.cornerRadius = 6 // 与初始化时保持一致
248
+ // badgeLabel.clipsToBounds = true
249
+ //
250
+ // badgeLabel.frame = CGRect(x: 0, y: 0, width: width, height: height)
251
+ // // text badge 与文字垂直居中对齐,稍微向上偏移使其看起来更自然
252
+ // badgeLabel.center = CGPoint(x: centerX, y: centerY - 1)
253
+ // badgeContainer.frame = badgeLabel.frame
254
+ // }
255
+ //
256
+ // private func layoutDotBadge(at rightX: CGFloat, topY: CGFloat, itemModel: BMComponentSegmentedTitleItemModel, size: CGFloat) {
257
+ // guard case .dot(let color, _) = itemModel.badge else {
258
+ // badgeContainer.isHidden = true
259
+ // return
260
+ // }
261
+ //
262
+ // badgeContainer.isHidden = false
263
+ // badgeLabel.isHidden = true
264
+ // badgeDot.isHidden = false
265
+ //
266
+ // badgeDot.backgroundColor = color
267
+ // badgeDot.clipsToBounds = true
268
+ // badgeDot.layer.cornerRadius = size / 2
269
+ //
270
+ // // dot 在文字右上角,位置更靠近文字
271
+ // // 计算位置:文字右边缘稍微向左,文字顶部稍微向下
272
+ // let offsetX: CGFloat = -size / 3 // 向左偏移,使 dot 部分覆盖文字右边缘
273
+ // let offsetY: CGFloat = size / 3 // 向下偏移,使 dot 在文字顶部附近
274
+ //
275
+ // badgeDot.frame = CGRect(x: 0, y: 0, width: size, height: size)
276
+ // badgeDot.center = CGPoint(x: rightX + offsetX, y: topY + offsetY)
277
+ // badgeContainer.frame = badgeDot.frame
278
+ // }
279
+ //
280
+ // open override func reloadData(itemModel: JXSegmentedBaseItemModel, selectedType: JXSegmentedViewItemSelectedType) {
281
+ // super.reloadData(itemModel: itemModel, selectedType: selectedType)
282
+ //
283
+ // guard let myItemModel = itemModel as? BMComponentSegmentedTitleItemModel else {
284
+ // return
285
+ // }
286
+ //
287
+ // // 更新图标颜色以匹配文字颜色
288
+ // if let icon = myItemModel.icon {
289
+ // iconImageView.image = icon.withTintColor(titleLabel.textColor ?? .black)
290
+ // }
291
+ //
292
+ // setNeedsLayout()
293
+ // }
294
+ //}
@@ -0,0 +1,49 @@
1
+ //
2
+ // BMComponentSegmentedTitleDataSource.swift
3
+ // BMComponents
4
+ //
5
+ // Created by james on 2026/1/13.
6
+ //
7
+
8
+ import UIKit
9
+
10
+ // MARK: - Item Model
11
+ //open class BMComponentSegmentedTitleItemModel: JXSegmentedTitleItemModel {
12
+ // open var icon: UIImage?
13
+ // open var badge: BMComponentSegmentedBadge?
14
+ // open var iconPosition: BMComponentSegmentedIconPosition = .trailing
15
+ //}
16
+ //
17
+ //// MARK: - Data Source
18
+ //open class BMComponentSegmentedTitleDataSource: JXSegmentedTitleDataSource {
19
+ // /// 图标数组(可选,与 titles 数量一致)
20
+ // open var icons: [UIImage?]?
21
+ // /// 图标位置数组(可选,与 titles 数量一致)
22
+ // open var iconPositions: [BMComponentSegmentedIconPosition]?
23
+ // /// 徽章数组(可选,与 titles 数量一致)
24
+ // open var badges: [BMComponentSegmentedBadge?]?
25
+ //
26
+ // open override func preferredItemModelInstance() -> JXSegmentedBaseItemModel {
27
+ // return BMComponentSegmentedTitleItemModel()
28
+ // }
29
+ //
30
+ // open override func preferredRefreshItemModel(_ itemModel: JXSegmentedBaseItemModel, at index: Int, selectedIndex: Int) {
31
+ // super.preferredRefreshItemModel(itemModel, at: index, selectedIndex: selectedIndex)
32
+ // guard let myItemModel = itemModel as? BMComponentSegmentedTitleItemModel else {
33
+ // return
34
+ // }
35
+ // myItemModel.icon = icons?[index]
36
+ // myItemModel.iconPosition = iconPositions?[index] ?? .trailing
37
+ // myItemModel.badge = badges?[index]
38
+ // }
39
+ //
40
+ // // MARK: - JXSegmentedViewDataSource
41
+ // open override func registerCellClass(in segmentedView: JXSegmentedView) {
42
+ // segmentedView.collectionView.register(BMComponentSegmentedTitleCell.self, forCellWithReuseIdentifier: "cell")
43
+ // }
44
+ //
45
+ // open override func segmentedView(_ segmentedView: JXSegmentedView, cellForItemAt index: Int) -> JXSegmentedBaseCell {
46
+ // let cell = segmentedView.dequeueReusableCell(withReuseIdentifier: "cell", at: index)
47
+ // return cell
48
+ // }
49
+ //}