@onekeyfe/react-native-tab-view 1.1.43 → 1.1.44
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/RCTTabViewContainerView.swift +18 -103
- package/package.json +1 -1
|
@@ -50,15 +50,6 @@ struct IconSource {
|
|
|
50
50
|
|
|
51
51
|
class RCTTabViewContainerView: UIView {
|
|
52
52
|
|
|
53
|
-
// MARK: - Debug logging
|
|
54
|
-
|
|
55
|
-
private static let debugLog = true
|
|
56
|
-
|
|
57
|
-
private func log(_ message: String, function: String = #function) {
|
|
58
|
-
guard Self.debugLog else { return }
|
|
59
|
-
RCTTabViewLog.debug("RCTTabView", message: "[\(function)] \(message)")
|
|
60
|
-
}
|
|
61
|
-
|
|
62
53
|
// MARK: - Internal State
|
|
63
54
|
|
|
64
55
|
private var tabBarController: UITabBarController?
|
|
@@ -68,26 +59,17 @@ class RCTTabViewContainerView: UIView {
|
|
|
68
59
|
private var imageLoader: RCTImageLoaderProtocol?
|
|
69
60
|
private let iconSize = CGSize(width: 27, height: 27)
|
|
70
61
|
private var longPressHandler: LongPressGestureHandler?
|
|
71
|
-
private var tabBarHiddenObservation: NSKeyValueObservation?
|
|
72
|
-
private var tabBarAlphaObservation: NSKeyValueObservation?
|
|
73
|
-
private var tabBarFrameObservation: NSKeyValueObservation?
|
|
74
62
|
/// Cached view controllers keyed by tab key to avoid unnecessary reparenting
|
|
75
63
|
private var cachedViewControllers: [String: UIViewController] = [:]
|
|
76
64
|
|
|
77
65
|
// MARK: - Props (set by Fabric ComponentView via KVC)
|
|
78
66
|
|
|
79
67
|
@objc var items: [NSDictionary]? {
|
|
80
|
-
didSet {
|
|
81
|
-
log("items prop changed: \(oldValue?.count ?? 0) -> \(items?.count ?? 0) items")
|
|
82
|
-
rebuildViewControllers()
|
|
83
|
-
}
|
|
68
|
+
didSet { rebuildViewControllers() }
|
|
84
69
|
}
|
|
85
70
|
|
|
86
71
|
@objc var selectedPage: String? {
|
|
87
|
-
didSet {
|
|
88
|
-
log("selectedPage prop changed: \(oldValue ?? "nil") -> \(selectedPage ?? "nil")")
|
|
89
|
-
updateSelectedTab()
|
|
90
|
-
}
|
|
72
|
+
didSet { updateSelectedTab() }
|
|
91
73
|
}
|
|
92
74
|
|
|
93
75
|
@objc var icons: [NSDictionary]? {
|
|
@@ -115,10 +97,7 @@ class RCTTabViewContainerView: UIView {
|
|
|
115
97
|
}
|
|
116
98
|
|
|
117
99
|
@objc var tabBarHidden: Bool = false {
|
|
118
|
-
didSet {
|
|
119
|
-
log("tabBarHidden prop changed: \(oldValue) -> \(tabBarHidden)")
|
|
120
|
-
updateTabBarHidden()
|
|
121
|
-
}
|
|
100
|
+
didSet { updateTabBarHidden() }
|
|
122
101
|
}
|
|
123
102
|
|
|
124
103
|
@objc var translucent: NSNumber? {
|
|
@@ -181,9 +160,6 @@ class RCTTabViewContainerView: UIView {
|
|
|
181
160
|
|
|
182
161
|
override func layoutSubviews() {
|
|
183
162
|
super.layoutSubviews()
|
|
184
|
-
if let tbc = tabBarController {
|
|
185
|
-
log("layoutSubviews: bounds=\(bounds), tabBar.isHidden=\(tbc.tabBar.isHidden), tabBar.frame=\(tbc.tabBar.frame), tabBar.alpha=\(tbc.tabBar.alpha)")
|
|
186
|
-
}
|
|
187
163
|
handleLayout()
|
|
188
164
|
}
|
|
189
165
|
|
|
@@ -205,26 +181,12 @@ class RCTTabViewContainerView: UIView {
|
|
|
205
181
|
|
|
206
182
|
setupTabBarControllerIfNeeded()
|
|
207
183
|
tabBarController?.view.frame = bounds
|
|
208
|
-
|
|
209
|
-
// Log container's direct subviews
|
|
210
|
-
if let tbc = tabBarController {
|
|
211
|
-
let strayCount = self.subviews.filter { sv in sv !== tbc.view && childViews.contains(where: { $0 === sv }) }.count
|
|
212
|
-
if strayCount > 0 {
|
|
213
|
-
log("⚠️ handleLayout: \(strayCount) stray childViews found as direct subviews of self!")
|
|
214
|
-
}
|
|
215
|
-
log("handleLayout: self.subviews=\(self.subviews.count), strayChildren=\(strayCount)")
|
|
216
|
-
}
|
|
217
|
-
|
|
218
184
|
onNativeLayout?(["width": Double(bounds.width), "height": Double(bounds.height)])
|
|
219
185
|
}
|
|
220
186
|
|
|
221
187
|
private func setupTabBarControllerIfNeeded() {
|
|
222
188
|
guard tabBarController == nil else { return }
|
|
223
|
-
guard let parentVC = self.reactViewController() else {
|
|
224
|
-
log("setupTabBarControllerIfNeeded: no parent view controller found")
|
|
225
|
-
return
|
|
226
|
-
}
|
|
227
|
-
log("setupTabBarControllerIfNeeded: creating UITabBarController, parentVC=\(type(of: parentVC))")
|
|
189
|
+
guard let parentVC = self.reactViewController() else { return }
|
|
228
190
|
|
|
229
191
|
let tbc = UITabBarController()
|
|
230
192
|
tbc.delegate = TabViewDelegateProxy.shared
|
|
@@ -239,29 +201,6 @@ class RCTTabViewContainerView: UIView {
|
|
|
239
201
|
|
|
240
202
|
self.tabBarController = tbc
|
|
241
203
|
|
|
242
|
-
// KVO observers to detect external changes to tab bar visibility
|
|
243
|
-
if Self.debugLog {
|
|
244
|
-
tabBarHiddenObservation = tbc.tabBar.observe(\.isHidden, options: [.old, .new]) { [weak self] tabBar, change in
|
|
245
|
-
self?.log("KVO tabBar.isHidden changed: \(change.oldValue ?? false) -> \(change.newValue ?? false), frame=\(tabBar.frame)")
|
|
246
|
-
if change.newValue == true && self?.tabBarHidden == false {
|
|
247
|
-
self?.log("⚠️ TAB BAR WAS HIDDEN EXTERNALLY! tabBarHidden prop is false but isHidden became true")
|
|
248
|
-
Thread.callStackSymbols.prefix(15).forEach { self?.log(" \($0)") }
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
tabBarAlphaObservation = tbc.tabBar.observe(\.alpha, options: [.old, .new]) { [weak self] tabBar, change in
|
|
252
|
-
if change.oldValue != change.newValue {
|
|
253
|
-
self?.log("KVO tabBar.alpha changed: \(change.oldValue ?? 0) -> \(change.newValue ?? 0), frame=\(tabBar.frame)")
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
tabBarFrameObservation = tbc.tabBar.observe(\.frame, options: [.old, .new]) { [weak self] tabBar, change in
|
|
257
|
-
let oldFrame = change.oldValue ?? .zero
|
|
258
|
-
let newFrame = change.newValue ?? .zero
|
|
259
|
-
if oldFrame != newFrame {
|
|
260
|
-
self?.log("KVO tabBar.frame changed: \(oldFrame) -> \(newFrame), isHidden=\(tabBar.isHidden), alpha=\(tabBar.alpha)")
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
204
|
setupLongPressGesture()
|
|
266
205
|
rebuildViewControllers()
|
|
267
206
|
updateTabBarAppearance()
|
|
@@ -314,11 +253,7 @@ class RCTTabViewContainerView: UIView {
|
|
|
314
253
|
}
|
|
315
254
|
|
|
316
255
|
private func rebuildViewControllers() {
|
|
317
|
-
guard let tbc = tabBarController else {
|
|
318
|
-
log("rebuildViewControllers: no tabBarController yet")
|
|
319
|
-
return
|
|
320
|
-
}
|
|
321
|
-
log("rebuildViewControllers: tabBar.isHidden=\(tbc.tabBar.isHidden), tabBarHidden=\(tabBarHidden), items=\(parsedItems.count), childViews=\(childViews.count)")
|
|
256
|
+
guard let tbc = tabBarController else { return }
|
|
322
257
|
|
|
323
258
|
let filtered = filteredItems
|
|
324
259
|
let allItems = parsedItems
|
|
@@ -333,23 +268,16 @@ class RCTTabViewContainerView: UIView {
|
|
|
333
268
|
let vc: UIViewController
|
|
334
269
|
if let cached = cachedViewControllers[tabData.key] {
|
|
335
270
|
vc = cached
|
|
336
|
-
log("rebuildViewControllers: reusing cached VC for tab[\(tabData.key)]")
|
|
337
271
|
} else {
|
|
338
272
|
vc = UIViewController()
|
|
339
273
|
vc.view.backgroundColor = .clear
|
|
340
274
|
vc.view.clipsToBounds = true
|
|
341
275
|
cachedViewControllers[tabData.key] = vc
|
|
342
|
-
log("rebuildViewControllers: created new VC for tab[\(tabData.key)]")
|
|
343
276
|
}
|
|
344
277
|
|
|
345
278
|
// Attach child view if not already attached to this VC
|
|
346
279
|
if originalIndex < childViews.count {
|
|
347
280
|
let childView = childViews[originalIndex]
|
|
348
|
-
let currentSuperview = childView.superview
|
|
349
|
-
let superviewType = currentSuperview.map { String(describing: type(of: $0)) } ?? "nil"
|
|
350
|
-
let isSelf = currentSuperview === self
|
|
351
|
-
let isVcView = currentSuperview === vc.view
|
|
352
|
-
log("rebuildViewControllers: tab[\(tabData.key)] childView superview=\(superviewType), isSelf=\(isSelf), isVcView=\(isVcView), userInteraction=\(childView.isUserInteractionEnabled)")
|
|
353
281
|
if childView.superview !== vc.view {
|
|
354
282
|
childView.removeFromSuperview()
|
|
355
283
|
vc.view.addSubview(childView)
|
|
@@ -358,7 +286,6 @@ class RCTTabViewContainerView: UIView {
|
|
|
358
286
|
childView.translatesAutoresizingMaskIntoConstraints = true
|
|
359
287
|
childView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
360
288
|
childView.frame = vc.view.bounds
|
|
361
|
-
log("rebuildViewControllers: tab[\(tabData.key)] MOVED childView to vc.view (was \(superviewType))")
|
|
362
289
|
}
|
|
363
290
|
}
|
|
364
291
|
|
|
@@ -411,7 +338,6 @@ class RCTTabViewContainerView: UIView {
|
|
|
411
338
|
// Only call setViewControllers if the VC list actually changed
|
|
412
339
|
let currentVCs = tbc.viewControllers ?? []
|
|
413
340
|
if currentVCs.count != viewControllers.count || !zip(currentVCs, viewControllers).allSatisfy({ $0 === $1 }) {
|
|
414
|
-
log("rebuildViewControllers: updating VCs (old=\(currentVCs.count), new=\(viewControllers.count))")
|
|
415
341
|
if disablePageAnimations {
|
|
416
342
|
UIView.performWithoutAnimation {
|
|
417
343
|
tbc.setViewControllers(viewControllers, animated: false)
|
|
@@ -419,14 +345,6 @@ class RCTTabViewContainerView: UIView {
|
|
|
419
345
|
} else {
|
|
420
346
|
tbc.setViewControllers(viewControllers, animated: false)
|
|
421
347
|
}
|
|
422
|
-
} else {
|
|
423
|
-
log("rebuildViewControllers: VCs unchanged, skipping setViewControllers")
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
// Log the view hierarchy after setting VCs
|
|
427
|
-
log("rebuildViewControllers: tbc.view.subviews=\(tbc.view.subviews.map { "\(type(of: $0)):\(String(format: "%.0f", $0.frame.width))x\(String(format: "%.0f", $0.frame.height))" })")
|
|
428
|
-
if let selectedVC = tbc.selectedViewController {
|
|
429
|
-
log("rebuildViewControllers: selectedVC.view.frame=\(selectedVC.view.frame), subviews=\(selectedVC.view.subviews.count)")
|
|
430
348
|
}
|
|
431
349
|
|
|
432
350
|
updateSelectedTab()
|
|
@@ -436,11 +354,7 @@ class RCTTabViewContainerView: UIView {
|
|
|
436
354
|
|
|
437
355
|
private func updateSelectedTab() {
|
|
438
356
|
guard let tbc = tabBarController,
|
|
439
|
-
let selectedPage else {
|
|
440
|
-
log("updateSelectedTab: skipped (tbc=\(tabBarController != nil), selectedPage=\(selectedPage ?? "nil"))")
|
|
441
|
-
return
|
|
442
|
-
}
|
|
443
|
-
log("updateSelectedTab: selectedPage=\(selectedPage), tabBar.isHidden=\(tbc.tabBar.isHidden)")
|
|
357
|
+
let selectedPage else { return }
|
|
444
358
|
|
|
445
359
|
let filtered = filteredItems
|
|
446
360
|
guard let index = filtered.firstIndex(where: { $0.key == selectedPage }) else { return }
|
|
@@ -547,11 +461,7 @@ class RCTTabViewContainerView: UIView {
|
|
|
547
461
|
}
|
|
548
462
|
|
|
549
463
|
private func updateTabBarHidden() {
|
|
550
|
-
guard let tbc = tabBarController else {
|
|
551
|
-
log("updateTabBarHidden: no tabBarController yet")
|
|
552
|
-
return
|
|
553
|
-
}
|
|
554
|
-
log("updateTabBarHidden: setting tabBar.isHidden=\(tabBarHidden), current tabBar.isHidden=\(tbc.tabBar.isHidden), tabBar.frame=\(tbc.tabBar.frame)")
|
|
464
|
+
guard let tbc = tabBarController else { return }
|
|
555
465
|
tbc.tabBar.isHidden = tabBarHidden
|
|
556
466
|
tbc.tabBar.isUserInteractionEnabled = !tabBarHidden
|
|
557
467
|
|
|
@@ -564,7 +474,16 @@ class RCTTabViewContainerView: UIView {
|
|
|
564
474
|
guard let tbc = tabBarController else { return }
|
|
565
475
|
if #available(iOS 18.0, *) {
|
|
566
476
|
#if compiler(>=6.0)
|
|
567
|
-
|
|
477
|
+
if sidebarAdaptable {
|
|
478
|
+
tbc.mode = .tabSidebar
|
|
479
|
+
} else {
|
|
480
|
+
tbc.mode = .tabBar
|
|
481
|
+
// Force compact horizontal size class on iPad to keep TabBar at bottom
|
|
482
|
+
// iPad defaults to .regular which causes iOS 18+ to show tabs at top
|
|
483
|
+
if UIDevice.current.userInterfaceIdiom == .pad {
|
|
484
|
+
tbc.traitOverrides.horizontalSizeClass = .compact
|
|
485
|
+
}
|
|
486
|
+
}
|
|
568
487
|
#endif
|
|
569
488
|
}
|
|
570
489
|
}
|
|
@@ -720,16 +639,12 @@ class TabViewDelegateProxy: NSObject, UITabBarControllerDelegate {
|
|
|
720
639
|
|
|
721
640
|
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
|
|
722
641
|
guard let containerView = mapping[ObjectIdentifier(tabBarController)] else {
|
|
723
|
-
RCTTabViewLog.debug("RCTTabView", message: "[DelegateProxy] shouldSelect: no container view found!")
|
|
724
642
|
return false
|
|
725
643
|
}
|
|
726
644
|
let isReselection = tabBarController.selectedViewController == viewController
|
|
727
|
-
RCTTabViewLog.debug("RCTTabView", message: "[DelegateProxy] shouldSelect: isReselection=\(isReselection), tabBar.isHidden=\(tabBarController.tabBar.isHidden)")
|
|
728
645
|
|
|
729
646
|
if let index = tabBarController.viewControllers?.firstIndex(of: viewController) {
|
|
730
|
-
|
|
731
|
-
RCTTabViewLog.debug("RCTTabView", message: "[DelegateProxy] shouldSelect result=\(result)")
|
|
732
|
-
return result
|
|
647
|
+
return containerView.shouldSelectTab(at: index, isReselection: isReselection)
|
|
733
648
|
}
|
|
734
649
|
|
|
735
650
|
return false
|