Skip to content

Commit 1b6e5c2

Browse files
kligarskikkafar
authored andcommitted
feat(iOS, Tabs): revert contentInsetAdjustmentBehavior to automatic for ScrollViews (#173)
## Description By default, `react-native` sets `contentInsetAdjustmentBehavior` for ScrollView to `'never'` ([docs](https://reactnative.dev/docs/scrollview#contentinsetadjustmentbehavior-ios), [code](https://github.com/facebook/react-native/blob/ce306aca34636b3928837d427d386083050729fb/packages/react-native/React/Views/ScrollView/RCTScrollView.m#L377)). In order to make many of navigation controllers work properly (including tab bar controller), you need to change it back to `'automatic'` (which is [UIKit default](https://developer.apple.com/documentation/uikit/uiscrollview/contentinsetadjustmentbehavior-swift.property?language=objc)). In this PR, we add logic to automatically revert `contentInsetAdjustmentBehavior` back to `'automatic'`. Closes software-mansion/react-native-screens-labs#166. ## Changes - add `RNSScrollViewHelper` with `overrideScrollViewBehaviorInFirstDescendantChainFrom:(UIView *)view` - use it in `RNSTabsScreenViewController` and `RNSScreen` - add prop to JS ## Test code and steps to reproduce Open Tab3. You can also manually disable the behavior via prop. ## Checklist - [x] Included code example that can be used to test this change - [ ] Ensured that CI passes
1 parent 29ae3bd commit 1b6e5c2

18 files changed

+177
-16
lines changed

android/src/main/java/com/swmansion/rnscreens/gamma/tabs/TabScreenViewManager.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@ class TabScreenViewManager :
146146
value: ReadableMap?,
147147
) = Unit
148148

149+
override fun setOverrideScrollViewContentInsetAdjustmentBehavior(
150+
view: TabScreen,
151+
value: Boolean
152+
) = Unit
153+
149154
companion object {
150155
const val REACT_CLASS = "RNSBottomTabsScreen"
151156
}

