Commit 2661c28
authored
feat(iOS, Tabs): add bottomAccessory support (#3288)
## Description
Adds support for `botttomAccessory` in Bottom Tabs starting from iOS 26.
## Synchronization with ShadowTree
When bottom accessory transitions between `regular` and `inline`
environments (when tab bar is minimized), we need to update the position
and size of the bottom accessory. Our approach is different for RN <
0.82 and RN >= 0.82.
### Possible approaches
We considered following approaches:
1. asynchronous state updates & events
- this would result in frame changing right after animation start to the
final size
- using `double-rendering` approach (explained further in PR
description) wouldn't really improve the situation
2. (a)synchronous state updates & asynchronous events + DisplayLink
- this allows us to track the native animation of the bottom accessory
and match frame size
- unfortunately, as DisplayLink allows us to read presentation layer
frame, we will always be 1 frame delayed
- with asynchronous updates, this delay increases to multiple frames;
with synchronous updates this would be exactly 1 frame (but this doesn't
mean it looks better!)
3. synchronous state updates & asynchronous events
- synchronous state updates allow us to rely on `CoreAnimations`
framework that animates views natively (details explained further in PR
description)
- unfortunately, this mechanism requires all changes to be performed
synchronously - state update (size change) is handled synchronously
thanks to `immediate` mode for state update introduced in RN 0.82 but
"synchronous" event dispatch in RN 0.82 isn't exactly `synchronous`
(update is performed on next loop beat, this is not enough for CA)
- due to this limitation, any changes in reaction to environment change
are buggy (see `Mounting/unmounting views during transition` section)
4. synchronous state updates & `double-rendering`
- to mitigate problems described above, we render the component twice -
first for `regular` environment, second for `inline` environment
- we change which component is visible on environment change -> we can
do this fully synchronously in native code
- CA animates this change with cross-fade animation
- this results seems to be a good compromise but requires React state
synchronization between components, e.g. via context
For now, we decided to use:
- Paper: approach 2 (asynchronous)
- Fabric RN < 0.82: approach 2 (asynchronous)
- Fabric RN >= 0.82: approach 4
Those solutions are described in more detail below:
### Legacy architecture & New architecture prior to
`[email protected]`
In versions prior to RN 0.82, we need use `DisplayLink` and presentation
layer frames to get intermediate frames during the transition. This
approach however has a major drawback - we are always at least one frame
behind the current state as we're observing what is currently presented.
When the difference in size/origin between frames is significant, you
can see the content "jumping". In the case of bottom accessory, this is
especially visible when using non-translucent background and
transitioning from `inline` to `regular` environment (pay attention to
the right edge of the accessory).
https://github.com/user-attachments/assets/3931c318-2bb7-4bc2-847f-95168578d7d2
Introduction of synchronous state updates in RN 0.82 (more details
below) does not improve the situation when using this approach as we are
still going to be at least one frame behind the animation.
### `[email protected]` and higher
Thanks to introduction of [synchronous state updates in RN
0.82](facebook/react-native#52604), we can rely
fully on native mechanisms for handling the transition. Bottom accessory
only receives the final frame of the transition and thanks to
synchronous state updates, we can immediately recalculate the layout in
the Shadow Tree and update the Host Tree. This allows Core Animation
framework to make the transition smooth. Details of how we think this
works are available
[here](#3285 (comment)).
Unfortunately, when using `react-native`, the situation is a little bit
more complicated.
#### Text
Text component behaves differently to the native platform. During the
transition, it immediately adapts to the final frame size and then it is
stretched. In bare UIKit app, the text adapt to new frame size at the
end of the transition.
| `react-native` | `UIKit` |
| --- | --- |
| <video
src="https://github.com/user-attachments/assets/ea509fee-53bb-4300-a28d-3404525d47dd"
/> | <video
src="https://github.com/user-attachments/assets/17b2d3f8-5f04-406a-828c-504d040ad013"
/> |
This requires more investigation and potentially changes in
`react-native`.
#### Borders
`CoreAnimation` does not support non-uniform borders so `react-native`
handles them in a custom way that does not seem to be compatible with
the transition mechanism.
| non-uniform borders | uniform borders + **CA enabled** |
| --- | --- |
| <video
src="https://github.com/user-attachments/assets/e9ce53a9-bb4b-4465-a986-1a1f08791454"
/> | <video
src="https://github.com/user-attachments/assets/bcf029a4-764a-446c-82b6-91bfd42494c6"
/> |
This requires more investigation and potentially changes in
`react-native`.
#### Images
Similar problem (in a way it looks, not the exact mechanism of the bug)
happens when using images e.g. with `width: 100%`.
https://github.com/user-attachments/assets/1aa0b77a-7800-471f-bd38-ac3eed506b87
This requires more investigation and potentially changes in
`react-native`.
#### Mounting/unmounting views during transition
While state updates are performed synchronously, any changes to React
Element Tree in reaction to environment change are handled
asynchronously. We think that this is why the transition handled by
`CoreAnimation` breaks when trying to mount/unmount components on
environment change.
https://github.com/user-attachments/assets/ef0730db-386a-46be-8fcf-e0def3ca4097
Here, we try to remove the note icon. Unfortunately, the rest of the
layout does not adapt. You can also observe the text stretching as
mentioned 2 sections above.
In order to mitigate this issue, we use `double-rendering` approach,
which has been described in `Possible approaches` section.
https://github.com/user-attachments/assets/7d105e62-1f69-4886-845a-464addfa608b
## Changes
- add `BottomTabsAccessory` JS component and use it in `BottomTabs`,
- add `BottomTabsAccessoryComponentView`,
`BottomTabsAccessoryEventEmitter`,
`BottomTabsAccessoryComponentViewManager`,
- add `BottomAccessoryHelper` to handle size and environment changes,
- add `BottomTabsAccessoryShadowStateProxy` to synchronize state between
Host and ShadowTree,
- adapt `BottomTabsHost` to accept 2 types of children (`Screen` and
`Accessory`),
- add test screen.
## Test code and steps to reproduce
Run `Test3288`.
## Checklist
- [x] Included code example that can be used to test this change
- [x] Updated TS types
- [ ] Updated documentation: <!-- For adding new props to native-stack
-->
- [ ]
https://github.com/software-mansion/react-native-screens/blob/main/guides/GUIDE_FOR_LIBRARY_AUTHORS.md
- [ ]
https://github.com/software-mansion/react-native-screens/blob/main/native-stack/README.md
- [ ]
https://github.com/software-mansion/react-native-screens/blob/main/src/types.tsx
- [ ]
https://github.com/software-mansion/react-native-screens/blob/main/src/native-stack/types.tsx
- [x] Ensured that CI passes1 parent 10af839 commit 2661c28
File tree
50 files changed
+1713
-57
lines changed- apps/src/tests
- common/cpp/react/renderer/components/rnscreens
- ios
- bottom-tabs
- accessory
- host
- screen
- conversion
- safe-area
- utils
- src
- components/bottom-tabs
- fabric/bottom-tabs
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
50 files changed
+1713
-57
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
156 | 156 | | |
157 | 157 | | |
158 | 158 | | |
| 159 | + | |
159 | 160 | | |
160 | 161 | | |
161 | 162 | | |
| |||
Lines changed: 34 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
Lines changed: 14 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
Lines changed: 26 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
Lines changed: 20 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
0 commit comments