Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions ios/RNNComponentPresenter.mm
Original file line number Diff line number Diff line change
@@ -1,9 +1,83 @@
#import "RNNComponentPresenter.h"
#import "RNNComponentViewController.h"
#import "RNNScrollEdgeEffectOptions.h"
#import "TopBarTitlePresenter.h"
#import "UITabBarController+RNNOptions.h"
#import "UIViewController+RNNOptions.h"

static NSNumber *RNNScrollEdgeEffectStyleFromString(NSString *value) {
if ([value isEqualToString:@"hard"])
return @(2);
if ([value isEqualToString:@"soft"])
return @(1);
return @(0); // automatic
}

static RNNScrollEdgeOptions *RNNScrollEdgeOptionsForKey(RNNScrollEdgeEffectOptions *options,
NSString *edgeKey) {
if ([edgeKey isEqualToString:@"topEdgeEffect"])
return options.top;
if ([edgeKey isEqualToString:@"bottomEdgeEffect"])
return options.bottom;
if ([edgeKey isEqualToString:@"leftEdgeEffect"])
return options.left;
if ([edgeKey isEqualToString:@"rightEdgeEffect"])
return options.right;
return nil;
}

static BOOL RNNScrollEdgeEffectHasAnyValue(RNNScrollEdgeEffectOptions *options) {
if (!options)
return NO;
if (options.hidden.hasValue || options.style.hasValue)
return YES;
NSArray<RNNScrollEdgeOptions *> *edges =
@[ options.top ?: [RNNScrollEdgeOptions new], options.bottom ?: [RNNScrollEdgeOptions new],
options.left ?: [RNNScrollEdgeOptions new], options.right ?: [RNNScrollEdgeOptions new] ];
for (RNNScrollEdgeOptions *edge in edges) {
if (edge.hidden.hasValue || edge.style.hasValue)
return YES;
}
return NO;
}

static void RNNApplyScrollEdgeEffectToScrollView(UIScrollView *scrollView,
RNNScrollEdgeEffectOptions *options) {
if (![scrollView respondsToSelector:NSSelectorFromString(@"topEdgeEffect")])
return;
NSArray<NSString *> *edgeKeys =
@[ @"topEdgeEffect", @"bottomEdgeEffect", @"leftEdgeEffect", @"rightEdgeEffect" ];
for (NSString *key in edgeKeys) {
id effect = [scrollView valueForKey:key];
if (!effect)
continue;
RNNScrollEdgeOptions *perEdge = RNNScrollEdgeOptionsForKey(options, key);

Bool *hidden = (perEdge.hidden.hasValue) ? perEdge.hidden : options.hidden;
Text *style = (perEdge.style.hasValue) ? perEdge.style : options.style;

if (hidden.hasValue &&
[effect respondsToSelector:NSSelectorFromString(@"setHidden:")]) {
[effect setValue:@([hidden withDefault:NO]) forKey:@"hidden"];
}
if (style.hasValue &&
[effect respondsToSelector:NSSelectorFromString(@"setStyle:")]) {
[effect setValue:RNNScrollEdgeEffectStyleFromString(style.get) forKey:@"style"];
}
}
}

static void RNNApplyScrollEdgeEffectToView(UIView *view, RNNScrollEdgeEffectOptions *options) {
if (!RNNScrollEdgeEffectHasAnyValue(options))
return;
if ([view isKindOfClass:[UIScrollView class]]) {
RNNApplyScrollEdgeEffectToScrollView((UIScrollView *)view, options);
}
for (UIView *subview in view.subviews) {
RNNApplyScrollEdgeEffectToView(subview, options);
}
}