android/src/paper/java/com/facebook/react/viewmanagers/RNSBottomTabsManagerDelegate.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ public void setProperty(T view, String propName, @Nullable Object value) {
6363
case "tabBarItemIconColorActive":
6464
mViewManager.setTabBarItemIconColorActive(view, ColorPropConverter.getColor(value, view.getContext()));
6565
break;
66+
case "tabBarItemTitleFontSizeActive":
67+
mViewManager.setTabBarItemTitleFontSizeActive(view, value == null ? 0f : ((Double) value).floatValue());
68+
break;
6669
case "controlNavigationStateInJS":
6770
mViewManager.setControlNavigationStateInJS(view, value == null ? false : (boolean) value);
6871
break;

android/src/paper/java/com/facebook/react/viewmanagers/RNSBottomTabsManagerInterface.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,6 @@ public interface RNSBottomTabsManagerInterface<T extends View> {
2828
void setTabBarItemBadgeBackgroundColor(T view, @Nullable Integer value);
2929
void setTabBarItemTitleFontColorActive(T view, @Nullable Integer value);
3030
void setTabBarItemIconColorActive(T view, @Nullable Integer value);
31+
void setTabBarItemTitleFontSizeActive(T view, float value);
3132
void setControlNavigationStateInJS(T view, boolean value);
3233
}

android/src/paper/java/com/facebook/react/viewmanagers/RNSBottomTabsScreenManagerDelegate.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,18 @@ public void setProperty(T view, String propName, @Nullable Object value) {
6969
case "selectedIconSFSymbolName":
7070
mViewManager.setSelectedIconSFSymbolName(view, value == null ? null : (String) value);
7171
break;
72+
case "iconResourceName":
73+
mViewManager.setIconResourceName(view, value == null ? null : (String) value);
74+
break;
7275
case "badgeValue":
7376
mViewManager.setBadgeValue(view, value == null ? null : (String) value);
7477
break;
7578
case "specialEffects":
7679
mViewManager.setSpecialEffects(view, (ReadableMap) value);
7780
break;
81+
case "overrideScrollViewContentInsetAdjustmentBehavior":
82+
mViewManager.setOverrideScrollViewContentInsetAdjustmentBehavior(view, value == null ? true : (boolean) value);
83+
break;
7884
default:
7985
super.setProperty(view, propName, value);
8086
}

android/src/paper/java/com/facebook/react/viewmanagers/RNSBottomTabsScreenManagerInterface.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ public interface RNSBottomTabsScreenManagerInterface<T extends View> {
3030
void setTitle(T view, @Nullable String value);
3131
void setIconSFSymbolName(T view, @Nullable String value);
3232
void setSelectedIconSFSymbolName(T view, @Nullable String value);
33+
void setIconResourceName(T view, @Nullable String value);
3334
void setBadgeValue(T view, @Nullable String value);
3435
void setSpecialEffects(T view, @Nullable ReadableMap value);
36+
void setOverrideScrollViewContentInsetAdjustmentBehavior(T view, boolean value);
3537
}

apps/src/tests/TestBottomTabs/tabs/Tab3.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { ScrollView, Text } from 'react-native';
55
export function Tab3() {
66
return (
77
<ScrollView
8-
contentInsetAdjustmentBehavior="automatic"
98
contentContainerStyle={{
109
width: '100%',
1110
height: 'auto',

apps/src/tests/TestBottomTabs/tabs/Tab4.tsx

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export function LongText() {
7272

7373
function Screen1({ navigation }: StackNavigationProp) {
7474
return (
75-
<ScrollView contentInsetAdjustmentBehavior="automatic">
75+
<ScrollView>
7676
<Button
7777
title="Go to screen 2"
7878
onPress={() => navigation.push('Screen2')}
@@ -84,7 +84,7 @@ function Screen1({ navigation }: StackNavigationProp) {
8484

8585
function Screen2({ navigation }: StackNavigationProp) {
8686
return (
87-
<ScrollView contentInsetAdjustmentBehavior="automatic">
87+
<ScrollView>
8888
<Button
8989
title="Go to screen 3"
9090
onPress={() => navigation.push('Screen3')}
@@ -94,9 +94,13 @@ function Screen2({ navigation }: StackNavigationProp) {
9494
);
9595
}
9696

97-
function Screen3() {
97+
function Screen3({ navigation }: StackNavigationProp) {
9898
return (
99-
<ScrollView contentInsetAdjustmentBehavior="automatic">
99+
<ScrollView>
100+
<Button
101+
title="Go to screen 3"
102+
onPress={() => navigation.push('Screen3')}
103+
/>
100104
<LongText />
101105
</ScrollView>
102106
);
@@ -106,9 +110,23 @@ export function Tab4() {
106110
return (
107111
<NavigationContainer>
108112
<Stack.Navigator>
109-
<Stack.Screen name="Screen1" component={Screen1} />
110-
<Stack.Screen name="Screen2" component={Screen2} />
111-
<Stack.Screen name="Screen3" component={Screen3} />
113+
<Stack.Screen
114+
name="Screen1"
115+
component={Screen1}
116+
options={{ headerTransparent: true }}
117+
/>
118+
<Stack.Screen
119+
name="Screen2"
120+
component={Screen2}
121+
options={{
122+
headerLargeTitle: true,
123+
}}
124+
/>
125+
<Stack.Screen
126+
name="Screen3"
127+
component={Screen3}
128+
options={{ headerTransparent: true }}
129+
/>
112130
</Stack.Navigator>
113131
</NavigationContainer>
114132
);

ios/RNSScreen.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#import "RNSEnums.h"
55
#import "RNSScreenContainer.h"
66
#import "RNSScreenContentWrapper.h"
7+
#import "RNSScrollViewBehaviorOverriding.h"
78

89
#if RCT_NEW_ARCH_ENABLED
910
#import <React/RCTViewComponentView.h>
@@ -55,7 +56,7 @@ namespace react = facebook::react;
5556
#else
5657
RCTView
5758
#endif
58-
<RNSScreenContentWrapperDelegate>
59+
<RNSScreenContentWrapperDelegate, RNSScrollViewBehaviorOverriding>
5960

6061
@property (nonatomic) BOOL fullScreenSwipeEnabled;
6162
@property (nonatomic) BOOL fullScreenSwipeShadowEnabled;

ios/RNSScreen.mm

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#import "RNSScreenStack.h"
3333
#import "RNSScreenStackHeaderConfig.h"
3434
#import "RNSTabBarController.h"
35+
#import "RNSScrollViewHelper.h"
3536

3637
#import "RNSDefines.h"
3738
#import "UIView+RNSUtility.h"
@@ -1177,6 +1178,36 @@ - (void)setBounds:(CGRect)bounds
11771178
[super setBounds:bounds];
11781179
}
11791180

1181+
#pragma mark - RNSScrollViewBehaviorOverriding
1182+
1183+
- (BOOL)shouldOverrideScrollViewContentInsetAdjustmentBehavior
1184+
{
1185+
// RNSScreenView does not have a property to control this behavior.
1186+
// It looks for parent that conforms to RNSScrollViewBehaviorOverriding to determine
1187+
// if it should override ScrollView's behavior.
1188+
1189+
// As this method is called when RNSScreen willMoveToParentViewController
1190+
// and view does not have superView yet, we need to use reactSuperViews.
1191+
UIView *parent = [self reactSuperview];
1192+
1193+
while (parent != nil) {
1194+
if ([parent respondsToSelector:@selector(shouldOverrideScrollViewContentInsetAdjustmentBehavior)]) {
1195+
id<RNSScrollViewBehaviorOverriding> overrideProvider = static_cast<id<RNSScrollViewBehaviorOverriding>>(parent);
1196+
return [overrideProvider shouldOverrideScrollViewContentInsetAdjustmentBehavior];
1197+
}
1198+
parent = [parent reactSuperview];
1199+
}
1200+
1201+
return NO;
1202+
}
1203+
1204+
- (void)overrideScrollViewBehaviorInFirstDescendantChainIfNeeded
1205+
{
1206+
if ([self shouldOverrideScrollViewContentInsetAdjustmentBehavior]) {
1207+
[RNSScrollViewHelper overrideScrollViewBehaviorInFirstDescendantChainFrom:self];
1208+
}
1209+
}
1210+
11801211
#pragma mark - Fabric specific
11811212
#ifdef RCT_NEW_ARCH_ENABLED
11821213

@@ -1684,6 +1715,8 @@ - (void)willMoveToParentViewController:(UIViewController *)parent
16841715
if (responder != nil) {
16851716
_previousFirstResponder = responder;
16861717
}
1718+
} else {
1719+
[self.screenView overrideScrollViewBehaviorInFirstDescendantChainIfNeeded];
16871720
}
16881721
}
16891722

ios/RNSScrollViewBehaviorOverriding.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#include <UIKit/UIKit.h>
2+
3+
NS_ASSUME_NONNULL_BEGIN
4+
5+
/**
6+
* Views that require ScrollView contentInsetAdjustmentBehavior overriding should conform to this protocol.
7+
*/
8+
@protocol RNSScrollViewBehaviorOverriding
9+
10+
/**
11+
* Returns whether view should override contentInsetAdjustmentBehavior for first ScrollView in first descendant chain.
12+
* It can be a property or method involving some logic to determine if ScrollView's behavior should be overriden.
13+
*/
14+
- (BOOL)shouldOverrideScrollViewContentInsetAdjustmentBehavior;
15+
16+
/**
17+
* Overrides contentInsetAdjustmentBehavior for first ScrollView in first descendant chain
18+
* if overrideScrollViewContentInsetAdjustmentBehavior returns true.
19+
*/
20+
- (void)overrideScrollViewBehaviorInFirstDescendantChainIfNeeded;
21+
22+
@end
23+
24+
NS_ASSUME_NONNULL_END

0 commit comments

Comments
 (0)