Skip to content

Commit 469fc70

Browse files
authored
feat(iOS): Add scroll edge effects (#3212)
## Description Closes software-mansion/react-native-screens-labs#419. This PR adds support for setting [UIScrollEdgeEffect](https://developer.apple.com/documentation/uikit/uiscrolledgeeffect) on ScrollViews that are first descendants of screens. For each edge, UIKit supports setting number of `style`s and a `hidden` toggle. Our API will merge those two into one enum: `automatic`, `hard`, `soft` and `hidden`. This still leaves us with 4 separate Screen props, for each edge, and another 4 for BottomTabsScreen. ## Changes Added {bottom,left,right,top}ScrollEdgeEffect prop for Screen and BottomTabsScreen. ## Test code and steps to reproduce https://github.com/user-attachments/assets/d3efc202-a502-46e4-a3a9-06e553401319 Use BottomTabsTest, add `scrollEdgeEffect: { bottom: 'hard' }` to Tab3, add `scrollEdgeEffect: { bottom: 'hard', top: 'hard' }` to a Screen in Tab4. ## Important Setting the effect on Screen inside BottomTabs won't work on Paper, because it is immediately overwritten by BottomTabs' values, will be fixed in the future.
1 parent 32050b9 commit 469fc70

27 files changed

+491
-3
lines changed

android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,26 @@ open class ScreenViewManager :
316316
value: String?,
317317
) = Unit
318318

319+
override fun setBottomScrollEdgeEffect(
320+
view: Screen?,
321+
value: String?,
322+
) = Unit
323+
324+
override fun setLeftScrollEdgeEffect(
325+
view: Screen?,
326+
value: String?,
327+
) = Unit
328+
329+
override fun setRightScrollEdgeEffect(
330+
view: Screen?,
331+
value: String?,
332+
) = Unit
333+
334+
override fun setTopScrollEdgeEffect(
335+
view: Screen?,
336+
value: String?,
337+
) = Unit
338+
319339
@ReactProp(name = "sheetAllowedDetents")
320340
override fun setSheetAllowedDetents(
321341
view: Screen,

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,26 @@ class TabScreenViewManager :
153153
value: Boolean,
154154
) = Unit
155155

