However, when making the RN wrapper for adaptive-modal, i encountered an issue. When the modal is being animated, the modal content’s bounds (which is a RN view) is not being updated during the course of the animation
The react modal content view is now being resized by autolayout (since the blue BG color fills the modal), however the subviews of the react modal content (the view with red BG color) is not aware that it's parent's bounds have changed.
As a workaround, I also manually set the size of the "modal content" via RCTBridge + UIManager.setSize(size: CGSize, forView: UIView) during UIViewController.viewDidLayoutSubviews.
This triggers YGLayout recalculate the bounds of the react modal content, and it's subviews.
Various combinations of layout constraints (equal to edges, equal to size, centered, etc).
Directly setting UIViewController.view to the modal content.
Invoking UIManager.setSize inside the animation block of UIViewPropertyAnimator used by adaptive-modal.
Fixing The Underlying Problem
Is there anything that we can try to make UIViewPropertyAnimator work with YGLayout + RCTView? Or is this just not possible due to how RN works?
If the RCTView's somehow recalculate + update their frame/bounds inside of a UIViewPropertyAnimator's animation block, it should work right?
Is there something the wrapper library is doing wrong? Maybe not using the API's correctly?
Is there something i can change in adaptive-modal to make it better work w/ react-native?
Running The Library's Example
An unfinished version of the library is available via NPM:
npm install react-native-ios-adaptive-modal
However, i recommend just cloning the library's repo, and running the example instead:
# Clone the libary repo
git clone https://github.com/dominicstop/react-native-ios-adaptive-modal.git
# Setup library + example
cd ./react-native-ios-adaptive-modal
npm install ; cd example && npm install && cd ios && pod install
# Build and run the example
npm run start
open ./reactnativeiosadaptivemodalexample.xcworkspace/
This is what you should see when running the example:
The library is still in active development, so it's incomplete/unstable, and the API/props for the modal component is not yet finalized. Here are some relevant files related to the example:
Desc: These are some test/example component items shown in the videos. They have buttons to present the modal, and cycling through the preset modal config items.
Desc: This is the function that creates and starts the UIViewPropertyAnimator that drives the modal animations.
How The Library Works
adaptive-modal is a small helper library for presenting a view controller with custom modal presentations.
adaptive-modal accepts a config that describes the state, behavior and layout of the modal, e.g. the various snapping points, layout (e.g. width/height, margin/padding), transition animation (e.g. duration, easing, etc), keyframes (e.g. BG blur, modal content BG blur, BG color, corner radius), etc.
It then transforms that config into something UIKit can understand via handling all the necessary conformances to UIViewControllerAnimatedTransitioning, UIAdaptivePresentationControllerDelegate, UIViewControllerTransitioningDelegate, and animating the modal to create a custom modal transition.
To define the various snapping points, ComputableLayout is used to configure the size and position of the modal. It is essentially just a function that turns a config into raw x, y, width and height values.
DGSwiftUtilities and react-native-ios-utilities are just helper libraries.
More Workarounds (Proposal)
A possible solution could be batched layout updates.
UIViewPropertyAnimator smoothly animates the modal between each snap point; this is to say that the layout values (position and size) are animated continuously.
Because of this, when we use CADisplayLink to periodically update the react modal content, it stutters because it’s being updated discreetly; In other words, we are sampling the layout values frame by frame, and as such the size of the react modal content will be slightly behind.
UIViewPropertyAnimator uses CAAnimation under the hood, so the modal animation is offloaded to the GPU; as such the view’s frame/bounds isn’t actually updated in real-time (only after the animation is finished). Because of this, for every tick/frame, we are taking a snapshot of the modal via view.layer.presentation() and reading the CALayer’s frame.
I think the main reason for the stutter is because the react modal content’s layout updates is not exactly in sync with the modal.
A hacky workaround is to do: synced snapshot/frame-by-frame updates — e.g. do not use UIViewPropertyAnimator directly for animating the layout for the modal.
We will make use of a shadow/dummy modal that is invisible.
This invisible modal will be animated by UIViewPropertyAnimator.
During the animation, for every tick we sample the dummy modal’s current frame.
We update the actual modal with the sampled frame.
We notify a delegate that the modal’s frame has been updated.
Bug Summary
Summary:
UIViewPropertyAnimator
does not animateRCTView
size.Bug Description
adaptive-modal
usesUIViewPropertyAnimator
andAutoLayout
to animate the modalhttps://github.com/dominicstop/react-native-ios-adaptive-modal/assets/18517029/036f1ad9-494c-4f90-b390-cfe84088e5a0
https://github.com/dominicstop/react-native-ios-adaptive-modal/assets/18517029/780710b5-d987-4ec2-912d-7c0835560658
However, when making the RN wrapper for
adaptive-modal
, i encountered an issue. When the modal is being animated, the modal content’s bounds (which is a RN view) is not being updated during the course of the animationhttps://github.com/dominicstop/react-native-ios-adaptive-modal/assets/18517029/e1f428e4-6972-494d-8c09-e77923b5280f
I used autolayout to stretch the react "content view" to the view controller's view:
https://github.com/dominicstop/react-native-ios-adaptive-modal/assets/18517029/bdf304b1-b559-4f89-b4f4-f7a0e0d346c3
The react modal content view is now being resized by autolayout (since the blue BG color fills the modal), however the subviews of the react modal content (the view with red BG color) is not aware that it's parent's bounds have changed.
As a workaround, I also manually set the size of the "modal content" via
RCTBridge
+UIManager.setSize(size: CGSize, forView: UIView)
duringUIViewController.viewDidLayoutSubviews
.This triggers
YGLayout
recalculate the bounds of the react modal content, and it's subviews.https://github.com/dominicstop/react-native-ios-adaptive-modal/assets/18517029/46f3c094-03d3-43e9-93bc-9a59f1ce3af8
But i noticed that it was still not enough, so i used
CADisplayLink
to periodically update the size of the modal contenthttps://github.com/dominicstop/react-native-ios-adaptive-modal/assets/18517029/f71ff1c1-a9de-400e-8a56-a2c795f3ecd3
It was still a bit jittery, so i centered the modal content via autolayout so it wouldn’t move around as much
https://github.com/dominicstop/react-native-ios-adaptive-modal/assets/18517029/bc11c170-d012-4605-b1bc-3a618b828588
It's less jittery now, but it's still a bit janky. But if we're careful with how you style the modal content, it can be less noticeable.
https://github.com/dominicstop/react-native-ios-adaptive-modal/assets/18517029/4ccb2bb8-91fc-480f-9e36-2f7d5826b10b
https://github.com/dominicstop/react-native-ios-adaptive-modal/assets/18517029/0180a11e-4b17-4017-9631-a2d33a1e9ada
Unfortunately, this solution is not perfect.
Here are a list of things i’ve tried:
UIViewController.view
to the modal content.UIManager.setSize
inside the animation block ofUIViewPropertyAnimator
used byadaptive-modal
.Fixing The Underlying Problem
Is there anything that we can try to make
UIViewPropertyAnimator
work withYGLayout
+RCTView
? Or is this just not possible due to how RN works?If the
RCTView
's somehow recalculate + update their frame/bounds inside of aUIViewPropertyAnimator
's animation block, it should work right?Is there something the wrapper library is doing wrong? Maybe not using the API's correctly?
Is there something i can change in
adaptive-modal
to make it better work w/react-native
?Running The Library's Example
An unfinished version of the library is available via NPM:
However, i recommend just cloning the library's repo, and running the example instead:
This is what you should see when running the example:
https://github.com/dominicstop/react-native-ios-adaptive-modal/assets/18517029/ecdae9f5-4279-4f02-8836-b5f9349e23a9
The library is still in active development, so it's incomplete/unstable, and the API/props for the modal component is not yet finalized. Here are some relevant files related to the example:
AdaptiveModalConfigPresets.ts
:example/src/constants/AdaptiveModalConfigPresets.ts
AdaptiveModalViewTest01.tsx
, andAdaptiveModalViewTest02
example/src/examples
RNIAdaptiveModalController
ios/RNIAdaptiveModalView/RNIAdaptiveModalController.swift
adaptive-modal
. This contains the react modal content view.AdaptiveModalManager._animateModal()
UIViewPropertyAnimator
that drives the modal animations.How The Library Works
adaptive-modal
is a small helper library for presenting a view controller with custom modal presentations.adaptive-modal
accepts a config that describes the state, behavior and layout of the modal, e.g. the various snapping points, layout (e.g. width/height, margin/padding), transition animation (e.g. duration, easing, etc), keyframes (e.g. BG blur, modal content BG blur, BG color, corner radius), etc.It then transforms that config into something
UIKit
can understand via handling all the necessary conformances toUIViewControllerAnimatedTransitioning
,UIAdaptivePresentationControllerDelegate
,UIViewControllerTransitioningDelegate
, and animating the modal to create a custom modal transition.To define the various snapping points,
ComputableLayout
is used to configure the size and position of the modal. It is essentially just a function that turns a config into raw x, y, width and height values.https://github.com/dominicstop/react-native-ios-adaptive-modal/assets/18517029/68a37565-8134-4c8e-add2-760b13a94846
DGSwiftUtilities
andreact-native-ios-utilities
are just helper libraries.More Workarounds (Proposal)
A possible solution could be batched layout updates.
UIViewPropertyAnimator
smoothly animates the modal between each snap point; this is to say that the layout values (position and size) are animated continuously.Because of this, when we use
CADisplayLink
to periodically update the react modal content, it stutters because it’s being updated discreetly; In other words, we are sampling the layout values frame by frame, and as such the size of the react modal content will be slightly behind.UIViewPropertyAnimator
usesCAAnimation
under the hood, so the modal animation is offloaded to the GPU; as such the view’s frame/bounds isn’t actually updated in real-time (only after the animation is finished). Because of this, for every tick/frame, we are taking a snapshot of the modal viaview.layer.presentation()
and reading theCALayer
’s frame.I think the main reason for the stutter is because the react modal content’s layout updates is not exactly in sync with the modal.
A hacky workaround is to do: synced snapshot/frame-by-frame updates — e.g. do not use
UIViewPropertyAnimator
directly for animating the layout for the modal.UIViewPropertyAnimator
.This workaround has some downsides though.
https://github.com/dominicstop/react-native-ios-adaptive-modal/assets/18517029/4382a1f8-c02b-4aad-825d-34f24c39cbce