@implementation RNNComponentPresenter {
TopBarTitlePresenter *_topBarTitlePresenter;
RNNButtonsPresenter *_buttonsPresenter;
Expand Down Expand Up @@ -34,6 +108,11 @@ - (void)componentWillAppear {
- (void)componentDidAppear {
[_topBarTitlePresenter componentDidAppear];
[_buttonsPresenter componentDidAppear];

RNNComponentViewController *viewController = self.boundViewController;
RNNNavigationOptions *withDefault =
[viewController.options withDefault:[self defaultOptions]];
RNNApplyScrollEdgeEffectToView(viewController.view, withDefault.scrollEdgeEffect);
}

- (void)componentDidDisappear {
Expand Down Expand Up @@ -102,6 +181,8 @@ - (void)applyOptions:(RNNNavigationOptions *)options {
defaultDisabledColor:withDefault.topBar.rightButtonDisabledColor
animated:[withDefault.topBar.animateRightButtons withDefault:NO]];
}

RNNApplyScrollEdgeEffectToView(viewController.view, withDefault.scrollEdgeEffect);
}

- (void)applyOptionsOnInit:(RNNNavigationOptions *)options {
Expand Down Expand Up @@ -236,6 +317,10 @@ - (void)mergeOptions:(RNNNavigationOptions *)mergeOptions
}

[_topBarTitlePresenter mergeOptions:mergeOptions.topBar resolvedOptions:withDefault.topBar];

if (RNNScrollEdgeEffectHasAnyValue(mergeOptions.scrollEdgeEffect)) {
RNNApplyScrollEdgeEffectToView(viewController.view, mergeOptions.scrollEdgeEffect);
}
}

- (void)renderComponents:(RNNNavigationOptions *)options
Expand Down
2 changes: 2 additions & 0 deletions ios/RNNNavigationOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#import "RNNModalOptions.h"
#import "RNNOverlayOptions.h"
#import "RNNPreviewOptions.h"
#import "RNNScrollEdgeEffectOptions.h"
#import "RNNSharedElementAnimationOptions.h"
#import "RNNSideMenuOptions.h"
#import "RNNSplitViewOptions.h"
Expand Down Expand Up @@ -34,6 +35,7 @@ extern const NSInteger BLUR_TOPBAR_TAG;
@property(nonatomic, strong) RNNModalOptions *modal;
@property(nonatomic, strong) DeprecationOptions *deprecations;
@property(nonatomic, strong) WindowOptions *window;
@property(nonatomic, strong) RNNScrollEdgeEffectOptions *scrollEdgeEffect;

