Skip to content

Commit a3a4a09

Browse files
committed
fix: measure available space for views on iOS
1 parent fc096de commit a3a4a09

File tree

8 files changed

+102
-14
lines changed

8 files changed

+102
-14
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React
2+
3+
@objc public class OnNativeLayoutEvent: NSObject, RCTEvent {
4+
private var size: CGSize
5+
@objc public var viewTag: NSNumber
6+
@objc public var coalescingKey: UInt16
7+
8+
@objc public var eventName: String {
9+
return "onNativeLayout"
10+
}
11+
12+
@objc public init(reactTag: NSNumber, size: CGSize, coalescingKey: UInt16) {
13+
self.viewTag = reactTag
14+
self.size = size
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+
"width": size.width,
33+
"height": size.height
34+
]
35+
]
36+
}
37+
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,23 @@ extension View {
5050
customize: closure
5151
)
5252
}
53+
54+
55+
@MainActor
56+
@ViewBuilder
57+
func measureView(onLayout: @escaping (_ size: CGSize) -> Void) -> some View {
58+
self
59+
.background (
60+
GeometryReader { geometry in
61+
Color.clear
62+
.onChange(of: geometry.size) { newValue in
63+
onLayout(newValue)
64+
}
65+
.onAppear {
66+
onLayout(geometry.size)
67+
}
68+
}
69+
.ignoresSafeArea(.container, edges: .vertical)
70+
)
71+
}
5372
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,16 @@ - (void)onTabBarMeasuredWithHeight:(NSInteger)height reactTag:(NSNumber *)reactT
232232
}
233233
}
234234

235+
- (void)onLayoutWithSize:(CGSize)size reactTag:(NSNumber *)reactTag {
236+
auto eventEmitter = std::static_pointer_cast<const RNCTabViewEventEmitter>(_eventEmitter);
237+
if (eventEmitter) {
238+
eventEmitter->onNativeLayout(RNCTabViewEventEmitter::OnNativeLayout {
239+
.height = size.height,
240+
.width = size.width
241+
});
242+
}
243+
}
244+
235245
@end
236246

237247
Class<RCTComponentViewProtocol> RNCTabViewCls(void)

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ - (void)onTabBarMeasuredWithHeight:(NSInteger)height reactTag:(NSNumber *)reactT
6565
[self.bridge.eventDispatcher sendEvent:event];
6666
}
6767

