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.
- package/android/src/main/java/com/reactnativenavigation/utils/SystemUiUtils.kt +17 -7
- package/android/src/test/java/com/reactnativenavigation/utils/SystemUiUtilsTest.kt +64 -1
- package/ios/RNNBasePresenter.mm +7 -0
- package/ios/RNNReactButtonView.h +2 -0
- package/ios/RNNReactButtonView.mm +143 -19
- package/ios/RNNStackController.mm +7 -0
- package/ios/RNNStackPresenter.h +4 -0
- package/ios/RNNStackPresenter.mm +8 -0
- package/ios/RNNUIBarButtonItem.h +1 -7
- package/ios/RNNUIBarButtonItem.mm +65 -30
- package/ios/TopBarAppearancePresenter.mm +115 -27
- package/ios/TopBarPresenter.h +6 -0
- package/ios/TopBarPresenter.mm +4 -0
- package/package.json +1 -1
|
@@ -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(
|
|
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 ?:
|
|
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
|
-
|
|
325
|
-
|
|
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
|
+
}
|
package/ios/RNNBasePresenter.mm
CHANGED
|
@@ -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
|
|
package/ios/RNNReactButtonView.h
CHANGED
|
@@ -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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
51
|
-
if (![self designRequiresCompatibility]) {
|
|
52
|
-
[self updateConstraintsToFitSize];
|
|
53
|
-
}
|
|
54
|
-
}
|
|
168
|
+
[self setNeedsLayout];
|
|
55
169
|
}
|
|
56
170
|
}
|
|
57
171
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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])
|
|
70
|
-
|
|
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
|
-
|
|
196
|
+
if (wrapperWidth > selfWidth + 0.5) {
|
|
197
|
+
_didCenterHorizontally = YES;
|
|
75
198
|
CGFloat tx = (wrapperWidth - selfWidth) / 2.0;
|
|
76
|
-
self.layer.affineTransform
|
|
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];
|
package/ios/RNNStackPresenter.h
CHANGED
|
@@ -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
|
package/ios/RNNStackPresenter.mm
CHANGED
|
@@ -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];
|
package/ios/RNNUIBarButtonItem.h
CHANGED
|
@@ -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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
[self.
|
|
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
|
|
package/ios/TopBarPresenter.h
CHANGED
|
@@ -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;
|
package/ios/TopBarPresenter.mm
CHANGED
|
@@ -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];
|