react-native-navigation 8.8.4 → 8.8.5-snapshot.2563

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -79,7 +79,7 @@ object SystemUiUtils {
79
79
  @JvmStatic
80
80
  fun setupSystemBarBackgrounds(activity: Activity, contentLayout: ViewGroup) {
81
81
  setupStatusBarBackground(activity)
82
- setupNavigationBarBackground(contentLayout)
82
+ setupNavigationBarBackground(activity.window, contentLayout)
83
83
  }
84
84
 
85
85
  private fun setupStatusBarBackground(activity: Activity) {
@@ -117,10 +117,10 @@ object SystemUiUtils {
117
117
  return view
118
118
  }
119
119
 
120
- private fun setupNavigationBarBackground(contentLayout: ViewGroup) {
120
+ private fun setupNavigationBarBackground(window: Window?, contentLayout: ViewGroup) {
121
121
  if (navBarBackgroundView != null) return
122
122
  val view = View(contentLayout.context).apply {
123
- setBackgroundColor(Color.BLACK)
123
+ setBackgroundColor(getNavigationBarBackgroundColor(window))
124
124
  }
125
125
  val params = FrameLayout.LayoutParams(
126
126
  FrameLayout.LayoutParams.MATCH_PARENT, 0, Gravity.BOTTOM
@@ -134,7 +134,7 @@ object SystemUiUtils {
134
134
  val wasThreeButton = isThreeButtonNav
135
135
  isThreeButtonNav = tappableHeight > 0
136
136
  if (isThreeButtonNav != wasThreeButton) {
137
- val color = lastExplicitNavBarColor ?: getDefaultNavBarColor()
137
+ val color = lastExplicitNavBarColor ?: getNavigationBarBackgroundColor(v)
138
138
  v.setBackgroundColor(color)
139
139
  }
140
140
  val lp = v.layoutParams
@@ -147,6 +147,17 @@ object SystemUiUtils {
147
147
  view.requestApplyInsets()
148
148
  }
149
149
 
150
+ private fun getNavigationBarBackgroundColor(window: Window?): Int {
151
+ lastExplicitNavBarColor?.let { return it }
152
+ @Suppress("DEPRECATION")
153
+ return window?.navigationBarColor ?: getDefaultNavBarColor()
154
+ }
155
+
156
+ private fun getNavigationBarBackgroundColor(view: View): Int {
157
+ lastExplicitNavBarColor?.let { return it }
158
+ return (view.background as? ColorDrawable)?.color ?: getDefaultNavBarColor()
159
+ }
160
+
150
161
  /**
151
162
  * Returns the default navigation bar color, applying 80% opacity for 3-button navigation.
152
163
  * Gesture navigation gets a fully opaque color since the bar is minimal.
@@ -321,9 +332,8 @@ object SystemUiUtils {
321
332
  window?.let {
322
333
  WindowInsetsControllerCompat(window, window.decorView).isAppearanceLightNavigationBars = lightColor
323
334
  }
324
- if (isEdgeToEdgeActive) {
325
- navBarBackgroundView?.setBackgroundColor(color)
326
- } else {
335
+ navBarBackgroundView?.setBackgroundColor(color)
336
+ if (!isEdgeToEdgeActive) {
327
337
  @Suppress("DEPRECATION")
328
338
  window?.navigationBarColor = color
329
339
  }
@@ -1,16 +1,28 @@
1
1
  package com.reactnativenavigation.utils
2
2
 
3
3
  import android.graphics.Color
4
+ import android.graphics.drawable.ColorDrawable
5
+ import android.view.View
4
6
  import android.view.Window
7
+ import android.widget.FrameLayout
8
+ import androidx.appcompat.app.AppCompatActivity
5
9
  import com.reactnativenavigation.BaseRobolectricTest
6
10
  import com.reactnativenavigation.utils.SystemUiUtils.STATUS_BAR_HEIGHT_TRANSLUCENCY
11
+ import org.assertj.core.api.Java6Assertions.assertThat
12
+ import org.junit.After
7
13
  import org.junit.Test
8
14
  import org.mockito.Mockito
9
15
  import org.mockito.kotlin.verify
16
+ import org.robolectric.Robolectric
10
17
  import kotlin.math.ceil
11
18
 
12
19
  class SystemUiUtilsTest : BaseRobolectricTest() {
13
20
 
21
+ @After
22
+ fun afterEach() {
23
+ SystemUiUtils.tearDown()
24
+ }
25
+
14
26
  @Test
15
27
  fun `setStatusBarColor - should change color considering alpha`() {
16
28
  val window = Mockito.mock(Window::class.java)
@@ -24,4 +36,55 @@ class SystemUiUtilsTest : BaseRobolectricTest() {
24
36
 
25
37
  verify(window).statusBarColor = Color.argb(ceil(STATUS_BAR_HEIGHT_TRANSLUCENCY*255).toInt(), 22, 255, 255)
26
38
  }
27
- }
39
+
40
+ @Test
41
+ fun `setupSystemBarBackgrounds - initializes navigation bar background from resolved color`() {
42
+ val activity = Robolectric.setupActivity(AppCompatActivity::class.java)
43
+ val contentLayout = FrameLayout(activity)
44
+ val initialColor = Color.RED
45
+ SystemUiUtils.setNavigationBarBackgroundColor(activity.window, initialColor, false)
46
+
47
+ SystemUiUtils.setupSystemBarBackgrounds(activity, contentLayout)
48
+
49
+ assertThat(getBackgroundColor(getNavigationBarBackground(contentLayout))).isEqualTo(initialColor)
50
+ }
51
+
52
+ @Test
53
+ fun `setNavigationBarBackgroundColor - updates view and window color when edge-to-edge is inactive`() {
54
+ val activity = Robolectric.setupActivity(AppCompatActivity::class.java)
55
+ val contentLayout = FrameLayout(activity)
56
+ @Suppress("DEPRECATION")
57
+ activity.window.navigationBarColor = Color.BLACK
58
+ SystemUiUtils.setupSystemBarBackgrounds(activity, contentLayout)
59
+
60
+ SystemUiUtils.setNavigationBarBackgroundColor(activity.window, Color.WHITE, true)
61
+
62
+ assertThat(getBackgroundColor(getNavigationBarBackground(contentLayout))).isEqualTo(Color.WHITE)
63
+ assertThat(activity.window.decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR)
64
+ .isEqualTo(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR)
65
+ }
66
+
67
+ @Test
68
+ fun `setNavigationBarBackgroundColor - updates view and icon appearance when edge-to-edge is active`() {
69
+ val activity = Robolectric.setupActivity(AppCompatActivity::class.java)
70
+ val contentLayout = FrameLayout(activity)
71
+ val initialColor = Color.RED
72
+ SystemUiUtils.setNavigationBarBackgroundColor(activity.window, initialColor, false)
73
+ SystemUiUtils.setupSystemBarBackgrounds(activity, contentLayout)
74
+ SystemUiUtils.activateEdgeToEdge()
75
+
76
+ SystemUiUtils.setNavigationBarBackgroundColor(activity.window, Color.WHITE, true)
77
+
78
+ assertThat(getBackgroundColor(getNavigationBarBackground(contentLayout))).isEqualTo(Color.WHITE)
79
+ assertThat(activity.window.decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR)
80
+ .isEqualTo(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR)
81
+ }
82
+
83
+ private fun getNavigationBarBackground(contentLayout: FrameLayout): View {
84
+ return contentLayout.getChildAt(contentLayout.childCount - 1)
85
+ }
86
+
87
+ private fun getBackgroundColor(view: View): Int {
88
+ return (view.background as ColorDrawable).color
89
+ }
90
+ }
@@ -144,6 +144,13 @@
144
144
  }
145
145
 
146
146
  - (UINavigationItem *)currentNavigationItem {
147
+ if ([self.boundViewController isKindOfClass:[UINavigationController class]]) {
148
+ UINavigationController *navigationController =
149
+ (UINavigationController *)self.boundViewController;
150
+ if (navigationController.topViewController) {
151
+ return navigationController.topViewController.navigationItem;
152
+ }
153
+ }
147
154
  return self.boundViewController.getCurrentChild.navigationItem;
148
155
  }
149
156
 
@@ -6,4 +6,6 @@
6
6
 
7
7
  @interface RNNReactButtonView : RNNComponentView
8
8
 
9
+ @property(nonatomic, copy) void (^intrinsicSizeDidChangeHandler)(CGSize intrinsicSize);
10
+
9
11
  @end
@@ -1,10 +1,13 @@
1
1
  #import "RNNReactButtonView.h"
2
2
  #import <React/RCTSurface.h>
3
3
 
4
+ static const CGFloat kMinBarButtonSlotSize = 44.0;
5
+
4
6
  @implementation RNNReactButtonView {
5
7
  NSLayoutConstraint *_widthConstraint;
6
8
  NSLayoutConstraint *_heightConstraint;
7
- BOOL _didCenter;
9
+ BOOL _didCenterHorizontally;
10
+ CGFloat _lastReportedWidth;
8
11
  }
9
12
 
10
13
  - (instancetype)initWithHost:(RCTHost *)host
@@ -19,14 +22,16 @@
19
22
 
20
23
  if (@available(iOS 26.0, *)) {
21
24
  if (![self designRequiresCompatibility]) {
25
+ self.sizeFlexibility = RCTRootViewSizeFlexibilityWidth;
22
26
  self.translatesAutoresizingMaskIntoConstraints = NO;
23
27
  _widthConstraint = [self.widthAnchor constraintEqualToConstant:0];
24
- _heightConstraint = [self.heightAnchor constraintEqualToConstant:0];
28
+ _heightConstraint = [self.heightAnchor constraintEqualToConstant:kMinBarButtonSlotSize];
25
29
  _widthConstraint.priority = UILayoutPriorityDefaultHigh;
26
30
  _heightConstraint.priority = UILayoutPriorityDefaultHigh;
27
31
  _widthConstraint.active = YES;
28
32
  _heightConstraint.active = YES;
29
- _didCenter = NO;
33
+ _didCenterHorizontally = NO;
34
+ self.delegate = self;
30
35
  }
31
36
  }
32
37
 
@@ -43,37 +48,156 @@
43
48
  return result;
44
49
  }
45
50
 
51
+ - (UIView *)surfaceHostView {
52
+ #ifdef RCT_NEW_ARCH_ENABLED
53
+ UIView *surfaceView = (UIView *)self.surface.view;
54
+ if (surfaceView != nil && surfaceView.superview == self) {
55
+ return surfaceView;
56
+ }
57
+ #endif
58
+ return self.subviews.firstObject;
59
+ }
60
+
61
+ - (void)accumulateLeafBoundsInContainer:(UIView *)container unionRect:(CGRect *)unionRect {
62
+ for (UIView *subview in container.subviews) {
63
+ if (subview.hidden || subview.alpha < 0.01) {
64
+ continue;
65
+ }
66
+ if (subview.subviews.count == 0) {
67
+ CGRect rect = [subview convertRect:subview.bounds toView:self];
68
+ if (rect.size.width > 0 && rect.size.height > 0) {
69
+ *unionRect = CGRectIsNull(*unionRect) ? rect : CGRectUnion(*unionRect, rect);
70
+ }
71
+ } else {
72
+ [self accumulateLeafBoundsInContainer:subview unionRect:unionRect];
73
+ }
74
+ }
75
+ }
76
+
77
+ - (CGRect)visibleLeafContentBounds {
78
+ CGRect unionRect = CGRectNull;
79
+ UIView *surfaceView = [self surfaceHostView];
80
+ if (surfaceView) {
81
+ [self accumulateLeafBoundsInContainer:surfaceView unionRect:&unionRect];
82
+ }
83
+ if (CGRectIsNull(unionRect)) {
84
+ [self accumulateLeafBoundsInContainer:self unionRect:&unionRect];
85
+ }
86
+ return CGRectIsNull(unionRect) ? CGRectZero : unionRect;
87
+ }
88
+
89
+ - (CGSize)measuredSurfaceContentSize {
90
+ CGSize intrinsic = CGSizeZero;
91
+ #ifdef RCT_NEW_ARCH_ENABLED
92
+ intrinsic = self.surface.intrinsicSize;
93
+ #else
94
+ intrinsic = self.intrinsicContentSize;
95
+ #endif
96
+ if (intrinsic.width <= 0 || intrinsic.height <= 0) {
97
+ return CGSizeZero;
98
+ }
99
+
100
+ CGFloat maxWidth = self.bounds.size.width > 0 ? self.bounds.size.width : CGFLOAT_MAX;
101
+ CGSize fit = [self sizeThatFits:CGSizeMake(maxWidth, CGFLOAT_MAX)];
102
+ if (fit.height > 0 && fit.height < intrinsic.height - 0.5) {
103
+ intrinsic.height = fit.height;
104
+ }
105
+ return intrinsic;
106
+ }
107
+
108
+ - (void)centerSurfaceContentIfNeeded {
109
+ if (self.bounds.size.width <= 0 || self.bounds.size.height <= 0) {
110
+ return;
111
+ }
112
+
113
+ UIView *surfaceView = [self surfaceHostView];
114
+ if (!surfaceView || surfaceView == self) {
115
+ return;
116
+ }
117
+
118
+ CGRect surfaceFrame = surfaceView.frame;
119
+ if (surfaceFrame.size.width <= 0) {
120
+ surfaceFrame.size.width = self.bounds.size.width;
121
+ }
122
+ if (surfaceFrame.size.height <= 0) {
123
+ surfaceFrame.size.height = self.bounds.size.height;
124
+ }
125
+ surfaceView.frame = CGRectMake(0, 0, surfaceFrame.size.width, surfaceFrame.size.height);
126
+
127
+ CGRect contentBounds = [self visibleLeafContentBounds];
128
+ if (!CGRectIsEmpty(contentBounds)) {
129
+ CGFloat targetY = (self.bounds.size.height - contentBounds.size.height) / 2.0;
130
+ CGFloat deltaY = targetY - contentBounds.origin.y;
131
+ if (fabs(deltaY) > 0.5) {
132
+ surfaceView.frame = CGRectOffset(surfaceView.frame, 0, deltaY);
133
+ }
134
+ return;
135
+ }
136
+
137
+ CGSize contentSize = [self measuredSurfaceContentSize];
138
+ if (contentSize.height > 0 && self.bounds.size.height > contentSize.height + 0.5) {
139
+ CGFloat ty = (self.bounds.size.height - contentSize.height) / 2.0;
140
+ surfaceView.frame = CGRectMake(0, ty, self.bounds.size.width, contentSize.height);
141
+ }
142
+ }
143
+
144
+ - (void)handleIntrinsicSizeChange:(CGSize)intrinsicSize {
145
+ if (intrinsicSize.width <= 0 || intrinsicSize.height <= 0) {
146
+ return;
147
+ }
148
+
149
+ CGFloat width = MAX(intrinsicSize.width, kMinBarButtonSlotSize);
150
+ if (_widthConstraint && _heightConstraint) {
151
+ _widthConstraint.constant = width;
152
+ _heightConstraint.constant = kMinBarButtonSlotSize;
153
+ }
154
+
155
+ if (self.intrinsicSizeDidChangeHandler && width > kMinBarButtonSlotSize &&
156
+ width > _lastReportedWidth) {
157
+ _lastReportedWidth = width;
158
+ self.intrinsicSizeDidChangeHandler(intrinsicSize);
159
+ }
160
+
161
+ [self setNeedsLayout];
162
+ }
163
+
46
164
  - (void)didMountComponentsWithRootTag:(NSInteger)rootTag {
47
165
  if (self.surface.rootTag == rootTag) {
48
166
  [super didMountComponentsWithRootTag:rootTag];
49
167
  [self sizeToFit];
50
- if (@available(iOS 26.0, *)) {
51
- if (![self designRequiresCompatibility]) {
52
- [self updateConstraintsToFitSize];
53
- }
54
- }
168
+ [self setNeedsLayout];
55
169
  }
56
170
  }
57
171
 
58
- - (void)updateConstraintsToFitSize {
59
- CGSize size = self.frame.size;
60
- if (size.width > 0 && size.height > 0) {
61
- _widthConstraint.constant = size.width;
62
- _heightConstraint.constant = size.height;
63
- }
172
+ #ifdef RCT_NEW_ARCH_ENABLED
173
+ - (void)surface:(RCTSurface *)surface didChangeIntrinsicSize:(CGSize)intrinsicSize {
174
+ [self handleIntrinsicSizeChange:intrinsicSize];
175
+ }
176
+ #else
177
+ - (void)rootViewDidChangeIntrinsicSize:(RCTRootView *)rootView {
178
+ [self handleIntrinsicSizeChange:rootView.intrinsicContentSize];
179
+ [rootView setNeedsUpdateConstraints];
180
+ [rootView updateConstraintsIfNeeded];
64
181
  }
182
+ #endif
65
183
 
66
184
  - (void)layoutSubviews {
67
185
  [super layoutSubviews];
68
186
  if (@available(iOS 26.0, *)) {
69
- if ([self designRequiresCompatibility]) return;
70
- if (!_didCenter && self.superview && self.frame.size.width > 0) {
187
+ if ([self designRequiresCompatibility]) {
188
+ return;
189
+ }
190
+
191
+ [self centerSurfaceContentIfNeeded];
192
+
193
+ if (!_didCenterHorizontally && self.superview && self.frame.size.width > 0) {
71
194
  CGFloat wrapperWidth = self.superview.bounds.size.width;
72
195
  CGFloat selfWidth = self.frame.size.width;
73
- if (wrapperWidth > selfWidth) {
74
- _didCenter = YES;
196
+ if (wrapperWidth > selfWidth + 0.5) {
197
+ _didCenterHorizontally = YES;
75
198
  CGFloat tx = (wrapperWidth - selfWidth) / 2.0;
76
- self.layer.affineTransform = CGAffineTransformMakeTranslation(tx, 0);
199
+ CGAffineTransform transform = self.layer.affineTransform;
200
+ self.layer.affineTransform = CGAffineTransformMakeTranslation(tx, transform.ty);
77
201
  }
78
202
  }
79
203
  }
@@ -28,6 +28,13 @@
28
28
  return self;
29
29
  }
30
30
 
31
+ - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
32
+ if (@available(iOS 26.0, *)) {
33
+ [self.presenter applyTopBarBackgroundBeforeShowingViewController:viewController];
34
+ }
35
+ [super pushViewController:viewController animated:animated];
36
+ }
37
+
31
38
  - (void)viewDidLayoutSubviews {
32
39
  [super viewDidLayoutSubviews];
33
40
  [self.presenter applyOptionsOnViewDidLayoutSubviews:self.resolveOptions];
@@ -1,6 +1,7 @@
1
1
  #import "RNNBasePresenter.h"
2
2
  #import "RNNComponentViewCreator.h"
3
3
  #import "RNNReactComponentRegistry.h"
4
+ #import <UIKit/UIKit.h>
4
5
 
5
6
  @interface RNNStackPresenter : RNNBasePresenter
6
7
 
@@ -9,6 +10,9 @@
9
10
 
10
11
  - (void)applyOptionsBeforePopping:(RNNNavigationOptions *)options;
11
12
 
13
+ - (void)applyTopBarBackgroundBeforeShowingViewController:(UIViewController *)viewController
14
+ API_AVAILABLE(ios(26.0));
15
+
12
16
  - (BOOL)shouldPopItem:(UINavigationItem *)item options:(RNNNavigationOptions *)options;
13
17
 
14
18
  @end
@@ -101,6 +101,14 @@
101
101
  [_topBarPresenter applyOptionsBeforePopping:options.topBar];
102
102
  }
103
103
 
104
+ - (void)applyTopBarBackgroundBeforeShowingViewController:(UIViewController *)viewController {
105
+ if (@available(iOS 26.0, *)) {
106
+ RNNNavigationOptions *withDefault = viewController.resolveOptionsWithDefault;
107
+ [_topBarPresenter applyBackgroundForTransitionToViewController:viewController
108
+ topBarOptions:withDefault.topBar];
109
+ }
110
+ }
111
+
104
112
  - (void)mergeOptions:(RNNNavigationOptions *)mergeOptions
105
113
  resolvedOptions:(RNNNavigationOptions *)resolvedOptions {
106
114
  [super mergeOptions:mergeOptions resolvedOptions:resolvedOptions];
@@ -7,13 +7,7 @@
7
7
 
8
8
  typedef void (^RNNButtonPressCallback)(NSString *buttonId);
9
9
 
10
- @interface RNNUIBarButtonItem : UIBarButtonItem <
11
- #ifdef RCT_NEW_ARCH_ENABLED
12
- RCTSurfaceDelegate
13
- #else
14
- RCTRootViewDelegate
15
- #endif
16
- >
10
+ @interface RNNUIBarButtonItem : UIBarButtonItem
17
11
 
18
12
  @property(nonatomic, strong) NSString *buttonId;
19
13
 
@@ -7,6 +7,8 @@
7
7
  #import <React/RCTSurface.h>
8
8
  #endif
9
9
 
10
+ static const CGFloat kMinBarButtonSlotSize = 44.0;
11
+
10
12
  @interface RNNUIBarButtonItem ()
11
13
 
12
14
  @property(nonatomic, strong) NSLayoutConstraint *widthConstraint;
@@ -18,6 +20,7 @@
18
20
  @implementation RNNUIBarButtonItem {
19
21
  RNNIconCreator *_iconCreator;
20
22
  RNNButtonOptions *_buttonOptions;
23
+ BOOL _didApplyWidthResize;
21
24
  }
22
25
 
23
26
  - (instancetype)init {
@@ -94,6 +97,49 @@
94
97
  return self;
95
98
  }
96
99
 
100
+ - (BOOL)designRequiresCompatibility {
101
+ static BOOL checked = NO;
102
+ static BOOL result = NO;
103
+ if (!checked) {
104
+ checked = YES;
105
+ result = [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIDesignRequiresCompatibility"] boolValue];
106
+ }
107
+ return result;
108
+ }
109
+
110
+ - (void)applyCustomViewIntrinsicSize:(CGSize)intrinsicSize {
111
+ if (!self.widthConstraint || !self.heightConstraint || intrinsicSize.width <= 0) {
112
+ return;
113
+ }
114
+
115
+ CGFloat width = MAX(intrinsicSize.width, kMinBarButtonSlotSize);
116
+ if (_didApplyWidthResize && width <= self.widthConstraint.constant) {
117
+ return;
118
+ }
119
+
120
+ if (self.widthConstraint.constant == width) {
121
+ _didApplyWidthResize = YES;
122
+ return;
123
+ }
124
+
125
+ self.widthConstraint.constant = width;
126
+ _didApplyWidthResize = YES;
127
+
128
+ [self.customView setNeedsLayout];
129
+ [self invalidateNavigationBarLayout];
130
+ }
131
+
132
+ - (void)invalidateNavigationBarLayout {
133
+ UIView *view = self.customView;
134
+ while (view) {
135
+ if ([view isKindOfClass:[UINavigationBar class]]) {
136
+ [view setNeedsLayout];
137
+ return;
138
+ }
139
+ view = view.superview;
140
+ }
141
+ }
142
+
97
143
  - (instancetype)initWithCustomView:(RNNReactView *)reactView
98
144
  buttonOptions:(RNNButtonOptions *)buttonOptions
99
145
  onPress:(RNNButtonPressCallback)onPress {
@@ -106,16 +152,25 @@
106
152
  // back in via the hideSharedBackground option.
107
153
  self.hidesSharedBackground =
108
154
  [buttonOptions.hideSharedBackground withDefault:YES];
109
- // Pin the custom view to a stable 44pt bar-item size so the navbar
110
- // reserves the final slot up-front (incl. during push-transition
111
- // snapshots) and doesn't relayout once React mounts. Without this,
112
- // React's post-mount sizeToFit changes bounds, the navbar relayouts,
113
- // and the customView visibly snaps from a 0x0 / wrong-position state
114
- // to its centered final frame. React content centers within via its
115
- // own flex layout.
116
- reactView.translatesAutoresizingMaskIntoConstraints = NO;
117
- [reactView.widthAnchor constraintEqualToConstant:44].active = YES;
118
- [reactView.heightAnchor constraintEqualToConstant:44].active = YES;
155
+ if (![self designRequiresCompatibility]) {
156
+ // Reserve a stable 44pt slot for push-transition snapshots, then grow
157
+ // width once when React reports intrinsic size (e.g. wide pickers).
158
+ // Height stays 44; vertical alignment is handled in React.
159
+ reactView.translatesAutoresizingMaskIntoConstraints = NO;
160
+ self.widthConstraint = [reactView.widthAnchor constraintEqualToConstant:kMinBarButtonSlotSize];
161
+ self.heightConstraint = [reactView.heightAnchor constraintEqualToConstant:kMinBarButtonSlotSize];
162
+ self.widthConstraint.priority = UILayoutPriorityRequired;
163
+ self.heightConstraint.priority = UILayoutPriorityRequired;
164
+ self.widthConstraint.active = YES;
165
+ self.heightConstraint.active = YES;
166
+ if ([reactView isKindOfClass:[RNNReactButtonView class]]) {
167
+ RNNReactButtonView *buttonView = (RNNReactButtonView *)reactView;
168
+ __weak RNNUIBarButtonItem *weakSelf = self;
169
+ buttonView.intrinsicSizeDidChangeHandler = ^(CGSize intrinsicSize) {
170
+ [weakSelf applyCustomViewIntrinsicSize:intrinsicSize];
171
+ };
172
+ }
173
+ }
119
174
  }
120
175
  [self applyOptions:buttonOptions];
121
176
  self.onPress = onPress;
@@ -219,26 +274,6 @@
219
274
  }
220
275
 
221
276
 
222
- #ifdef RCT_NEW_ARCH_ENABLED
223
- // TODO: Verify
224
- - (void)surface:(RCTSurface *)surface didChangeIntrinsicSize:(CGSize)intrinsicSize {
225
- self.widthConstraint.constant = intrinsicSize.width;
226
- self.heightConstraint.constant = intrinsicSize.height;
227
- [surface setSize:intrinsicSize];
228
- //[rootView setNeedsUpdateConstraints];
229
- //[rootView updateConstraintsIfNeeded];
230
- //surface.hidden = NO;
231
- }
232
- #else
233
- - (void)rootViewDidChangeIntrinsicSize:(RCTRootView *)rootView {
234
- self.widthConstraint.constant = rootView.intrinsicContentSize.width;
235
- self.heightConstraint.constant = rootView.intrinsicContentSize.height;
236
- [rootView setNeedsUpdateConstraints];
237
- [rootView updateConstraintsIfNeeded];
238
- rootView.hidden = NO;
239
- }
240
- #endif
241
-
242
277
  - (void)onButtonPressed:(RNNUIBarButtonItem *)barButtonItem {
243
278
  self.onPress(self.buttonId);
244
279
  }
@@ -1,9 +1,18 @@
1
1
  #import "TopBarAppearancePresenter.h"
2
2
  #import "RNNFontAttributesCreator.h"
3
+ #import "UIColor+RNNUtils.h"
4
+ #import "UIImage+utils.h"
3
5
  #import "UIViewController+LayoutProtocol.h"
4
6
 
5
7
  @interface TopBarAppearancePresenter ()
6
8
 
9
+ - (void)applyBackgroundToNavigationItem:(UINavigationItem *)item topBarOptions:(RNNTopBarOptions *)options;
10
+ - (void)syncNavigationBarAppearanceFromNavigationItem:(UINavigationItem *)item;
11
+ - (void)applyBackgroundToAppearance:(UINavigationBarAppearance *)appearance
12
+ withOpaqueColor:(UIColor *)color
13
+ transparent:(BOOL)transparent
14
+ translucent:(BOOL)translucent;
15
+
7
16
  @end
8
17
 
9
18
  @implementation TopBarAppearancePresenter
@@ -11,6 +20,97 @@
11
20
  @synthesize borderColor = _borderColor;
12
21
  @synthesize scrollEdgeBorderColor = _scrollEdgeBorderColor;
13
22
 
23
+ - (void)applyBackgroundToNavigationItem:(UINavigationItem *)item topBarOptions:(RNNTopBarOptions *)options {
24
+ UIColor *color = [options.background.color withDefault:nil];
25
+ BOOL transparent = color.isTransparent;
26
+ BOOL translucent = [options.background.translucent withDefault:NO];
27
+
28
+ if (!item.standardAppearance) {
29
+ item.standardAppearance = [UINavigationBarAppearance new];
30
+ }
31
+ if (!item.scrollEdgeAppearance) {
32
+ item.scrollEdgeAppearance = [UINavigationBarAppearance new];
33
+ }
34
+ [self applyBackgroundToAppearance:item.standardAppearance
35
+ withOpaqueColor:color
36
+ transparent:transparent
37
+ translucent:translucent];
38
+ [self applyBackgroundToAppearance:item.scrollEdgeAppearance
39
+ withOpaqueColor:color
40
+ transparent:transparent
41
+ translucent:translucent];
42
+
43
+ if ([options.scrollEdgeAppearance.active withDefault:NO]) {
44
+ UIColor *scrollEdgeColor = [options.scrollEdgeAppearance.background.color withDefault:nil];
45
+ BOOL scrollEdgeTransparent = scrollEdgeColor.isTransparent;
46
+ BOOL scrollEdgeTranslucent =
47
+ [options.scrollEdgeAppearance.background.translucent withDefault:translucent];
48
+ UIColor *resolvedScrollEdgeColor = scrollEdgeColor ?: color;
49
+ [self applyBackgroundToAppearance:item.scrollEdgeAppearance
50
+ withOpaqueColor:resolvedScrollEdgeColor
51
+ transparent:scrollEdgeTransparent
52
+ translucent:scrollEdgeTranslucent];
53
+ }
54
+
55
+ if (@available(iOS 26.0, *)) {
56
+ [self syncNavigationBarAppearanceFromNavigationItem:item];
57
+ }
58
+ }
59
+
60
+ - (void)applyBackgroundForTransitionToViewController:(UIViewController *)viewController
61
+ topBarOptions:(RNNTopBarOptions *)options {
62
+ if (@available(iOS 26.0, *)) {
63
+ [self applyBackgroundToNavigationItem:viewController.navigationItem topBarOptions:options];
64
+ }
65
+ }
66
+
67
+ - (void)syncNavigationBarAppearanceFromNavigationItem:(UINavigationItem *)item {
68
+ UINavigationBar *navigationBar = self.navigationController.navigationBar;
69
+ if (item.standardAppearance) {
70
+ navigationBar.standardAppearance = [item.standardAppearance copy];
71
+ }
72
+ UINavigationBarAppearance *scrollEdgeAppearance =
73
+ item.scrollEdgeAppearance ?: item.standardAppearance;
74
+ if (scrollEdgeAppearance) {
75
+ navigationBar.scrollEdgeAppearance = [scrollEdgeAppearance copy];
76
+ }
77
+ }
78
+
79
+ - (void)applyBackgroundToAppearance:(UINavigationBarAppearance *)appearance
80
+ withOpaqueColor:(UIColor *)color
81
+ transparent:(BOOL)transparent
82
+ translucent:(BOOL)translucent {
83
+ if (transparent || color.isTransparent) {
84
+ [appearance configureWithTransparentBackground];
85
+ if (@available(iOS 26.0, *)) {
86
+ appearance.backgroundEffect = nil;
87
+ appearance.backgroundColor = UIColor.clearColor;
88
+ appearance.backgroundImage = nil;
89
+ }
90
+ return;
91
+ }
92
+
93
+ if (color) {
94
+ if (@available(iOS 26.0, *)) {
95
+ [appearance configureWithTransparentBackground];
96
+ appearance.backgroundEffect = nil;
97
+ appearance.shadowColor = nil;
98
+ appearance.backgroundColor = color;
99
+ appearance.backgroundImage = [UIImage imageWithSize:CGSizeMake(1, 1) color:color];
100
+ } else {
101
+ [appearance configureWithOpaqueBackground];
102
+ appearance.backgroundColor = color;
103
+ }
104
+ return;
105
+ }
106
+
107
+ if (translucent) {
108
+ [appearance configureWithDefaultBackground];
109
+ } else {
110
+ [appearance configureWithOpaqueBackground];
111
+ }
112
+ }
113
+
14
114
  - (void)applyOptions:(RNNTopBarOptions *)options {
15
115
  [self setTranslucent:[options.background.translucent withDefault:NO]];
16
116
  [self
@@ -77,36 +177,24 @@
77
177
  }
78
178
 
79
179
  - (void)updateScrollEdgeAppearance {
80
- if (self.scrollEdgeTransparent) {
81
- [self.getScrollEdgeAppearance configureWithTransparentBackground];
82
- } else if (self.scrollEdgeAppearanceColor) {
83
- [self.getScrollEdgeAppearance configureWithOpaqueBackground];
84
- [self.getScrollEdgeAppearance setBackgroundColor:self.scrollEdgeAppearanceColor];
85
- } else if (self.scrollEdgeTranslucent) {
86
- [self.getScrollEdgeAppearance configureWithDefaultBackground];
87
- } else {
88
- [self.getScrollEdgeAppearance configureWithOpaqueBackground];
89
- if (self.backgroundColor) {
90
- [self.getScrollEdgeAppearance setBackgroundColor:self.backgroundColor];
91
- }
92
- }
180
+ UIColor *color = self.scrollEdgeAppearanceColor ?: self.backgroundColor;
181
+ [self applyBackgroundToAppearance:self.getScrollEdgeAppearance
182
+ withOpaqueColor:color
183
+ transparent:self.scrollEdgeTransparent
184
+ translucent:self.scrollEdgeTranslucent];
93
185
  }
94
186
 
95
187
  - (void)updateBackgroundAppearance {
96
- if (self.transparent) {
97
- [self.getAppearance configureWithTransparentBackground];
98
- [self.getScrollEdgeAppearance configureWithTransparentBackground];
99
- } else if (self.backgroundColor) {
100
- [self.getAppearance configureWithOpaqueBackground];
101
- [self.getScrollEdgeAppearance configureWithOpaqueBackground];
102
- [self.getAppearance setBackgroundColor:self.backgroundColor];
103
- [self.getScrollEdgeAppearance setBackgroundColor:self.backgroundColor];
104
- } else if (self.translucent) {
105
- [self.getAppearance configureWithDefaultBackground];
106
- [self.getScrollEdgeAppearance configureWithDefaultBackground];
107
- } else {
108
- [self.getAppearance configureWithOpaqueBackground];
109
- [self.getScrollEdgeAppearance configureWithOpaqueBackground];
188
+ [self applyBackgroundToAppearance:self.getAppearance
189
+ withOpaqueColor:self.backgroundColor
190
+ transparent:self.transparent
191
+ translucent:self.translucent];
192
+ [self applyBackgroundToAppearance:self.getScrollEdgeAppearance
193
+ withOpaqueColor:self.backgroundColor
194
+ transparent:self.transparent
195
+ translucent:self.translucent];
196
+ if (@available(iOS 26.0, *)) {
197
+ [self syncNavigationBarAppearanceFromNavigationItem:self.currentNavigationItem];
110
198
  }
111
199
  }
112
200
 
@@ -1,12 +1,18 @@
1
1
  #import "RNNBasePresenter.h"
2
2
  #import "RNNTopBarOptions.h"
3
+ #import <UIKit/UIKit.h>
3
4
 
4
5
  @interface TopBarPresenter : RNNBasePresenter
5
6
 
7
+ - (UINavigationController *)navigationController;
8
+
6
9
  - (void)applyOptions:(RNNTopBarOptions *)options;
7
10
 
8
11
  - (void)applyOptionsBeforePopping:(RNNTopBarOptions *)options;
9
12
 
13
+ - (void)applyBackgroundForTransitionToViewController:(UIViewController *)viewController
14
+ topBarOptions:(RNNTopBarOptions *)options API_AVAILABLE(ios(26.0));
15
+
10
16
  - (void)mergeOptions:(RNNTopBarOptions *)options withDefault:(RNNTopBarOptions *)defaultOptions;
11
17
 
12
18
  - (instancetype)initWithNavigationController:(UINavigationController *)boundNavigationController;
@@ -71,6 +71,10 @@
71
71
  [self.navigationController.navigationBar setBackIndicatorTransitionMaskImage:image];
72
72
  }
73
73
 
74
+ - (void)applyBackgroundForTransitionToViewController:(UIViewController *)viewController
75
+ topBarOptions:(RNNTopBarOptions *)options {
76
+ }
77
+
74
78
  - (void)setBackgroundColor:(UIColor *)backgroundColor {
75
79
  _backgroundColor = backgroundColor;
76
80
  [self updateBackgroundAppearance];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-navigation",
3
- "version": "8.8.4",
3
+ "version": "8.8.5-snapshot.2563",
4
4
  "description": "React Native Navigation - truly native navigation for iOS and Android",
5
5
  "license": "MIT",
6
6
  "nativePackage": true,