68+
- (void)onLayoutWithSize:(CGSize)size reactTag:(NSNumber *)reactTag {
69+
auto event = [[OnNativeLayoutEvent alloc] initWithReactTag:reactTag size:size coalescingKey:_coalescingKey++];
70+
[self.bridge.eventDispatcher sendEvent:event];
71+
}
72+
6873
- (UIView *)view
6974
{
7075
return [[TabViewProvider alloc] initWithDelegate:self];

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,17 @@ struct TabViewImpl: View {
5959

6060
var onSelect: (_ key: String) -> Void
6161
var onLongPress: (_ key: String) -> Void
62+
var onLayout: (_ size: CGSize) -> Void
6263
var onTabBarMeasured: (_ height: Int) -> Void
6364

6465
var body: some View {
6566
TabView(selection: $props.selectedPage) {
6667
ForEach(props.children.indices, id: \.self) { index in
6768
renderTabItem(at: index)
6869
}
70+
.measureView(onLayout: { size in
71+
onLayout(size)
72+
})
6973
}
7074
#if !os(tvOS)
7175
.onTabItemEvent({ index, isLongPress in
@@ -326,13 +330,10 @@ extension View {
326330
) -> some View {
327331
if flag {
328332
self
329-
.ignoresSafeArea(.container, edges: .all)
330-
.frame(idealWidth: frame.width, idealHeight: frame.height)
333+
.ignoresSafeArea(.container, edges: .vertical)
331334
} else {
332335
self
333-
.ignoresSafeArea(.container, edges: .horizontal)
334336
.ignoresSafeArea(.container, edges: .bottom)
335-
.frame(idealWidth: frame.width, idealHeight: frame.height)
336337
}
337338
}
338339

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

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ import React
44
import SDWebImage
55
import SDWebImageSVGCoder
66

7-
@objc public final class TabInfo: NSObject {
8-
@objc public let key: String
9-
@objc public let title: String
10-
@objc public let badge: String
11-
@objc public let sfSymbol: String
12-
@objc public let activeTintColor: UIColor?
13-
@objc public let hidden: Bool
14-
15-
@objc
7+
@objcMembers
8+
public final class TabInfo: NSObject {
9+
public let key: String
10+
public let title: String
11+
public let badge: String
12+
public let sfSymbol: String
13+
public let activeTintColor: UIColor?
14+
public let hidden: Bool
15+
1616
public init(
1717
key: String,
1818
title: String,
@@ -35,6 +35,7 @@ import SDWebImageSVGCoder
3535
func onPageSelected(key: String, reactTag: NSNumber?)
3636
func onLongPress(key: String, reactTag: NSNumber?)
3737
func onTabBarMeasured(height: Int, reactTag: NSNumber?)
38+
func onLayout(size: CGSize, reactTag: NSNumber?)
3839
}
3940

4041
@objc public class TabViewProvider: UIView {
@@ -184,6 +185,8 @@ import SDWebImageSVGCoder
184185
self.delegate?.onPageSelected(key: key, reactTag: self.reactTag)
185186
} onLongPress: { key in
186187
self.delegate?.onLongPress(key: key, reactTag: self.reactTag)
188+
} onLayout: { size in
189+
self.delegate?.onLayout(size: size, reactTag: self.reactTag)
187190
} onTabBarMeasured: { height in
188191
self.delegate?.onTabBarMeasured(height: height, reactTag: self.reactTag)
189192
})

packages/react-native-bottom-tabs/src/TabView.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,9 @@ const TabView = <Route extends BaseRoute>({
171171
// @ts-ignore
172172
const focusedKey = navigationState.routes[navigationState.index].key;
173173
const [tabBarHeight, setTabBarHeight] = React.useState<number | undefined>(0);
174+
const [measuredDimensions, setMeasuredDimensions] = React.useState<
175+
{ width: number; height: number } | undefined
176+
>();
174177

175178
const trimmedRoutes = React.useMemo(() => {
176179
if (
@@ -273,6 +276,9 @@ const TabView = <Route extends BaseRoute>({
273276
onTabBarMeasured={({ nativeEvent: { height } }) => {
274277
setTabBarHeight(height);
275278
}}
279+
onNativeLayout={({ nativeEvent: { width, height } }) => {
280+
setMeasuredDimensions({ width, height });
281+
}}
276282
hapticFeedbackEnabled={hapticFeedbackEnabled}
277283
activeTintColor={activeTintColor}
278284
inactiveTintColor={inactiveTintColor}
@@ -310,7 +316,7 @@ const TabView = <Route extends BaseRoute>({
310316
style={
311317
Platform.OS === 'android'
312318
? [StyleSheet.absoluteFill, { zIndex, opacity }]
313-
: styles.fullWidth
319+
: [{ position: 'absolute' }, measuredDimensions]
314320
}
315321
>
316322
{renderScene({

packages/react-native-bottom-tabs/src/TabViewNativeComponent.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNati
22
import type { ColorValue, ProcessedColorValue, ViewProps } from 'react-native';
33
import type {
44
DirectEventHandler,
5+
Double,
56
Int32,
67
WithDefault,
78
} from 'react-native/Libraries/Types/CodegenTypes';
@@ -16,6 +17,11 @@ export type OnTabBarMeasured = Readonly<{
1617
height: Int32;
1718
}>;
1819

20+
export type OnNativeLayout = Readonly<{
21+
width: Double;
22+
height: Double;
23+
}>;
24+
1925
export type TabViewItems = ReadonlyArray<{
2026
key: string;
2127
title: string;
@@ -31,6 +37,7 @@ export interface TabViewProps extends ViewProps {
3137
onPageSelected?: DirectEventHandler<OnPageSelectedEventData>;
3238
onTabLongPress?: DirectEventHandler<OnPageSelectedEventData>;
3339
onTabBarMeasured?: DirectEventHandler<OnTabBarMeasured>;
40+
onNativeLayout?: DirectEventHandler<OnNativeLayout>;
3441
icons?: ReadonlyArray<ImageSource>;
3542
labeled?: boolean;
3643
sidebarAdaptable?: boolean;

0 commit comments

Comments
 (0)