Skip to content

Commit 51c523b

Browse files
authored
feat: implement useBottomTabBarHeight (#158)
* feat: implement useBottomTabBarHeight * docs(changeset): feat: add useBottomTabBarHeight() hook * feat: add events for old arch * feat: add docs
1 parent 153f5f7 commit 51c523b

17 files changed

+308
-84
lines changed

.changeset/perfect-onions-reply.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'react-native-bottom-tabs': patch
3+
---
4+
5+
feat: add useBottomTabBarHeight() hook

apps/example/ios/Podfile.lock

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1209,7 +1209,7 @@ PODS:
12091209
- ReactCommon/turbomodule/bridging
12101210
- ReactCommon/turbomodule/core
12111211
- Yoga
1212-
- react-native-bottom-tabs (0.5.2):
1212+
- react-native-bottom-tabs (0.6.0):
12131213
- DoubleConversion
12141214
- glog
12151215
- RCT-Folly (= 2024.01.01.00)
@@ -1222,7 +1222,7 @@ PODS:
12221222
- React-graphics
12231223
- React-ImageManager
12241224
- React-jsi
1225-
- react-native-bottom-tabs/common (= 0.5.2)
1225+
- react-native-bottom-tabs/common (= 0.6.0)
12261226
- React-NativeModulesApple
12271227
- React-RCTFabric
12281228
- React-rendererdebug
@@ -1234,7 +1234,7 @@ PODS:
12341234
- SDWebImageSVGCoder (>= 1.7.0)
12351235
- SwiftUIIntrospect (~> 1.0)
12361236
- Yoga
1237-
- react-native-bottom-tabs/common (0.5.2):
1237+
- react-native-bottom-tabs/common (0.6.0):
12381238
- DoubleConversion
12391239
- glog
12401240
- RCT-Folly (= 2024.01.01.00)
@@ -1945,7 +1945,7 @@ SPEC CHECKSUMS:
19451945
React-logger: d79b704bf215af194f5213a6b7deec50ba8e6a9b
19461946
React-Mapbuffer: b982d5bba94a8bc073bda48f0d27c9b28417fae3
19471947
React-microtasksnativemodule: 8fa285fed833a04a754bf575f8ded65fc240b88d
1948-
react-native-bottom-tabs: 402d19b4a55e6e17c78736a9dd2592cd9864881e
1948+
react-native-bottom-tabs: c17ddaf86c160134349c6325e020c1f38ee5f743
19491949
react-native-safe-area-context: 73505107f7c673cd550a561aeb6271f152c483b6
19501950
React-nativeconfig: 8c83d992b9cc7d75b5abe262069eaeea4349f794
19511951
React-NativeModulesApple: b8465afc883f5bf3fe8bac3767e394d581a5f123
@@ -1982,8 +1982,8 @@ SPEC CHECKSUMS:
19821982
SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c
19831983
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
19841984
SwiftUIIntrospect: fee9aa07293ee280373a591e1824e8ddc869ba5d
1985-
Yoga: 055f92ad73f8c8600a93f0e25ac0b2344c3b07e6
1985+
Yoga: aa3df615739504eebb91925fc9c58b4922ea9a08
19861986

19871987
PODFILE CHECKSUM: 1c1dbca3e400ef935aa9a150cb2dcb58fb8c4536
19881988

1989-
COCOAPODS: 1.15.2
1989+
COCOAPODS: 1.14.3
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import * as React from 'react';
2+
import { View, Text, StyleSheet } from 'react-native';
3+
4+
type MusicControlProps = {
5+
bottomOffset: number;
6+
};
7+
8+
export const MusicControl: React.FC<MusicControlProps> = ({ bottomOffset }) => {
9+
return (
10+
<View
11+
style={[
12+
styles.musicControlContainer,
13+
{
14+
bottom: bottomOffset + 10,
15+
},
16+
]}
17+
>
18+
<View style={styles.musicControlContent}>
19+
<View style={styles.songInfo}>
20+
<Text style={styles.songTitle} numberOfLines={1}>
21+
Currently Playing Song
22+
</Text>
23+
</View>
24+
<View style={styles.controls}>
25+
<Text style={styles.controlButton}></Text>
26+
<Text style={styles.controlButton}></Text>
27+
</View>
28+
</View>
29+
</View>
30+
);
31+
};
32+
33+
const styles = StyleSheet.create({
34+
musicControlContainer: {
35+
position: 'absolute',
36+
left: 15,
37+
right: 15,
38+
borderRadius: 18,
39+
height: 55,
40+
backgroundColor: '#fff',
41+
shadowColor: '#000',
42+
shadowOffset: {
43+
width: 0,
44+
height: 4,
45+
},
46+
shadowOpacity: 0.25,
47+
shadowRadius: 5,
48+
elevation: 8,
49+
},
50+
musicControlContent: {
51+
flex: 1,
52+
flexDirection: 'row',
53+
alignItems: 'center',
54+
paddingHorizontal: 16,
55+
paddingVertical: 8,
56+
},
57+
songInfo: {
58+
flex: 1,
59+
marginRight: 16,
60+
},
61+
songTitle: {
62+
fontSize: 14,
63+
fontWeight: '600',
64+
color: '#000',
65+
},
66+
controls: {
67+
flexDirection: 'row',
68+
alignItems: 'center',
69+
},
70+
controlButton: {
71+
fontSize: 24,
72+
paddingHorizontal: 12,
73+
color: '#000',
74+
},
75+
});

apps/example/src/Screens/Contacts.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
Text,
1010
View,
1111
} from 'react-native';
12+
import { useBottomTabBarHeight } from 'react-native-bottom-tabs';
13+
import { MusicControl } from '../Components/MusicControl';
1214