156+
override fun setBottomScrollEdgeEffect(
157+
view: TabScreen?,
158+
value: String?,
159+
) = Unit
160+
161+
override fun setLeftScrollEdgeEffect(
162+
view: TabScreen?,
163+
value: String?,
164+
) = Unit
165+
166+
override fun setRightScrollEdgeEffect(
167+
view: TabScreen?,
168+
value: String?,
169+
) = Unit
170+
171+
override fun setTopScrollEdgeEffect(
172+
view: TabScreen?,
173+
value: String?,
174+
) = Unit
175+
156176
// Android specific
157177
@ReactProp(name = "tabBarItemBadgeTextColor", customType = "Color")
158178
override fun setTabBarItemBadgeTextColor(

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,18 @@ public void setProperty(T view, String propName, @Nullable Object value) {
8282
case "overrideScrollViewContentInsetAdjustmentBehavior":
8383
mViewManager.setOverrideScrollViewContentInsetAdjustmentBehavior(view, value == null ? true : (boolean) value);
8484
break;
85+
case "bottomScrollEdgeEffect":
86+
mViewManager.setBottomScrollEdgeEffect(view, (String) value);
87+
break;
88+
case "leftScrollEdgeEffect":
89+
mViewManager.setLeftScrollEdgeEffect(view, (String) value);
90+
break;
91+
case "rightScrollEdgeEffect":
92+
mViewManager.setRightScrollEdgeEffect(view, (String) value);
93+
break;
94+
case "topScrollEdgeEffect":
95+
mViewManager.setTopScrollEdgeEffect(view, (String) value);
96+
break;
8597
default:
8698
super.setProperty(view, propName, value);
8799
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,8 @@ public interface RNSBottomTabsScreenManagerInterface<T extends View> {
3535
void setSystemItem(T view, @Nullable String value);
3636
void setSpecialEffects(T view, @Nullable ReadableMap value);
3737
void setOverrideScrollViewContentInsetAdjustmentBehavior(T view, boolean value);
38+
void setBottomScrollEdgeEffect(T view, @Nullable String value);
39+
void setLeftScrollEdgeEffect(T view, @Nullable String value);
40+
void setRightScrollEdgeEffect(T view, @Nullable String value);
41+
void setTopScrollEdgeEffect(T view, @Nullable String value);
3842
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,18 @@ public void setProperty(T view, String propName, @Nullable Object value) {
121121
case "nativeBackButtonDismissalEnabled":
122122
mViewManager.setNativeBackButtonDismissalEnabled(view, value == null ? false : (boolean) value);
123123
break;
124+
case "bottomScrollEdgeEffect":
125+
mViewManager.setBottomScrollEdgeEffect(view, (String) value);
126+
break;
127+
case "leftScrollEdgeEffect":
128+
mViewManager.setLeftScrollEdgeEffect(view, (String) value);
129+
break;
130+
case "rightScrollEdgeEffect":
131+
mViewManager.setRightScrollEdgeEffect(view, (String) value);
132+
break;
133+
case "topScrollEdgeEffect":
134+
mViewManager.setTopScrollEdgeEffect(view, (String) value);
135+
break;
124136
default:
125137
super.setProperty(view, propName, value);
126138
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,8 @@ public interface RNSScreenManagerInterface<T extends View> {
4848
void setNavigationBarTranslucent(T view, boolean value);
4949
void setNavigationBarHidden(T view, boolean value);
5050
void setNativeBackButtonDismissalEnabled(T view, boolean value);
51+
void setBottomScrollEdgeEffect(T view, @Nullable String value);
52+
void setLeftScrollEdgeEffect(T view, @Nullable String value);
53+
void setRightScrollEdgeEffect(T view, @Nullable String value);
54+
void setTopScrollEdgeEffect(T view, @Nullable String value);
5155
}

ios/RCTConvert+RNScreens.mm

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,17 @@ + (NSMutableDictionary *)blurEffectsForIOSVersion
5353
RNSOptionalBooleanUndefined,
5454
integerValue)
5555

56+
RCT_ENUM_CONVERTER(
57+
RNSScrollEdgeEffect,
58+
(@{
59+
@"automatic" : @(RNSScrollEdgeEffectAutomatic),
60+
@"hard" : @(RNSScrollEdgeEffectHard),
61+
@"soft" : @(RNSScrollEdgeEffectSoft),
62+
@"hidden" : @(RNSScrollEdgeEffectHidden),
63+
}),
64+
RNSScrollEdgeEffectAutomatic,
65+
integerValue)
66+
5667
@end
5768

5869
#endif // !RCT_NEW_ARCH_ENABLED

ios/RNSConvert.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ namespace react = facebook::react;
5353

5454
+ (RNSBlurEffectStyle)RNSBlurEffectStyleFromCppEquivalent:(react::RNSScreenStackHeaderConfigBlurEffect)blurEffect;
5555

56+
+ (RNSScrollEdgeEffect)RNSScrollEdgeEffectFromScreenBottomScrollEdgeEffectCppEquivalent:
57+
(react::RNSScreenBottomScrollEdgeEffect)edgeEffect;
58+
+ (RNSScrollEdgeEffect)RNSScrollEdgeEffectFromScreenLeftScrollEdgeEffectCppEquivalent:
59+
(react::RNSScreenLeftScrollEdgeEffect)edgeEffect;
60+
+ (RNSScrollEdgeEffect)RNSScrollEdgeEffectFromScreenRightScrollEdgeEffectCppEquivalent:
61+
(react::RNSScreenRightScrollEdgeEffect)edgeEffect;
62+
+ (RNSScrollEdgeEffect)RNSScrollEdgeEffectFromScreenTopScrollEdgeEffectCppEquivalent:
63+
(react::RNSScreenTopScrollEdgeEffect)edgeEffect;
64+
5665
#endif // RCT_NEW_ARCH_ENABLED
5766

5867
/// This method fails (by assertion) when `blurEffect == RNSBlurEffectStyleNone` or `blurEffect ==

ios/RNSConvert.mm

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,48 @@ + (RNSScreenSwipeDirection)RNSScreenSwipeDirectionFromCppEquivalent:(react::RNSS
133133
}
134134
}
135135

136+
#define SWITCH_EDGE_EFFECT(X) \
137+
switch (edgeEffect) { \
138+
using enum react::X; \
139+
case Automatic: \
140+
return RNSScrollEdgeEffectAutomatic; \
141+
case Hard: \
142+
return RNSScrollEdgeEffectHard; \
143+
case Soft: \
144+
return RNSScrollEdgeEffectSoft; \
145+
case Hidden: \
146+
return RNSScrollEdgeEffectHidden; \
147+
default: \
148+
RCTLogError(@"[RNScreens] unsupported edge effect"); \
149+
return RNSScrollEdgeEffectAutomatic; \
150+
}
151+
152+
+ (RNSScrollEdgeEffect)RNSScrollEdgeEffectFromScreenBottomScrollEdgeEffectCppEquivalent:
153+
(react::RNSScreenBottomScrollEdgeEffect)edgeEffect
154+
{
155+
SWITCH_EDGE_EFFECT(RNSScreenBottomScrollEdgeEffect);
156+
}
157+
158+
+ (RNSScrollEdgeEffect)RNSScrollEdgeEffectFromScreenLeftScrollEdgeEffectCppEquivalent:
159+
(react::RNSScreenLeftScrollEdgeEffect)edgeEffect
160+
{
161+
SWITCH_EDGE_EFFECT(RNSScreenLeftScrollEdgeEffect);
162+
}
163+
164+
+ (RNSScrollEdgeEffect)RNSScrollEdgeEffectFromScreenRightScrollEdgeEffectCppEquivalent:
165+
(react::RNSScreenRightScrollEdgeEffect)edgeEffect
166+
{
167+
SWITCH_EDGE_EFFECT(RNSScreenRightScrollEdgeEffect);
168+
}
169+
170+
+ (RNSScrollEdgeEffect)RNSScrollEdgeEffectFromScreenTopScrollEdgeEffectCppEquivalent:
171+
(react::RNSScreenTopScrollEdgeEffect)edgeEffect
172+
{
173+
SWITCH_EDGE_EFFECT(RNSScreenTopScrollEdgeEffect);
174+
}
175+
176+
#undef SWITCH_EDGE_EFFECT
177+
136178
+ (NSArray<NSNumber *> *)detentFractionsArrayFromVector:(const std::vector<react::Float> &)detents
137179
{
138180
auto array = [NSMutableArray<NSNumber *> arrayWithCapacity:detents.size()];

ios/RNSEnums.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ typedef NS_ENUM(NSInteger, RNSActivityState) {
3737
RNSActivityStateOnTop = 2
3838
};
3939

40+
typedef NS_ENUM(NSInteger, RNSScrollEdgeEffect) {
41+
RNSScrollEdgeEffectAutomatic,
42+
RNSScrollEdgeEffectHard,
43+
RNSScrollEdgeEffectSoft,
44+
RNSScrollEdgeEffectHidden,
45+
};
46+
4047
typedef NS_ENUM(NSInteger, RNSStatusBarStyle) {
4148
RNSStatusBarStyleAuto,
4249
RNSStatusBarStyleInverted,

0 commit comments

Comments
 (0)