@soundtrackio/react-native-bottom-tabs 1.4.0

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 (125) hide show
  1. package/README.md +32 -0
  2. package/android/build.gradle +132 -0
  3. package/android/gradle.properties +5 -0
  4. package/android/src/main/AndroidManifest.xml +3 -0
  5. package/android/src/main/AndroidManifestNew.xml +2 -0
  6. package/android/src/main/java/com/rcttabview/ImageSource.kt +86 -0
  7. package/android/src/main/java/com/rcttabview/LatestRequestTracker.kt +19 -0
  8. package/android/src/main/java/com/rcttabview/RCTTabView.kt +689 -0
  9. package/android/src/main/java/com/rcttabview/RCTTabViewManager.kt +222 -0
  10. package/android/src/main/java/com/rcttabview/RCTTabViewPackage.kt +19 -0
  11. package/android/src/main/java/com/rcttabview/RCTTabViewPrewarm.kt +40 -0
  12. package/android/src/main/java/com/rcttabview/RebuildBatch.kt +31 -0
  13. package/android/src/main/java/com/rcttabview/TabBarVisibility.kt +14 -0
  14. package/android/src/main/java/com/rcttabview/Utils.kt +31 -0
  15. package/android/src/main/java/com/rcttabview/events/OnNativeLayoutEvent.kt +25 -0
  16. package/android/src/main/java/com/rcttabview/events/OnTabBarMeasuredEvent.kt +24 -0
  17. package/android/src/main/java/com/rcttabview/events/PageSelectedEvent.kt +32 -0
  18. package/android/src/main/java/com/rcttabview/events/TabLongPressedEvent.kt +24 -0
  19. package/android/src/test/java/com/rcttabview/LatestRequestTrackerTest.kt +37 -0
  20. package/android/src/test/java/com/rcttabview/RebuildBatchTest.kt +44 -0
  21. package/android/src/test/java/com/rcttabview/TabBarVisibilityTest.kt +58 -0
  22. package/app.plugin.js +1 -0
  23. package/common/cpp/react/renderer/components/RNCTabView/RNCTabViewComponentDescriptor.h +33 -0
  24. package/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.cpp +19 -0
  25. package/common/cpp/react/renderer/components/RNCTabView/RNCTabViewShadowNode.h +35 -0
  26. package/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.cpp +15 -0
  27. package/common/cpp/react/renderer/components/RNCTabView/RNCTabViewState.h +25 -0
  28. package/ios/BottomAccessoryProvider.swift +27 -0
  29. package/ios/Extensions.swift +101 -0
  30. package/ios/PlatformAliases.swift +43 -0
  31. package/ios/RCTBottomAccessoryComponentView.h +17 -0
  32. package/ios/RCTBottomAccessoryComponentView.mm +76 -0
  33. package/ios/RCTTabViewComponentView.h +16 -0
  34. package/ios/RCTTabViewComponentView.mm +285 -0
  35. package/ios/RepresentableView.swift +55 -0
  36. package/ios/SVG/CoreSVG.h +19 -0
  37. package/ios/SVG/CoreSVG.mm +177 -0
  38. package/ios/SVG/SvgDecoder.h +10 -0
  39. package/ios/SVG/SvgDecoder.mm +38 -0
  40. package/ios/TabAppearModifier.swift +38 -0
  41. package/ios/TabBarFontSize.swift +88 -0
  42. package/ios/TabItem.swift +35 -0
  43. package/ios/TabItemEventModifier.swift +163 -0
  44. package/ios/TabView/AnyTabView.swift +7 -0
  45. package/ios/TabView/LegacyTabView.swift +70 -0
  46. package/ios/TabView/NewTabView.swift +138 -0
  47. package/ios/TabViewImpl.swift +566 -0
  48. package/ios/TabViewProps.swift +109 -0
  49. package/ios/TabViewProvider.swift +368 -0
  50. package/ios/react-native-bottom-tabs-Bridging-Header.h +4 -0
  51. package/lib/module/BottomAccessoryView.js +47 -0
  52. package/lib/module/BottomAccessoryView.js.map +1 -0
  53. package/lib/module/BottomAccessoryViewNativeComponent.ts +27 -0
  54. package/lib/module/DelayedFreeze.js +31 -0
  55. package/lib/module/DelayedFreeze.js.map +1 -0
  56. package/lib/module/NativeSVGDecoder.js +5 -0
  57. package/lib/module/NativeSVGDecoder.js.map +1 -0
  58. package/lib/module/SceneMap.js +28 -0
  59. package/lib/module/SceneMap.js.map +1 -0
  60. package/lib/module/TabView.js +294 -0
  61. package/lib/module/TabView.js.map +1 -0
  62. package/lib/module/TabViewNativeComponent.ts +73 -0
  63. package/lib/module/expo.js +35 -0
  64. package/lib/module/expo.js.map +1 -0
  65. package/lib/module/index.js +20 -0
  66. package/lib/module/index.js.map +1 -0
  67. package/lib/module/package.json +1 -0
  68. package/lib/module/types.js +4 -0
  69. package/lib/module/types.js.map +1 -0
  70. package/lib/module/utils/BottomTabBarHeightContext.js +5 -0
  71. package/lib/module/utils/BottomTabBarHeightContext.js.map +1 -0
  72. package/lib/module/utils/useBottomTabBarHeight.js +12 -0
  73. package/lib/module/utils/useBottomTabBarHeight.js.map +1 -0
  74. package/lib/typescript/package.json +1 -0
  75. package/lib/typescript/src/BottomAccessoryView.d.ts +7 -0
  76. package/lib/typescript/src/BottomAccessoryView.d.ts.map +1 -0
  77. package/lib/typescript/src/BottomAccessoryView.js +24 -0
  78. package/lib/typescript/src/BottomAccessoryViewNativeComponent.d.ts +15 -0
  79. package/lib/typescript/src/BottomAccessoryViewNativeComponent.d.ts.map +1 -0
  80. package/lib/typescript/src/BottomAccessoryViewNativeComponent.js +4 -0
  81. package/lib/typescript/src/DelayedFreeze.d.ts +7 -0
  82. package/lib/typescript/src/DelayedFreeze.d.ts.map +1 -0
  83. package/lib/typescript/src/DelayedFreeze.js +21 -0
  84. package/lib/typescript/src/NativeSVGDecoder.d.ts +5 -0
  85. package/lib/typescript/src/NativeSVGDecoder.d.ts.map +1 -0
  86. package/lib/typescript/src/NativeSVGDecoder.js +2 -0
  87. package/lib/typescript/src/SceneMap.d.ts +9 -0
  88. package/lib/typescript/src/SceneMap.d.ts.map +1 -0
  89. package/lib/typescript/src/SceneMap.js +15 -0
  90. package/lib/typescript/src/TabView.d.ts +219 -0
  91. package/lib/typescript/src/TabView.d.ts.map +1 -0
  92. package/lib/typescript/src/TabView.js +157 -0
  93. package/lib/typescript/src/TabViewNativeComponent.d.ts +59 -0
  94. package/lib/typescript/src/TabViewNativeComponent.d.ts.map +1 -0
  95. package/lib/typescript/src/TabViewNativeComponent.js +4 -0
  96. package/lib/typescript/src/expo.d.ts +6 -0
  97. package/lib/typescript/src/expo.d.ts.map +1 -0
  98. package/lib/typescript/src/expo.js +34 -0
  99. package/lib/typescript/src/index.d.ts +15 -0
  100. package/lib/typescript/src/index.d.ts.map +1 -0
  101. package/lib/typescript/src/index.js +11 -0
  102. package/lib/typescript/src/types.d.ts +31 -0
  103. package/lib/typescript/src/types.d.ts.map +1 -0
  104. package/lib/typescript/src/types.js +1 -0
  105. package/lib/typescript/src/utils/BottomTabBarHeightContext.d.ts +2 -0
  106. package/lib/typescript/src/utils/BottomTabBarHeightContext.d.ts.map +1 -0
  107. package/lib/typescript/src/utils/BottomTabBarHeightContext.js +2 -0
  108. package/lib/typescript/src/utils/useBottomTabBarHeight.d.ts +1 -0
  109. package/lib/typescript/src/utils/useBottomTabBarHeight.d.ts.map +1 -0
  110. package/lib/typescript/src/utils/useBottomTabBarHeight.js +9 -0
  111. package/lib/typescript/tsconfig.tsbuildinfo +1 -0
  112. package/package.json +135 -0
  113. package/react-native-bottom-tabs.podspec +43 -0
  114. package/src/BottomAccessoryView.tsx +59 -0
  115. package/src/BottomAccessoryViewNativeComponent.ts +27 -0
  116. package/src/DelayedFreeze.tsx +31 -0
  117. package/src/NativeSVGDecoder.ts +5 -0
  118. package/src/SceneMap.tsx +34 -0
  119. package/src/TabView.tsx +558 -0
  120. package/src/TabViewNativeComponent.ts +73 -0
  121. package/src/expo.ts +58 -0
  122. package/src/index.tsx +23 -0
  123. package/src/types.ts +36 -0
  124. package/src/utils/BottomTabBarHeightContext.ts +5 -0
  125. package/src/utils/useBottomTabBarHeight.ts +15 -0