1315
type Item = { name: string; number: number };
1416

@@ -97,6 +99,7 @@ export function Contacts({ query, ...rest }: Props) {
9799
console.log(Platform.OS, ' Rendering Contacts');
98100
const renderItem = ({ item }: { item: Item }) => <ContactItem item={item} />;
99101

102+
const tabBarHeight = useBottomTabBarHeight();
100103
const ref = React.useRef<FlatList>(null);
101104
useScrollToTop(ref);
102105

@@ -117,6 +120,7 @@ export function Contacts({ query, ...rest }: Props) {
117120
renderItem={renderItem}
118121
ItemSeparatorComponent={ItemSeparator}
119122
/>
123+
<MusicControl bottomOffset={tabBarHeight} />
120124
</SafeAreaView>
121125
);
122126
}

docs/docs/docs/guides/usage-with-react-navigation.mdx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,3 +265,38 @@ React.useEffect(() => {
265265
return unsubscribe;
266266
}, [navigation]);
267267
```
268+
269+
### Hooks
270+
271+
`useBottomTabBarHeight`
272+
273+
This hook returns the height of the bottom tab bar. This is useful when you want to place a component above the tab bar on iOS. It's not needed to offset the content of the screen as the navigator does it automatically.
274+
275+
```tsx
276+
import { useBottomTabBarHeight } from 'react-native-bottom-tabs';
277+
278+
function MyComponent() {
279+
const tabBarHeight = useBottomTabBarHeight();
280+
281+
return (
282+
<ScrollView>
283+
{/* Content */}
284+
<View style={{ bottom: tabBarHeight }} />
285+
</ScrollView>
286+
);
287+
}
288+
```
289+
290+
Alternatively, you can use the `BottomTabBarHeightContext` directly if you are using a class component or need it in a reusable component that can be used outside the bottom tab navigator:
291+
292+
```tsx
293+
import { BottomTabBarHeightContext } from 'react-native-bottom-tabs';
294+
295+
// ...
296+
297+
<BottomTabBarHeightContext.Consumer>
298+
{tabBarHeight => (
299+
/* render something */
300+
)}
301+
</BottomTabBarHeightContext.Consumer>
302+
```

packages/react-native-bottom-tabs/ios/PageSelectedEvent.swift renamed to packages/react-native-bottom-tabs/ios/Events/PageSelectedEvent.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,26 @@ import React
44
private var key: NSString
55
@objc public var viewTag: NSNumber
66
@objc public var coalescingKey: UInt16
7-
7+
88
@objc public var eventName: String {
99
return "onPageSelected"
1010
}
11-
11+
1212
@objc public init(reactTag: NSNumber, key: NSString, coalescingKey: UInt16) {
1313
self.viewTag = reactTag
1414
self.key = key
1515
self.coalescingKey = coalescingKey
1616
super.init()
1717
}
18-
18+
1919
@objc public func canCoalesce() -> Bool {
2020
return false
2121
}
22-
22+
2323
@objc public class func moduleDotMethod() -> String {
2424
return "RCTEventEmitter.receiveEvent"
2525
}
26-
26+
2727
@objc public func arguments() -> [Any] {
2828
return [
2929
viewTag,
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React
2+
3+
@objc public class TabBarMeasuredEvent: NSObject, RCTEvent {
4+
private var height: NSInteger
5+
@objc public var viewTag: NSNumber
6+
@objc public var coalescingKey: UInt16
7+
8+
@objc public var eventName: String {
9+
return "onTabBarMeasured"
10+
}
11+
12+
@objc public init(reactTag: NSNumber, height: NSInteger, coalescingKey: UInt16) {
13+
self.viewTag = reactTag
14+
self.height = height
15+
self.coalescingKey = coalescingKey
16+
super.init()
17+
}
18+
19+
@objc public func canCoalesce() -> Bool {
20+
return false
21+
}
22+
23+
@objc public class func moduleDotMethod() -> String {
24+
return "RCTEventEmitter.receiveEvent"
25+
}
26+
27+
@objc public func arguments() -> [Any] {
28+
return [
29+
viewTag,
30+
RCTNormalizeInputEventName(eventName) ?? eventName,
31+
[
32+
"height": height
33+
]
34+
]
35+
}
36+
}

packages/react-native-bottom-tabs/ios/TabLongPressedEvent.swift renamed to packages/react-native-bottom-tabs/ios/Events/TabLongPressedEvent.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,26 @@ protocol RCTEvent {}
88
private var key: NSString
99
@objc public var viewTag: NSNumber
1010
@objc public var coalescingKey: UInt16
11-
11+
1212
@objc public var eventName: String {
1313
return "onTabLongPress"
1414
}
15-
15+
1616
@objc public init(reactTag: NSNumber, key: NSString, coalescingKey: UInt16) {
1717
self.viewTag = reactTag
1818
self.key = key
1919
self.coalescingKey = coalescingKey
2020
super.init()
2121
}
22-
22+
2323
@objc public func canCoalesce() -> Bool {
2424
return false
2525
}
26-
26+
2727
@objc public class func moduleDotMethod() -> String {
2828
return "RCTEventEmitter.receiveEvent"
2929
}
30-
30+
3131
@objc public func arguments() -> [Any] {
3232
return [
3333
viewTag,

packages/react-native-bottom-tabs/ios/Fabric/RCTTabViewComponentView.mm

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,15 @@ - (void)onLongPressWithKey:(NSString *)key reactTag:(NSNumber *)reactTag {
223223
}
224224
}
225225

226+
- (void)onTabBarMeasuredWithHeight:(NSInteger)height reactTag:(NSNumber *)reactTag {
227+
auto eventEmitter = std::static_pointer_cast<const RNCTabViewEventEmitter>(_eventEmitter);
228+
if (eventEmitter) {
229+
eventEmitter->onTabBarMeasured(RNCTabViewEventEmitter::OnTabBarMeasured {
230+
.height = (int)height
231+
});
232+
}
233+
}
234+
226235
@end
227236

228237
Class<RCTComponentViewProtocol> RNCTabViewCls(void)

packages/react-native-bottom-tabs/ios/RCTTabViewViewManager.mm

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ - (instancetype)init
3030
RCT_EXPORT_VIEW_PROPERTY(items, NSArray)
3131
RCT_EXPORT_VIEW_PROPERTY(onPageSelected, RCTDirectEventBlock)
3232
RCT_EXPORT_VIEW_PROPERTY(onTabLongPress, RCTDirectEventBlock)
33+
RCT_EXPORT_VIEW_PROPERTY(onTabBarMeasured, RCTDirectEventBlock)
3334
RCT_EXPORT_VIEW_PROPERTY(selectedPage, NSString)
3435
RCT_EXPORT_VIEW_PROPERTY(tabViewStyle, NSString)
3536
RCT_EXPORT_VIEW_PROPERTY(icons, NSArray<RCTImageSource *>);
@@ -59,6 +60,11 @@ - (void)onPageSelectedWithKey:(NSString *)key reactTag:(NSNumber *)reactTag {
5960
[self.bridge.eventDispatcher sendEvent:event];
6061
}
6162

63+
- (void)onTabBarMeasuredWithHeight:(NSInteger)height reactTag:(NSNumber *)reactTag {
64+
auto event = [[TabBarMeasuredEvent alloc] initWithReactTag:reactTag height:height coalescingKey:_coalescingKey++];
65+
[self.bridge.eventDispatcher sendEvent:event];
66+
}
67+
6268
- (UIView *)view
6369
{
6470
return [[TabViewProvider alloc] initWithDelegate:self];

packages/react-native-bottom-tabs/ios/TabViewImpl.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,11 @@ struct RepresentableView: UIViewRepresentable {
5555
*/
5656
struct TabViewImpl: View {
5757
@ObservedObject var props: TabViewProps
58-
var onSelect: (_ key: String) -> Void
59-
var onLongPress: (_ key: String) -> Void
6058
@Weak var tabBar: UITabBar?
6159

60+
var onSelect: (_ key: String) -> Void
61+
var onLongPress: (_ key: String) -> Void
62+
var onTabBarMeasured: (_ height: Int) -> Void
6263

6364
var body: some View {
6465
TabView(selection: $props.selectedPage) {
@@ -83,6 +84,9 @@ struct TabViewImpl: View {
8384
#endif
8485
.introspectTabView(closure: { tabController in
8586
tabBar = tabController.tabBar
87+
onTabBarMeasured(
88+
Int(tabController.tabBar.frame.size.height)
89+
)
8690
})
8791
.configureAppearance(props: props, tabBar: tabBar)
8892
.tintColor(props.selectedActiveTintColor)

0 commit comments

Comments
 (0)