diff --git a/.changeset/hungry-readers-remember.md b/.changeset/hungry-readers-remember.md new file mode 100644 index 00000000..96ab33af --- /dev/null +++ b/.changeset/hungry-readers-remember.md @@ -0,0 +1,5 @@ +--- +"react-native-bottom-tabs": patch +--- + +fix: measure available space for views on iOS, make sideBarAdaptable work properly diff --git a/apps/example/ios/Podfile.lock b/apps/example/ios/Podfile.lock index 3a28b0a6..bc2327ca 100644 --- a/apps/example/ios/Podfile.lock +++ b/apps/example/ios/Podfile.lock @@ -1209,7 +1209,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-bottom-tabs (0.6.0): + - react-native-bottom-tabs (0.7.1): - DoubleConversion - glog - RCT-Folly (= 2024.01.01.00) @@ -1222,7 +1222,7 @@ PODS: - React-graphics - React-ImageManager - React-jsi - - react-native-bottom-tabs/common (= 0.6.0) + - react-native-bottom-tabs/common (= 0.7.1) - React-NativeModulesApple - React-RCTFabric - React-rendererdebug @@ -1234,7 +1234,7 @@ PODS: - SDWebImageSVGCoder (>= 1.7.0) - SwiftUIIntrospect (~> 1.0) - Yoga - - react-native-bottom-tabs/common (0.6.0): + - react-native-bottom-tabs/common (0.7.1): - DoubleConversion - glog - RCT-Folly (= 2024.01.01.00) @@ -1945,7 +1945,7 @@ SPEC CHECKSUMS: React-logger: d79b704bf215af194f5213a6b7deec50ba8e6a9b React-Mapbuffer: b982d5bba94a8bc073bda48f0d27c9b28417fae3 React-microtasksnativemodule: 8fa285fed833a04a754bf575f8ded65fc240b88d - react-native-bottom-tabs: c17ddaf86c160134349c6325e020c1f38ee5f743 + react-native-bottom-tabs: a08eaf6baf1b78375e17019536783dbbca3190ba react-native-safe-area-context: 73505107f7c673cd550a561aeb6271f152c483b6 React-nativeconfig: 8c83d992b9cc7d75b5abe262069eaeea4349f794 React-NativeModulesApple: b8465afc883f5bf3fe8bac3767e394d581a5f123 @@ -1982,8 +1982,8 @@ SPEC CHECKSUMS: SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d SwiftUIIntrospect: fee9aa07293ee280373a591e1824e8ddc869ba5d - Yoga: aa3df615739504eebb91925fc9c58b4922ea9a08 + Yoga: 055f92ad73f8c8600a93f0e25ac0b2344c3b07e6 PODFILE CHECKSUM: 1c1dbca3e400ef935aa9a150cb2dcb58fb8c4536 -COCOAPODS: 1.14.3 +COCOAPODS: 1.15.2 diff --git a/apps/example/src/Examples/NativeBottomTabsEmbeddedStacks.tsx b/apps/example/src/Examples/NativeBottomTabsEmbeddedStacks.tsx index 2484d0b3..63fe64aa 100644 --- a/apps/example/src/Examples/NativeBottomTabsEmbeddedStacks.tsx +++ b/apps/example/src/Examples/NativeBottomTabsEmbeddedStacks.tsx @@ -67,7 +67,7 @@ function ChatStackScreen() { function NativeBottomTabsEmbeddedStacks() { return ( - + String { + return "RCTEventEmitter.receiveEvent" + } + + public func arguments() -> [Any] { + return [ + viewTag, + RCTNormalizeInputEventName(eventName) ?? eventName, + [ + "width": size.width, + "height": size.height + ] + ] + } +} diff --git a/packages/react-native-bottom-tabs/ios/Events/PageSelectedEvent.swift b/packages/react-native-bottom-tabs/ios/Events/PageSelectedEvent.swift index 9799709f..9a6841ea 100644 --- a/packages/react-native-bottom-tabs/ios/Events/PageSelectedEvent.swift +++ b/packages/react-native-bottom-tabs/ios/Events/PageSelectedEvent.swift @@ -1,30 +1,25 @@ import React -@objc public class PageSelectedEvent: NSObject, RCTEvent { +@objcMembers +public class PageSelectedEvent: NSObject, RCTEvent { private var key: NSString - @objc public var viewTag: NSNumber - @objc public var coalescingKey: UInt16 + public var viewTag: NSNumber - @objc public var eventName: String { + public var eventName: String { return "onPageSelected" } - @objc public init(reactTag: NSNumber, key: NSString, coalescingKey: UInt16) { + public init(reactTag: NSNumber, key: NSString) { self.viewTag = reactTag self.key = key - self.coalescingKey = coalescingKey super.init() } - @objc public func canCoalesce() -> Bool { - return false - } - - @objc public class func moduleDotMethod() -> String { + public class func moduleDotMethod() -> String { return "RCTEventEmitter.receiveEvent" } - @objc public func arguments() -> [Any] { + public func arguments() -> [Any] { return [ viewTag, RCTNormalizeInputEventName(eventName) ?? eventName, diff --git a/packages/react-native-bottom-tabs/ios/Events/TabBarMeasuredEvent.swift b/packages/react-native-bottom-tabs/ios/Events/TabBarMeasuredEvent.swift index 114e1f79..94609c51 100644 --- a/packages/react-native-bottom-tabs/ios/Events/TabBarMeasuredEvent.swift +++ b/packages/react-native-bottom-tabs/ios/Events/TabBarMeasuredEvent.swift @@ -1,30 +1,25 @@ import React -@objc public class TabBarMeasuredEvent: NSObject, RCTEvent { +@objcMembers +public class TabBarMeasuredEvent: NSObject, RCTEvent { private var height: NSInteger - @objc public var viewTag: NSNumber - @objc public var coalescingKey: UInt16 + public var viewTag: NSNumber - @objc public var eventName: String { + public var eventName: String { return "onTabBarMeasured" } - @objc public init(reactTag: NSNumber, height: NSInteger, coalescingKey: UInt16) { + public init(reactTag: NSNumber, height: NSInteger) { self.viewTag = reactTag self.height = height - self.coalescingKey = coalescingKey super.init() } - @objc public func canCoalesce() -> Bool { - return false - } - - @objc public class func moduleDotMethod() -> String { + public class func moduleDotMethod() -> String { return "RCTEventEmitter.receiveEvent" } - @objc public func arguments() -> [Any] { + public func arguments() -> [Any] { return [ viewTag, RCTNormalizeInputEventName(eventName) ?? eventName, diff --git a/packages/react-native-bottom-tabs/ios/Events/TabLongPressedEvent.swift b/packages/react-native-bottom-tabs/ios/Events/TabLongPressedEvent.swift index 48dabcae..c0618702 100644 --- a/packages/react-native-bottom-tabs/ios/Events/TabLongPressedEvent.swift +++ b/packages/react-native-bottom-tabs/ios/Events/TabLongPressedEvent.swift @@ -4,31 +4,26 @@ import React // RCTEvent is not defined for new arch. protocol RCTEvent {} -@objc public class TabLongPressEvent: NSObject, RCTEvent { +@objcMembers +public class TabLongPressEvent: NSObject, RCTEvent { private var key: NSString - @objc public var viewTag: NSNumber - @objc public var coalescingKey: UInt16 + public var viewTag: NSNumber - @objc public var eventName: String { + public var eventName: String { return "onTabLongPress" } - @objc public init(reactTag: NSNumber, key: NSString, coalescingKey: UInt16) { + public init(reactTag: NSNumber, key: NSString) { self.viewTag = reactTag self.key = key - self.coalescingKey = coalescingKey super.init() } - @objc public func canCoalesce() -> Bool { - return false - } - - @objc public class func moduleDotMethod() -> String { + public class func moduleDotMethod() -> String { return "RCTEventEmitter.receiveEvent" } - @objc public func arguments() -> [Any] { + public func arguments() -> [Any] { return [ viewTag, RCTNormalizeInputEventName(eventName) ?? eventName, diff --git a/packages/react-native-bottom-tabs/ios/Extensions.swift b/packages/react-native-bottom-tabs/ios/Extensions.swift index b970ea9e..5a995d47 100644 --- a/packages/react-native-bottom-tabs/ios/Extensions.swift +++ b/packages/react-native-bottom-tabs/ios/Extensions.swift @@ -50,4 +50,23 @@ extension View { customize: closure ) } + + + @MainActor + @ViewBuilder + func measureView(onLayout: @escaping (_ size: CGSize) -> Void) -> some View { + self + .background ( + GeometryReader { geometry in + Color.clear + .onChange(of: geometry.size) { newValue in + onLayout(newValue) + } + .onAppear { + onLayout(geometry.size) + } + } + .ignoresSafeArea(.container, edges: .vertical) + ) + } } diff --git a/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm b/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm index 47022b77..5cfcdc17 100644 --- a/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm +++ b/packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm @@ -232,6 +232,16 @@ - (void)onTabBarMeasuredWithHeight:(NSInteger)height reactTag:(NSNumber *)reactT } } +- (void)onLayoutWithSize:(CGSize)size reactTag:(NSNumber *)reactTag { + auto eventEmitter = std::static_pointer_cast(_eventEmitter); + if (eventEmitter) { + eventEmitter->onNativeLayout(RNCTabViewEventEmitter::OnNativeLayout { + .height = size.height, + .width = size.width + }); + } +} + @end Class RNCTabViewCls(void) diff --git a/packages/react-native-bottom-tabs/ios/RCTTabViewViewManager.mm b/packages/react-native-bottom-tabs/ios/RCTTabViewViewManager.mm index f1fbb1cf..7b9b5294 100644 --- a/packages/react-native-bottom-tabs/ios/RCTTabViewViewManager.mm +++ b/packages/react-native-bottom-tabs/ios/RCTTabViewViewManager.mm @@ -12,21 +12,10 @@ @interface RCTTabView : RCTViewManager @end -@implementation RCTTabView { - uint16_t _coalescingKey; -} +@implementation RCTTabView RCT_EXPORT_MODULE(RNCTabView) -- (instancetype)init -{ - self = [super init]; - if (self) { - _coalescingKey = 0; - } - return self; -} - RCT_EXPORT_VIEW_PROPERTY(items, NSArray) RCT_EXPORT_VIEW_PROPERTY(onPageSelected, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onTabLongPress, RCTDirectEventBlock) @@ -51,17 +40,22 @@ - (instancetype)init // MARK: TabViewProviderDelegate - (void)onLongPressWithKey:(NSString *)key reactTag:(NSNumber *)reactTag { - auto event = [[TabLongPressEvent alloc] initWithReactTag:reactTag key:key coalescingKey:_coalescingKey++]; + auto event = [[TabLongPressEvent alloc] initWithReactTag:reactTag key:key]; [self.bridge.eventDispatcher sendEvent:event]; } - (void)onPageSelectedWithKey:(NSString *)key reactTag:(NSNumber *)reactTag { - auto event = [[PageSelectedEvent alloc] initWithReactTag:reactTag key:key coalescingKey:_coalescingKey++]; + auto event = [[PageSelectedEvent alloc] initWithReactTag:reactTag key:key]; [self.bridge.eventDispatcher sendEvent:event]; } - (void)onTabBarMeasuredWithHeight:(NSInteger)height reactTag:(NSNumber *)reactTag { - auto event = [[TabBarMeasuredEvent alloc] initWithReactTag:reactTag height:height coalescingKey:_coalescingKey++]; + auto event = [[TabBarMeasuredEvent alloc] initWithReactTag:reactTag height:height]; + [self.bridge.eventDispatcher sendEvent:event]; +} + +- (void)onLayoutWithSize:(CGSize)size reactTag:(NSNumber *)reactTag { + auto event = [[OnNativeLayoutEvent alloc] initWithReactTag:reactTag size:size]; [self.bridge.eventDispatcher sendEvent:event]; } diff --git a/packages/react-native-bottom-tabs/ios/TabViewImpl.swift b/packages/react-native-bottom-tabs/ios/TabViewImpl.swift index 8dc92a76..12921bdf 100644 --- a/packages/react-native-bottom-tabs/ios/TabViewImpl.swift +++ b/packages/react-native-bottom-tabs/ios/TabViewImpl.swift @@ -59,6 +59,7 @@ struct TabViewImpl: View { var onSelect: (_ key: String) -> Void var onLongPress: (_ key: String) -> Void + var onLayout: (_ size: CGSize) -> Void var onTabBarMeasured: (_ height: Int) -> Void var body: some View { @@ -66,6 +67,9 @@ struct TabViewImpl: View { ForEach(props.children.indices, id: \.self) { index in renderTabItem(at: index) } + .measureView(onLayout: { size in + onLayout(size) + }) } #if !os(tvOS) .onTabItemEvent({ index, isLongPress in @@ -326,13 +330,10 @@ extension View { ) -> some View { if flag { self - .ignoresSafeArea(.container, edges: .all) - .frame(idealWidth: frame.width, idealHeight: frame.height) + .ignoresSafeArea(.container, edges: .vertical) } else { self - .ignoresSafeArea(.container, edges: .horizontal) .ignoresSafeArea(.container, edges: .bottom) - .frame(idealWidth: frame.width, idealHeight: frame.height) } } diff --git a/packages/react-native-bottom-tabs/ios/TabViewProvider.swift b/packages/react-native-bottom-tabs/ios/TabViewProvider.swift index 5e2db172..b49d1d2c 100644 --- a/packages/react-native-bottom-tabs/ios/TabViewProvider.swift +++ b/packages/react-native-bottom-tabs/ios/TabViewProvider.swift @@ -4,15 +4,15 @@ import React import SDWebImage import SDWebImageSVGCoder -@objc public final class TabInfo: NSObject { - @objc public let key: String - @objc public let title: String - @objc public let badge: String - @objc public let sfSymbol: String - @objc public let activeTintColor: UIColor? - @objc public let hidden: Bool - - @objc +@objcMembers +public final class TabInfo: NSObject { + public let key: String + public let title: String + public let badge: String + public let sfSymbol: String + public let activeTintColor: UIColor? + public let hidden: Bool + public init( key: String, title: String, @@ -35,6 +35,7 @@ import SDWebImageSVGCoder func onPageSelected(key: String, reactTag: NSNumber?) func onLongPress(key: String, reactTag: NSNumber?) func onTabBarMeasured(height: Int, reactTag: NSNumber?) + func onLayout(size: CGSize, reactTag: NSNumber?) } @objc public class TabViewProvider: UIView { @@ -184,6 +185,8 @@ import SDWebImageSVGCoder self.delegate?.onPageSelected(key: key, reactTag: self.reactTag) } onLongPress: { key in self.delegate?.onLongPress(key: key, reactTag: self.reactTag) + } onLayout: { size in + self.delegate?.onLayout(size: size, reactTag: self.reactTag) } onTabBarMeasured: { height in self.delegate?.onTabBarMeasured(height: height, reactTag: self.reactTag) }) diff --git a/packages/react-native-bottom-tabs/src/TabView.tsx b/packages/react-native-bottom-tabs/src/TabView.tsx index 0ac3bc01..5a24442d 100644 --- a/packages/react-native-bottom-tabs/src/TabView.tsx +++ b/packages/react-native-bottom-tabs/src/TabView.tsx @@ -171,6 +171,9 @@ const TabView = ({ // @ts-ignore const focusedKey = navigationState.routes[navigationState.index].key; const [tabBarHeight, setTabBarHeight] = React.useState(0); + const [measuredDimensions, setMeasuredDimensions] = React.useState< + { width: number; height: number } | undefined + >(); const trimmedRoutes = React.useMemo(() => { if ( @@ -273,6 +276,9 @@ const TabView = ({ onTabBarMeasured={({ nativeEvent: { height } }) => { setTabBarHeight(height); }} + onNativeLayout={({ nativeEvent: { width, height } }) => { + setMeasuredDimensions({ width, height }); + }} hapticFeedbackEnabled={hapticFeedbackEnabled} activeTintColor={activeTintColor} inactiveTintColor={inactiveTintColor} @@ -310,7 +316,7 @@ const TabView = ({ style={ Platform.OS === 'android' ? [StyleSheet.absoluteFill, { zIndex, opacity }] - : styles.fullWidth + : [{ position: 'absolute' }, measuredDimensions] } > {renderScene({ diff --git a/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts b/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts index e79b1022..2824e72c 100644 --- a/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts +++ b/packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts @@ -2,6 +2,7 @@ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNati import type { ColorValue, ProcessedColorValue, ViewProps } from 'react-native'; import type { DirectEventHandler, + Double, Int32, WithDefault, } from 'react-native/Libraries/Types/CodegenTypes'; @@ -16,6 +17,11 @@ export type OnTabBarMeasured = Readonly<{ height: Int32; }>; +export type OnNativeLayout = Readonly<{ + width: Double; + height: Double; +}>; + export type TabViewItems = ReadonlyArray<{ key: string; title: string; @@ -31,6 +37,7 @@ export interface TabViewProps extends ViewProps { onPageSelected?: DirectEventHandler; onTabLongPress?: DirectEventHandler; onTabBarMeasured?: DirectEventHandler; + onNativeLayout?: DirectEventHandler; icons?: ReadonlyArray; labeled?: boolean; sidebarAdaptable?: boolean;