@@ -0,0 +1,35 @@
1
+ #ifdef __cplusplus
2
+
3
+ #pragma once
4
+
5
+ #include <react/renderer/components/RNCTabView/EventEmitters.h>
6
+ #include <react/renderer/components/RNCTabView/Props.h>
7
+ #include <react/renderer/components/RNCTabView/RNCTabViewState.h>
8
+ #include <react/renderer/components/view/ConcreteViewShadowNode.h>
9
+ #include <jsi/jsi.h>
10
+
11
+ namespace facebook::react {
12
+
13
+ JSI_EXPORT extern const char RNCTabViewComponentName[];
14
+
15
+ /*
16
+ * `ShadowNode` for <RNCTabView> component.
17
+ */
18
+ class JSI_EXPORT RNCTabViewShadowNode final: public ConcreteViewShadowNode<
19
+ RNCTabViewComponentName,
20
+ RNCTabViewProps,
21
+ RNCTabViewEventEmitter,
22
+ RNCTabViewState> {
23
+
24
+ public:
25
+ using ConcreteViewShadowNode::ConcreteViewShadowNode;
26
+ using StateData = ConcreteViewShadowNode::ConcreteStateData;
27
+
28
+ void setImageLoader(std::weak_ptr<void> imageLoader);
29
+
30
+ StateData &getStateDataMutable();
31
+ };
32
+
33
+ }
34
+
35
+ #endif
@@ -0,0 +1,15 @@
1
+ #include <react/renderer/components/RNCTabView/RNCTabViewState.h>
2
+
3
+ namespace facebook::react {
4
+
5
+ void RNCTabViewState::setImageLoader(
6
+ std::weak_ptr<void> imageLoader) {
7
+ imageLoader_ = imageLoader;
8
+ }
9
+
10
+ std::weak_ptr<void> RNCTabViewState::getImageLoader()
11
+ const noexcept {
12
+ return imageLoader_;
13
+ }
14
+
15
+ } // namespace facebook::react
@@ -0,0 +1,25 @@
1
+ #ifdef __cplusplus
2
+
3
+ #pragma once
4
+
5
+ #include <react/renderer/core/StateData.h>
6
+ #ifdef RN_SERIALIZABLE_STATE
7
+ #include <folly/dynamic.h>
8
+ #endif
9
+
10
+ namespace facebook::react {
11
+
12
+ class RNCTabViewState final {
13
+ public:
14
+ RNCTabViewState() = default;
15
+
16
+ void setImageLoader(std::weak_ptr<void> imageLoader);
17
+ std::weak_ptr<void> getImageLoader() const noexcept;
18
+
19
+ private:
20
+ std::weak_ptr<void> imageLoader_;
21
+ };
22
+
23
+ } // namespace facebook::react
24
+
25
+ #endif
@@ -0,0 +1,27 @@
1
+ import SwiftUI
2
+
3
+ @objc public class BottomAccessoryProvider: NSObject {
4
+ private weak var delegate: BottomAccessoryProviderDelegate?
5
+
6
+ @objc public convenience init(delegate: BottomAccessoryProviderDelegate) {
7
+ self.init()
8
+ self.delegate = delegate
9
+ }
10
+
11
+ #if !os(macOS) && compiler(>=6.2)
12
+ @available(iOS 26.0, tvOS 26.0, *)
13
+ public func emitPlacementChanged(_ placement: TabViewBottomAccessoryPlacement?) {
14
+ var placementValue = "none"
15
+ if placement == .inline {
16
+ placementValue = "inline"
17
+ } else if placement == .expanded {
18
+ placementValue = "expanded"
19
+ }
20
+ self.delegate?.onPlacementChanged(placement: placementValue)
21
+ }
22
+ #endif
23
+ }
24
+
25
+ @objc public protocol BottomAccessoryProviderDelegate {
26
+ func onPlacementChanged(placement: String)
27
+ }
@@ -0,0 +1,101 @@
1
+ import Foundation
2
+ import SwiftUI
3
+ @_spi(Advanced) import SwiftUIIntrospect
4
+
5
+ #if os(macOS)
6
+ import AppKit
7
+ #else
8
+ import UIKit
9
+ #endif
10
+
11
+ extension Collection {
12
+ // Returns the element at the specified index if it is within bounds, otherwise nil.
13
+ subscript(safe index: Index) -> Element? {
14
+ indices.contains(index) ? self[index] : nil
15
+ }
16
+ }
17
+
18
+ extension Collection where Element == TabInfo {
19
+ func findByKey(_ key: String?) -> Element? {
20
+ guard let key else { return nil }
21
+ guard !isEmpty else { return nil }
22
+ return first { $0.key == key }
23
+ }
24
+ }
25
+
26
+ extension PlatformView {
27
+ func pinEdges(to other: PlatformView) {
28
+ NSLayoutConstraint.activate([
29
+ leadingAnchor.constraint(equalTo: other.leadingAnchor),
30
+ trailingAnchor.constraint(equalTo: other.trailingAnchor),
31
+ topAnchor.constraint(equalTo: other.topAnchor),
32
+ bottomAnchor.constraint(equalTo: other.bottomAnchor)
33
+ ])
34
+ }
35
+ }
36
+
37
+ extension PlatformImage {
38
+ func resizeImageTo(size: CGSize) -> PlatformImage? {
39
+ #if os(macOS)
40
+ return NSImage(size: size, flipped: false) { rect -> Bool in
41
+ self.draw(in: rect,
42
+ from: CGRect(origin: .zero, size: self.size),
43
+ operation: .copy,
44
+ fraction: 1.0)
45
+ return true
46
+ }
47
+ #else
48
+ let renderer = UIGraphicsImageRenderer(size: size)
49
+ return renderer.image { _ in
50
+ self.draw(in: CGRect(origin: .zero, size: size))
51
+ }
52
+ #endif
53
+ }
54
+ }
55
+
56
+ extension View {
57
+ #if os(macOS)
58
+ @MainActor
59
+ @ViewBuilder
60
+ func introspectTabView(closure: @escaping (NSTabView) -> Void) -> some View {
61
+ self
62
+ .introspect(
63
+ .tabView,
64
+ on: .macOS(.v11...),
65
+ customize: closure
66
+ )
67
+ }
68
+ #else
69
+ @MainActor
70
+ @ViewBuilder
71
+ func introspectTabView(closure: @escaping (UITabBarController) -> Void) -> some View {
72
+ self
73
+ #if !os(visionOS)
74
+ .introspect(
75
+ .tabView,
76
+ on: .iOS(.v14...),
77
+ .tvOS(.v14...),
78
+ customize: closure
79
+ )
80
+ #endif
81
+ }
82
+ #endif
83
+
84
+ @MainActor
85
+ @ViewBuilder
86
+ func measureView(onLayout: @escaping (_ size: CGSize) -> Void) -> some View {
87
+ self
88
+ .background(
89
+ GeometryReader { geometry in
90
+ Color.clear
91
+ .onChange(of: geometry.size) { newValue in
92
+ onLayout(newValue)
93
+ }
94
+ .onAppear {
95
+ onLayout(geometry.size)
96
+ }
97
+ }
98
+ .ignoresSafeArea(.all, edges: .all)
99
+ )
100
+ }
101
+ }
@@ -0,0 +1,43 @@
1
+ import SwiftUI
2
+
3
+ #if os(macOS)
4
+ import AppKit
5
+ #else
6
+ import UIKit
7
+ #endif
8
+
9
+ #if os(macOS)
10
+ public typealias PlatformView = NSView
11
+ #else
12
+ public typealias PlatformView = UIView
13
+ #endif
14
+
15
+ #if os(macOS)
16
+ typealias PlatformImage = NSImage
17
+ #else
18
+ typealias PlatformImage = UIImage
19
+ #endif
20
+
21
+ #if os(macOS)
22
+ public typealias PlatformColor = NSColor
23
+ #else
24
+ public typealias PlatformColor = UIColor
25
+ #endif
26
+
27
+ #if os(macOS)
28
+ typealias PlatformHostingController = NSHostingController
29
+ #else
30
+ typealias PlatformHostingController = UIHostingController
31
+ #endif
32
+
33
+ #if os(macOS)
34
+ typealias PlatformViewRepresentable = NSViewRepresentable
35
+ #else
36
+ typealias PlatformViewRepresentable = UIViewRepresentable
37
+ #endif
38
+
39
+ #if os(macOS)
40
+ typealias PlatformFont = NSFont
41
+ #else
42
+ typealias PlatformFont = UIFont
43
+ #endif
@@ -0,0 +1,17 @@
1
+ #ifdef RCT_NEW_ARCH_ENABLED
2
+ #import <React/RCTViewComponentView.h>
3
+ #if TARGET_OS_OSX
4
+ #import <AppKit/AppKit.h>
5
+ #else
6
+ #import <UIKit/UIKit.h>
7
+ #endif
8
+
9
+ NS_ASSUME_NONNULL_BEGIN
10
+
11
+ @interface RCTBottomAccessoryComponentView: RCTViewComponentView
12
+
13
+ @end
14
+
15
+ NS_ASSUME_NONNULL_END
16
+
17
+ #endif /* RCTBottomAccessoryComponentView_h */
@@ -0,0 +1,76 @@
1
+ #ifdef RCT_NEW_ARCH_ENABLED
2
+ #import "RCTBottomAccessoryComponentView.h"
3
+
4
+ #import <react/renderer/components/RNCTabView/ComponentDescriptors.h>
5
+ #import <react/renderer/components/RNCTabView/EventEmitters.h>
6
+ #import <react/renderer/components/RNCTabView/Props.h>
7
+ #import <react/renderer/components/RNCTabView/RCTComponentViewHelpers.h>
8
+
9
+ #import <React/RCTFabricComponentsPlugins.h>
10
+
11
+ #if __has_include("react_native_bottom_tabs/react_native_bottom_tabs-Swift.h")
12
+ #import "react_native_bottom_tabs/react_native_bottom_tabs-Swift.h"
13
+ #else
14
+ #import "react_native_bottom_tabs-Swift.h"
15
+ #endif
16
+
17
+ using namespace facebook::react;
18
+
19
+ @interface RCTBottomAccessoryComponentView () <BottomAccessoryProviderDelegate> {
20
+ BottomAccessoryProvider* bottomAccessoryProvider;
21
+ }
22
+ @end
23
+
24
+ @implementation RCTBottomAccessoryComponentView
25
+
26
+ + (ComponentDescriptorProvider)componentDescriptorProvider
27
+ {
28
+ return concreteComponentDescriptorProvider<BottomAccessoryViewComponentDescriptor>();
29
+ }
30
+
31
+ - (instancetype)initWithFrame:(CGRect)frame
32
+ {
33
+ if (self = [super initWithFrame:frame]) {
34
+ static const auto defaultProps = std::make_shared<const BottomAccessoryViewProps>();
35
+ if (@available(iOS 26.0, *)) {
36
+ bottomAccessoryProvider = [[BottomAccessoryProvider alloc] initWithDelegate:self];
37
+ }
38
+ }
39
+
40
+ return self;
41
+ }
42
+
43
+ - (void)setFrame:(CGRect)frame
44
+ {
45
+ [super setFrame:frame];
46
+ auto eventEmitter = std::static_pointer_cast<const BottomAccessoryViewEventEmitter>(_eventEmitter);
47
+ if (eventEmitter) {
48
+ // TODO: Rewrite this to emit synchronous layout events using shadow nodes
49
+ eventEmitter->onNativeLayout(BottomAccessoryViewEventEmitter::OnNativeLayout {
50
+ .height = frame.size.height,
51
+ .width = frame.size.width
52
+ });
53
+ }
54
+ }
55
+
56
+ // MARK: BottomAccessoryProviderDelegate
57
+
58
+ - (void)onPlacementChangedWithPlacement:(NSString *)placement
59
+ {
60
+ auto eventEmitter = std::static_pointer_cast<const BottomAccessoryViewEventEmitter>(_eventEmitter);
61
+ if (eventEmitter) {
62
+ eventEmitter->onPlacementChanged(BottomAccessoryViewEventEmitter::OnPlacementChanged {
63
+ .placement = std::string([placement UTF8String])
64
+ });
65
+ }
66
+ }
67
+
68
+
69
+ Class<RCTComponentViewProtocol> BottomAccessoryViewCls(void)
70
+ {
71
+ return RCTBottomAccessoryComponentView.class;
72
+ }
73
+
74
+ @end
75
+
76
+ #endif
@@ -0,0 +1,16 @@
1
+ #ifdef RCT_NEW_ARCH_ENABLED
2
+ #import <React/RCTViewComponentView.h>
3
+ #if TARGET_OS_OSX
4
+ #import <AppKit/AppKit.h>
5
+ #else
6
+ #import <UIKit/UIKit.h>
7
+ #endif
8
+
9
+ NS_ASSUME_NONNULL_BEGIN
10
+
11
+ @interface RCTTabViewComponentView: RCTViewComponentView
12
+ @end
13
+
14
+ NS_ASSUME_NONNULL_END
15
+
16
+ #endif /* RCT_NEW_ARCH_ENABLED */
@@ -0,0 +1,285 @@
1
+ #ifdef RCT_NEW_ARCH_ENABLED
2
+ #import "RCTTabViewComponentView.h"
3
+
4
+ #import <react/renderer/components/RNCTabView/RNCTabViewComponentDescriptor.h>
5
+ #import <react/renderer/components/RNCTabView/EventEmitters.h>
6
+ #import <react/renderer/components/RNCTabView/Props.h>
7
+ #import <react/renderer/components/RNCTabView/RCTComponentViewHelpers.h>
8
+
9
+ #import <React/RCTFabricComponentsPlugins.h>
10
+
11
+ #if __has_include("react_native_bottom_tabs/react_native_bottom_tabs-Swift.h")
12
+ #import "react_native_bottom_tabs/react_native_bottom_tabs-Swift.h"
13
+ #else
14
+ #import "react_native_bottom_tabs-Swift.h"
15
+ #endif
16
+
17
+ #import <React/RCTImageLoader.h>
18
+ #import <React/RCTImageSource.h>
19
+ #import <React/RCTBridge+Private.h>
20
+ #import "RCTImagePrimitivesConversions.h"
21
+ #if __has_include(<React/RCTConversions.h>)
22
+ #import <React/RCTConversions.h>
23
+ #else
24
+ #import "RCTConversions.h"
25
+ #endif
26
+ #import <react/utils/ManagedObjectWrapper.h>
27
+
28
+ #if TARGET_OS_OSX
29
+ typedef NSView PlatformView;
30
+ #else
31
+ typedef UIView PlatformView;
32
+ #endif
33
+
34
+ // Overload `==` and `!=` operators for `RNCTabViewItemsStruct`
35
+
36
+ namespace facebook::react {
37
+
38
+ bool operator==(const RNCTabViewItemsStruct& lhs, const RNCTabViewItemsStruct& rhs) {
39
+ return lhs.key == rhs.key &&
40
+ lhs.title == rhs.title &&
41
+ lhs.sfSymbol == rhs.sfSymbol &&
42
+ lhs.focusedSfSymbol == rhs.focusedSfSymbol &&
43
+ lhs.badge == rhs.badge &&
44
+ lhs.activeTintColor == rhs.activeTintColor &&
45
+ lhs.iconRenderingMode == rhs.iconRenderingMode &&
46
+ lhs.hidden == rhs.hidden &&
47
+ lhs.testID == rhs.testID &&
48
+ lhs.role == rhs.role &&
49
+ lhs.preventsDefault == rhs.preventsDefault;
50
+ }
51
+
52
+ bool operator!=(const RNCTabViewItemsStruct& lhs, const RNCTabViewItemsStruct& rhs) {
53
+ return !(lhs == rhs);
54
+ }
55
+
56
+ }
57
+
58
+
59
+ using namespace facebook::react;
60
+
61
+ @interface RCTTabViewComponentView () <RCTRNCTabViewViewProtocol, TabViewProviderDelegate> {
62
+ }
63
+
64
+ @end
65
+
66
+ @implementation RCTTabViewComponentView {
67
+ TabViewProvider *_tabViewProvider;
68
+ }
69
+
70
+ + (ComponentDescriptorProvider)componentDescriptorProvider
71
+ {
72
+ return concreteComponentDescriptorProvider<RNCTabViewComponentDescriptor>();
73
+ }
74
+
75
+ - (instancetype)initWithFrame:(CGRect)frame
76
+ {
77
+ if (self = [super initWithFrame:frame]) {
78
+ static const auto defaultProps = std::make_shared<const RNCTabViewProps>();
79
+ _tabViewProvider = [[TabViewProvider alloc] initWithDelegate:self];
80
+ self.contentView = _tabViewProvider;
81
+ _props = defaultProps;
82
+ }
83
+
84
+ return self;
85
+ }
86
+
87
+ // Opt out of recycling for now, it's not working properly.
88
+ + (BOOL)shouldBeRecycled
89
+ {
90
+ return NO;
91
+ }
92
+
93
+ - (void)mountChildComponentView:(PlatformView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
94
+ [_tabViewProvider insertChild:childComponentView atIndex:index];
95
+ }
96
+
97
+ - (void)unmountChildComponentView:(PlatformView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
98
+ [_tabViewProvider removeChildAtIndex:index];
99
+ [childComponentView removeFromSuperview];
100
+ }
101
+
102
+ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
103
+ {
104
+ const auto &oldViewProps = *std::static_pointer_cast<RNCTabViewProps const>(_props);
105
+ const auto &newViewProps = *std::static_pointer_cast<RNCTabViewProps const>(props);
106
+
107
+ if (oldViewProps.items != newViewProps.items) {
108
+ _tabViewProvider.itemsData = convertItemsToArray(newViewProps.items);
109
+ }
110
+
111
+ if (oldViewProps.translucent != newViewProps.translucent) {
112
+ _tabViewProvider.translucent = newViewProps.translucent;
113
+ }
114
+
115
+ if (oldViewProps.icons != newViewProps.icons) {
116
+ auto iconsArray = [[NSMutableArray alloc] init];
117
+ for (auto &source: newViewProps.icons) {
118
+ auto imageSource = [[RCTImageSource alloc] initWithURLRequest:NSURLRequestFromImageSource(source) size:CGSizeMake(source.size.width, source.size.height) scale:source.scale];
119
+ [iconsArray addObject:imageSource];
120
+ }
121
+
122
+ _tabViewProvider.icons = iconsArray;
123
+ }
124
+
125
+ if (oldViewProps.focusedIcons != newViewProps.focusedIcons) {
126
+ auto focusedIconsArray = [[NSMutableArray alloc] init];
127
+ for (auto &source: newViewProps.focusedIcons) {
128
+ auto imageSource = [[RCTImageSource alloc] initWithURLRequest:NSURLRequestFromImageSource(source) size:CGSizeMake(source.size.width, source.size.height) scale:source.scale];
129
+ [focusedIconsArray addObject:imageSource];
130
+ }
131
+
132
+ _tabViewProvider.focusedIcons = focusedIconsArray;
133
+ }
134
+
135
+ if (oldViewProps.sidebarAdaptable != newViewProps.sidebarAdaptable) {
136
+ _tabViewProvider.sidebarAdaptable = newViewProps.sidebarAdaptable;
137
+ }
138
+
139
+ if (oldViewProps.minimizeBehavior != newViewProps.minimizeBehavior) {
140
+ _tabViewProvider.minimizeBehavior = RCTNSStringFromString(newViewProps.minimizeBehavior);
141
+ }
142
+
143
+ if (oldViewProps.disablePageAnimations != newViewProps.disablePageAnimations) {
144
+ _tabViewProvider.disablePageAnimations = newViewProps.disablePageAnimations;
145
+ }
146
+
147
+ if (oldViewProps.labeled != newViewProps.labeled) {
148
+ _tabViewProvider.labeled = newViewProps.labeled;
149
+ }
150
+
151
+ if (oldViewProps.selectedPage != newViewProps.selectedPage) {
152
+ _tabViewProvider.selectedPage = RCTNSStringFromString(newViewProps.selectedPage);
153
+ }
154
+
155
+ if (oldViewProps.scrollEdgeAppearance != newViewProps.scrollEdgeAppearance) {
156
+ _tabViewProvider.scrollEdgeAppearance = RCTNSStringFromString(newViewProps.scrollEdgeAppearance);
157
+ }
158
+
159
+ if (oldViewProps.labeled != newViewProps.labeled) {
160
+ _tabViewProvider.labeled = newViewProps.labeled;
161
+ }
162
+
163
+ if (oldViewProps.barTintColor != newViewProps.barTintColor) {
164
+ _tabViewProvider.barTintColor = RCTUIColorFromSharedColor(newViewProps.barTintColor);
165
+ }
166
+
167
+ if (oldViewProps.activeTintColor != newViewProps.activeTintColor) {
168
+ _tabViewProvider.activeTintColor = RCTUIColorFromSharedColor(newViewProps.activeTintColor);
169
+ }
170
+
171
+ if (oldViewProps.inactiveTintColor != newViewProps.inactiveTintColor) {
172
+ _tabViewProvider.inactiveTintColor = RCTUIColorFromSharedColor(newViewProps.inactiveTintColor);
173
+ }
174
+
175
+ if (oldViewProps.experimentalBakedTintColors != newViewProps.experimentalBakedTintColors) {
176
+ _tabViewProvider.experimentalBakedTintColors = newViewProps.experimentalBakedTintColors;
177
+ }
178
+
179
+ if (oldViewProps.hapticFeedbackEnabled != newViewProps.hapticFeedbackEnabled) {
180
+ _tabViewProvider.hapticFeedbackEnabled = newViewProps.hapticFeedbackEnabled;
181
+ }
182
+
183
+ if (oldViewProps.layoutDirection != newViewProps.layoutDirection) {
184
+ _tabViewProvider.layoutDirection = RCTNSStringFromStringNilIfEmpty(newViewProps.layoutDirection);
185
+ }
186
+
187
+ if (oldViewProps.fontSize != newViewProps.fontSize) {
188
+ _tabViewProvider.fontSize = [NSNumber numberWithInt:newViewProps.fontSize];
189
+ }
190
+
191
+ if (oldViewProps.fontWeight != newViewProps.fontWeight) {
192
+ _tabViewProvider.fontWeight = RCTNSStringFromStringNilIfEmpty(newViewProps.fontWeight);
193
+ }
194
+
195
+ if (oldViewProps.fontFamily != newViewProps.fontFamily) {
196
+ _tabViewProvider.fontFamily = RCTNSStringFromStringNilIfEmpty(newViewProps.fontFamily);
197
+ }
198
+
199
+ if (oldViewProps.tabBarHidden != newViewProps.tabBarHidden) {
200
+ _tabViewProvider.tabBarHidden = newViewProps.tabBarHidden;
201
+ }
202
+
203
+
204
+ [super updateProps:props oldProps:oldProps];
205
+ }
206
+
207
+ NSArray* convertItemsToArray(const std::vector<RNCTabViewItemsStruct>& items) {
208
+ NSMutableArray<TabInfo *> *result = [NSMutableArray array];
209
+
210
+ for (const auto& item : items) {
211
+ auto tabInfo = [[TabInfo alloc] initWithKey:RCTNSStringFromString(item.key)
212
+ title:RCTNSStringFromString(item.title)
213
+ badge:RCTNSStringFromStringNilIfEmpty(item.badge)
214
+ sfSymbol:RCTNSStringFromStringNilIfEmpty(item.sfSymbol)
215
+ focusedSfSymbol:RCTNSStringFromStringNilIfEmpty(item.focusedSfSymbol)
216
+ activeTintColor:RCTUIColorFromSharedColor(item.activeTintColor)
217
+ iconRenderingMode:RCTNSStringFromStringNilIfEmpty(item.iconRenderingMode)
218
+ hidden:item.hidden
219
+ testID:RCTNSStringFromStringNilIfEmpty(item.testID)
220
+ role:RCTNSStringFromStringNilIfEmpty(item.role)
221
+ preventsDefault:item.preventsDefault
222
+ ];
223
+
224
+ [result addObject:tabInfo];
225
+ }
226
+
227
+ return result;
228
+ }
229
+
230
+ - (void)updateState:(const facebook::react::State::Shared &)state oldState:(const facebook::react::State::Shared &)oldState
231
+ {
232
+ auto _state = std::static_pointer_cast<RNCTabViewShadowNode::ConcreteState const>(state);
233
+ auto data = _state->getData();
234
+ if (auto imgLoaderPtr = _state.get()->getData().getImageLoader().lock()) {
235
+ [_tabViewProvider setImageLoader:unwrapManagedObject(imgLoaderPtr)];
236
+ }
237
+ }
238
+
239
+ // MARK: TabViewProviderDelegate
240
+
241
+ - (void)onPageSelectedWithKey:(NSString *)key reactTag:(NSNumber *)reactTag {
242
+ auto eventEmitter = std::static_pointer_cast<const RNCTabViewEventEmitter>(_eventEmitter);
243
+ if (eventEmitter) {
244
+ eventEmitter->onPageSelected(RNCTabViewEventEmitter::OnPageSelected{
245
+ .key = [key cStringUsingEncoding:kCFStringEncodingUTF8]
246
+ });
247
+ }
248
+ }
249
+
250
+ - (void)onLongPressWithKey:(NSString *)key reactTag:(NSNumber *)reactTag {
251
+ auto eventEmitter = std::static_pointer_cast<const RNCTabViewEventEmitter>(_eventEmitter);
252
+ if (eventEmitter) {
253
+ eventEmitter->onTabLongPress(RNCTabViewEventEmitter::OnTabLongPress {
254
+ .key = [key cStringUsingEncoding:kCFStringEncodingUTF8]
255
+ });
256
+ }
257
+ }
258
+
259
+ - (void)onTabBarMeasuredWithHeight:(NSInteger)height reactTag:(NSNumber *)reactTag {
260
+ auto eventEmitter = std::static_pointer_cast<const RNCTabViewEventEmitter>(_eventEmitter);
261
+ if (eventEmitter) {
262
+ eventEmitter->onTabBarMeasured(RNCTabViewEventEmitter::OnTabBarMeasured {
263
+ .height = (int)height
264
+ });
265
+ }
266
+ }
267
+
268
+ - (void)onLayoutWithSize:(CGSize)size reactTag:(NSNumber *)reactTag {
269
+ auto eventEmitter = std::static_pointer_cast<const RNCTabViewEventEmitter>(_eventEmitter);
270
+ if (eventEmitter) {
271
+ eventEmitter->onNativeLayout(RNCTabViewEventEmitter::OnNativeLayout {
272
+ .height = size.height,
273
+ .width = size.width
274
+ });
275
+ }
276
+ }
277
+
278
+ @end
279
+
280
+ Class<RCTComponentViewProtocol> RNCTabViewCls(void)
281
+ {
282
+ return RCTTabViewComponentView.class;
283
+ }
284
+
285
+ #endif // RCT_NEW_ARCH_ENABLED
@@ -0,0 +1,55 @@
1
+ import SwiftUI
2
+
3
+ /**
4
+ Helper used to render UIView inside of SwiftUI.
5
+ Wraps each view with an additional wrapper to avoid directly managing React Native views.
6
+ This solves issues where the layout would have weird artifacts..
7
+ */
8
+ struct RepresentableView: PlatformViewRepresentable {
9
+ var view: PlatformView
10
+
11
+ #if os(macOS)
12
+
13
+ func makeNSView(context: Context) -> PlatformView {
14
+ let wrapper = NSView()
15
+ wrapper.addSubview(view)
16
+ return wrapper
17
+ }
18
+
19
+ func updateNSView(_ nsView: PlatformView, context: Context) {}
20
+
21
+ #else
22
+
23
+ func makeUIView(context: Context) -> PlatformView {
24
+ let wrapper = UIView()
25
+ Self.detachStaleViewControllers(in: view)
26
+ wrapper.addSubview(view)
27
+ return wrapper
28
+ }
29
+
30
+ func updateUIView(_ uiView: PlatformView, context: Context) {}
31
+
32
+ /**
33
+ When a tab is hidden, SwiftUI drops it from the view tree and discards its
34
+ `UIHostingController`, but view controllers that libraries such as
35
+ react-native-screens attached to that host (e.g. `RNSNavigationController`)
36
+ stay parented to it. Those libraries only attach when the controller has no
37
+ parent, so re-adding the same React Native view under a fresh host trips
38
+ UIKit's hierarchy consistency check and crashes. Detaching the stale
39
+ children first lets them re-attach to the new host on their own.
40
+ */
41
+ static func detachStaleViewControllers(in view: UIView) {
42
+ for subview in view.subviews {
43
+ if let controller = subview.next as? UIViewController, controller.view === subview {
44
+ if controller.parent != nil {
45
+ controller.willMove(toParent: nil)
46
+ controller.removeFromParent()
47
+ }
48
+ } else {
49
+ detachStaleViewControllers(in: subview)
50
+ }
51
+ }
52
+ }
53
+
54
+ #endif
55
+ }