@property(nonatomic, strong) Bool *popGesture;
@property(nonatomic, strong) Bool *navigationButtonEventOnSwipeBack;
Expand Down
4 changes: 4 additions & 0 deletions ios/RNNNavigationOptions.mm
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ - (instancetype)initWithDict:(NSDictionary *)dict {
self.modal = [[RNNModalOptions alloc] initWithDict:dict[@"modal"]];
self.deprecations = [[DeprecationOptions alloc] initWithDict:dict[@"deprecations"]];
self.window = [[WindowOptions alloc] initWithDict:dict[@"window"]];
self.scrollEdgeEffect =
[[RNNScrollEdgeEffectOptions alloc] initWithDict:dict[@"scrollEdgeEffect"]];

self.popGesture = [[Bool alloc] initWithValue:dict[@"popGesture"]];
self.navigationButtonEventOnSwipeBack = [[Bool alloc] initWithValue:dict[@"navigationButtonEventOnSwipeBack"]];
Expand Down Expand Up @@ -71,6 +73,7 @@ - (RNNNavigationOptions *)mergeOptions:(RNNNavigationOptions *)options {
[result.modal mergeOptions:options.modal];
[result.deprecations mergeOptions:options.deprecations];
[result.window mergeOptions:options.window];
[result.scrollEdgeEffect mergeOptions:options.scrollEdgeEffect];

if (options.popGesture.hasValue)
result.popGesture = options.popGesture;
Expand Down Expand Up @@ -105,6 +108,7 @@ - (RNNNavigationOptions *)copy {
[newOptions.modal mergeOptions:self.modal];
[newOptions.deprecations mergeOptions:self.deprecations];
[newOptions.window mergeOptions:self.window];
[newOptions.scrollEdgeEffect mergeOptions:self.scrollEdgeEffect];

newOptions.popGesture = self.popGesture;
newOptions.navigationButtonEventOnSwipeBack = self.navigationButtonEventOnSwipeBack;
Expand Down
20 changes: 20 additions & 0 deletions ios/RNNScrollEdgeEffectOptions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#import "RNNOptions.h"

@interface RNNScrollEdgeOptions : RNNOptions

@property(nonatomic, strong) Bool *hidden;
@property(nonatomic, strong) Text *style;

@end

@interface RNNScrollEdgeEffectOptions : RNNOptions

@property(nonatomic, strong) Bool *hidden;
@property(nonatomic, strong) Text *style;

@property(nonatomic, strong) RNNScrollEdgeOptions *top;
@property(nonatomic, strong) RNNScrollEdgeOptions *bottom;
@property(nonatomic, strong) RNNScrollEdgeOptions *left;
@property(nonatomic, strong) RNNScrollEdgeOptions *right;

@end
45 changes: 45 additions & 0 deletions ios/RNNScrollEdgeEffectOptions.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#import "RNNScrollEdgeEffectOptions.h"

@implementation RNNScrollEdgeOptions

- (instancetype)initWithDict:(NSDictionary *)dict {
self = [super initWithDict:dict];
self.hidden = [BoolParser parse:dict key:@"hidden"];
self.style = [TextParser parse:dict key:@"style"];
return self;
}

- (void)mergeOptions:(RNNScrollEdgeOptions *)options {
if (options.hidden.hasValue)
self.hidden = options.hidden;
if (options.style.hasValue)
self.style = options.style;
}

@end

@implementation RNNScrollEdgeEffectOptions

- (instancetype)initWithDict:(NSDictionary *)dict {
self = [super initWithDict:dict];
self.hidden = [BoolParser parse:dict key:@"hidden"];
self.style = [TextParser parse:dict key:@"style"];
self.top = [[RNNScrollEdgeOptions alloc] initWithDict:dict[@"top"]];
self.bottom = [[RNNScrollEdgeOptions alloc] initWithDict:dict[@"bottom"]];
self.left = [[RNNScrollEdgeOptions alloc] initWithDict:dict[@"left"]];
self.right = [[RNNScrollEdgeOptions alloc] initWithDict:dict[@"right"]];
return self;
}

- (void)mergeOptions:(RNNScrollEdgeEffectOptions *)options {
if (options.hidden.hasValue)
self.hidden = options.hidden;
if (options.style.hasValue)
self.style = options.style;
[self.top mergeOptions:options.top];
[self.bottom mergeOptions:options.bottom];
[self.left mergeOptions:options.left];
[self.right mergeOptions:options.right];
}

@end
55 changes: 55 additions & 0 deletions src/interfaces/Options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1721,10 +1721,65 @@ setRoot: {
* #### (iOS specific)
*/
window?: WindowOptions;
/**
* Show / hide or change the style of the iOS 26 scroll edge effect on every
* UIScrollView contained in the screen.
* #### (iOS 26+ specific)
*/
scrollEdgeEffect?: OptionsScrollEdgeEffect;
/**
* Enable or disable automatically blurring focused input, dismissing keyboard on unmount
* #### (Android specific)
* @default false
*/
blurOnUnmount?: boolean;
}

export interface OptionsScrollEdge {
/**
* Hide the scroll edge effect on this edge.
* #### (iOS 26+ specific)
*/
hidden?: boolean;
/**
* Style of the scroll edge effect on this edge.
* #### (iOS 26+ specific)
* @default 'automatic'
*/
style?: 'automatic' | 'soft' | 'hard';
}

export interface OptionsScrollEdgeEffect {
/**
* Hide the scroll edge effect on all four edges of contained scroll views.
* Per-edge values (top / bottom / left / right) take precedence over this.
* #### (iOS 26+ specific)
*/
hidden?: boolean;
/**
* Style of the scroll edge effect on all four edges. Per-edge values take precedence.
* #### (iOS 26+ specific)
* @default 'automatic'
*/
style?: 'automatic' | 'soft' | 'hard';
/**
* Per-edge override for the top edge effect. Falls back to the global hidden / style.
* #### (iOS 26+ specific)
*/
top?: OptionsScrollEdge;
/**
* Per-edge override for the bottom edge effect. Falls back to the global hidden / style.
* #### (iOS 26+ specific)
*/
bottom?: OptionsScrollEdge;
/**
* Per-edge override for the left edge effect. Falls back to the global hidden / style.
* #### (iOS 26+ specific)
*/
left?: OptionsScrollEdge;
/**
* Per-edge override for the right edge effect. Falls back to the global hidden / style.
* #### (iOS 26+ specific)
*/
right?: OptionsScrollEdge;
}