@apexnative/navigation 1.0.0-alpha.4 → 1.0.0-alpha.6
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/CHANGELOG.md +38 -0
- package/README.md +1 -1
- package/android/CLAUDE.md +23 -14
- package/android/CORE.md +32 -19
- package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +60 -13
- package/android/src/main/java/dev/apexnative/navigation/options/AnimationsOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/BottomTabsOptions.kt +13 -8
- package/android/src/main/java/dev/apexnative/navigation/options/HardwareBackButtonOptions.kt +9 -1
- package/android/src/main/java/dev/apexnative/navigation/options/StackAnimationOptions.kt +40 -2
- package/android/src/main/java/dev/apexnative/navigation/react/CommandAnswer.kt +28 -13
- package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +7 -4
- package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/react/ReactView.kt +39 -20
- package/android/src/main/java/dev/apexnative/navigation/react/ReloadCoordinator.kt +7 -6
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenEnvironment.kt +177 -0
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +19 -51
- package/android/src/main/java/dev/apexnative/navigation/react/ScrollEvents.kt +49 -128
- package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +30 -39
- package/android/src/main/java/dev/apexnative/navigation/react/events/EventEmitter.kt +34 -7
- package/android/src/main/java/dev/apexnative/navigation/react/events/NavigationState.kt +154 -0
- package/android/src/main/java/dev/apexnative/navigation/safearea/SafeAreaView.kt +12 -10
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +12 -12
- package/android/src/main/java/dev/apexnative/navigation/utils/ViewExtensions.kt +0 -13
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/ArrangementController.kt +10 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsCollapseBehavior.kt +5 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +22 -21
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowGeometry.kt +22 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/TabsRoom.kt +0 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentViewController.kt +12 -9
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ContentReadiness.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenReadiness.kt +103 -56
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenSafeArea.kt +29 -11
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/externalcomponent/ExternalComponentViewController.kt +2 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +20 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +119 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +7 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetBack.kt +42 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackCheck.kt +50 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackState.kt +45 -14
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +25 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +3 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/PreparedFrame.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +13 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +3 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/ScreenSearch.kt +3 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +2 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarComponents.kt +5 -51
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +4 -9
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ScrollEventListener.kt +6 -9
- package/android/src/main/java/dev/apexnative/navigation/views/animations/AnimationLayer.kt +73 -0
- package/android/src/main/java/dev/apexnative/navigation/views/animations/ValueInterpolation.kt +14 -0
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabsLayout.kt +61 -2
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomBottomTabItemView.kt +0 -5
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomTabRow.kt +72 -10
- package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +9 -2
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +11 -0
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +318 -25
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +18 -20
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +17 -25
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +15 -22
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +12 -9
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/TextChangeAnimator.kt +84 -35
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +49 -40
- package/cli/doctor/checks/splash.js +4 -22
- package/cli/splash/index.js +3 -1
- package/docs-md/architecture/transitions.md +4 -2
- package/docs-md/containers/modals.md +4 -2
- package/docs-md/containers/overlays.md +1 -1
- package/docs-md/containers/side-menu.md +1 -1
- package/docs-md/containers/tabs.md +2 -0
- package/docs-md/flows/bottom-sheets.md +1 -1
- package/docs-md/fundamentals/starting-the-app.md +1 -1
- package/docs-md/guides/cli.md +1 -2
- package/docs-md/help/glossary.md +2 -2
- package/docs-md/help/troubleshooting.md +23 -2
- package/docs-md/index.json +12 -0
- package/docs-md/migration/from-expo-router.md +1 -1
- package/docs-md/migration/from-react-native-navigation.md +1 -1
- package/docs-md/migration/from-react-navigation.md +2 -2
- package/docs-md/screen-ui/back-and-gestures.md +36 -0
- package/docs-md/screen-ui/bar-buttons.md +4 -4
- package/docs-md/screen-ui/search.md +1 -1
- package/docs-md/screen-ui/system-ui.md +16 -11
- package/docs-md/screen-ui/toolbar-and-fab.md +2 -2
- package/docs-md/screen-ui/top-bar.md +8 -4
- package/docs-md/start/installation.md +1 -1
- package/ios/Animations/ANNFrameDriver.mm +2 -0
- package/ios/Animations/Elements/ANNSharedElementFlight.mm +70 -4
- package/ios/Animations/Interpolators/ANNInterpolate.h +2 -30
- package/ios/Animations/Interpolators/ANNTransformParts.h +48 -0
- package/ios/Animations/Interpolators/ANNTransformParts.mm +507 -0
- package/ios/Animations/Interpolators/ANNViewTracks.h +6 -4
- package/ios/Animations/Interpolators/ANNViewTracks.mm +49 -9
- package/ios/CLAUDE.md +17 -15
- package/ios/CORE.md +30 -26
- package/ios/Contract/ANNCommandAnswer.h +9 -2
- package/ios/Contract/ANNCommandAnswer.mm +25 -12
- package/ios/Contract/ANNCommandRunner.h +8 -7
- package/ios/Contract/ANNCommandRunner.mm +21 -27
- package/ios/Contract/ANNLayoutNode.h +3 -3
- package/ios/Contract/ANNLayoutReader.h +3 -3
- package/ios/Core/ANNAppearanceDispatcher.h +5 -4
- package/ios/Core/ANNAppearanceDispatcher.mm +12 -1
- package/ios/Core/ANNJavaScriptCalls.h +11 -12
- package/ios/Core/ANNJavaScriptCalls.mm +7 -16
- package/ios/Core/ANNNavigationState.h +4 -6
- package/ios/Core/ANNNavigationState.mm +0 -4
- package/ios/Core/ANNReloadCoordinator.mm +4 -8
- package/ios/Core/ApexNavigation+Internal.h +1 -1
- package/ios/Layouts/Arrangement/ANNArrangementState.h +2 -17
- package/ios/Layouts/Arrangement/ANNArrangementState.mm +0 -38
- package/ios/Layouts/Arrangement/ANNArrangementViewController.mm +4 -4
- package/ios/Layouts/Root/ANNSplashViewController.h +5 -3
- package/ios/Layouts/Root/ANNSplashViewController.mm +147 -0
- package/ios/Layouts/Screen/ANNComponentViewController.h +2 -2
- package/ios/Layouts/Screen/ANNComponentViewController.mm +9 -22
- package/ios/Layouts/Screen/ANNPreviewInteraction.h +7 -4
- package/ios/Layouts/Screen/ANNPreviewInteraction.mm +72 -10
- package/ios/Layouts/Screen/ANNScreenEnvironment.h +63 -0
- package/ios/Layouts/Screen/ANNScreenEnvironment.mm +181 -0
- package/ios/Layouts/Screen/ANNScreenRegions.h +3 -18
- package/ios/Layouts/Screen/ANNScreenRegions.mm +0 -38
- package/ios/Layouts/Screen/ANNScreenSafeArea.h +4 -9
- package/ios/Layouts/Screen/ANNScreenSafeArea.mm +14 -16
- package/ios/Layouts/Stack/ANNPreviewPush.h +7 -6
- package/ios/Layouts/Stack/ANNPreviewPush.mm +142 -27
- package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.h +12 -1
- package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.mm +23 -0
- package/ios/Layouts/Tabs/ANNBottomTabsController.mm +2 -1
- package/ios/Layouts/Tabs/ANNBottomTabsCustomRow.mm +8 -3
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +4 -0
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +123 -36
- package/ios/Layouts/Tabs/ANNTabsDesign.h +6 -5
- package/ios/Layouts/Tabs/ANNTabsDesign.mm +9 -1
- package/ios/Layouts/Tabs/UITabBar+utils.h +5 -5
- package/ios/Layouts/Tabs/UITabBar+utils.mm +32 -13
- package/ios/Options/ANNBottomTabsOptions.h +2 -0
- package/ios/Options/ANNBottomTabsOptions.mm +1 -0
- package/ios/Options/ANNOptions.h +18 -0
- package/ios/Options/ANNOptions.mm +112 -1
- package/ios/Options/ANNOptionsReader.mm +110 -0
- package/ios/Options/Params/ANNParam.h +15 -0
- package/ios/Options/Params/ANNParam.mm +61 -0
- package/ios/Options/Params/Color.h +3 -0
- package/ios/Options/Params/Color.mm +21 -0
- package/ios/Options/Params/Image.h +4 -0
- package/ios/Options/Params/Image.mm +20 -0
- package/ios/React/ANNReactView.mm +6 -25
- package/ios/React/ANNSafeAreaViewComponentView.mm +26 -15
- package/ios/React/ANNSurfaceHostingView.h +6 -0
- package/ios/React/ANNSurfaceHostingView.mm +116 -0
- package/ios/TopBar/ANNBackButtonPresenter.mm +6 -5
- package/ios/TopBar/ANNBarAppearance.h +2 -1
- package/ios/TopBar/ANNBarAppearance.mm +111 -30
- package/ios/TopBar/ANNBarBackgroundComponent.mm +16 -0
- package/ios/TopBar/ANNBarButtonComponentView.h +4 -4
- package/ios/TopBar/ANNBarButtonComponentView.mm +1 -1
- package/ios/TopBar/ANNBarButtons.h +5 -3
- package/ios/TopBar/ANNBarButtons.mm +186 -17
- package/ios/TopBar/ANNLargeTitlePresenter.mm +24 -4
- package/ios/TopBar/ANNMainList.h +3 -0
- package/ios/TopBar/ANNMainList.mm +4 -0
- package/ios/TopBar/ANNScrollEdgeEffects.h +22 -6
- package/ios/TopBar/ANNScrollEdgeEffects.mm +123 -16
- package/ios/TopBar/ANNSearchPresenter.mm +2 -2
- package/ios/TopBar/ANNSearchResultsViewController.mm +5 -5
- package/ios/TopBar/ANNTopBarPresenter.h +3 -2
- package/ios/TopBar/ANNTopBarPresenter.mm +95 -22
- package/ios/TurboModules/ANNTurboModule.mm +20 -16
- package/lib/module/ARCHITECTURE.md +8 -4
- package/lib/module/api/commands/commands.js +54 -6
- package/lib/module/api/commands/commands.js.map +1 -1
- package/lib/module/api/hooks/useSafeAreaInsets.js +4 -3
- package/lib/module/api/hooks/useSafeAreaInsets.js.map +1 -1
- package/lib/module/api/options/OptionsChain.js +2 -2
- package/lib/module/engine/commands.js +21 -1
- package/lib/module/engine/commands.js.map +1 -1
- package/lib/module/engine/places.js +8 -0
- package/lib/module/engine/places.js.map +1 -1
- package/lib/module/engine/screens.js +29 -4
- package/lib/module/engine/screens.js.map +1 -1
- package/lib/typescript/api/app/createNavigation.d.ts +3 -3
- package/lib/typescript/api/commands/commands.d.ts.map +1 -1
- package/lib/typescript/api/commands/presentation.d.ts +4 -3
- package/lib/typescript/api/commands/presentation.d.ts.map +1 -1
- package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts +4 -3
- package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts.map +1 -1
- package/lib/typescript/api/options/OptionsChain.d.ts +2 -2
- package/lib/typescript/engine/commands.d.ts.map +1 -1
- package/lib/typescript/engine/places.d.ts +2 -0
- package/lib/typescript/engine/places.d.ts.map +1 -1
- package/lib/typescript/engine/screens.d.ts +20 -6
- package/lib/typescript/engine/screens.d.ts.map +1 -1
- package/lib/typescript/interfaces/Options.d.ts +1 -1
- package/lib/typescript/interfaces/Options.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/animations.d.ts +30 -9
- package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/bottomTabs.d.ts +18 -5
- package/lib/typescript/interfaces/options/bottomTabs.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/buttons.d.ts +8 -8
- package/lib/typescript/interfaces/options/stack.d.ts +9 -0
- package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/system.d.ts +8 -5
- package/lib/typescript/interfaces/options/system.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/toolbar.d.ts +2 -2
- package/lib/typescript/interfaces/options/topBar.d.ts +32 -29
- package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ARCHITECTURE.md +8 -4
- package/src/api/app/createNavigation.ts +3 -3
- package/src/api/commands/commands.ts +48 -7
- package/src/api/commands/presentation.ts +4 -3
- package/src/api/hooks/useSafeAreaInsets.ts +4 -3
- package/src/api/options/OptionsChain.ts +2 -2
- package/src/engine/commands.ts +30 -6
- package/src/engine/places.ts +9 -0
- package/src/engine/screens.tsx +29 -8
- package/src/interfaces/Options.ts +1 -0
- package/src/interfaces/options/animations.ts +31 -9
- package/src/interfaces/options/bottomTabs.ts +18 -5
- package/src/interfaces/options/buttons.ts +8 -8
- package/src/interfaces/options/stack.ts +9 -0
- package/src/interfaces/options/system.ts +8 -5
- package/src/interfaces/options/toolbar.ts +2 -2
- package/src/interfaces/options/topBar.ts +32 -29
- package/android/src/main/java/dev/apexnative/navigation/react/ArrangementStates.kt +0 -42
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +0 -70
- package/android/src/main/java/dev/apexnative/navigation/react/events/NavigationStateReporter.kt +0 -62
- package/android/src/main/java/dev/apexnative/navigation/react/events/StateHold.kt +0 -62
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowPlacer.kt +0 -223
- package/ios/Layouts/Screen/ANNScreenInsets.h +0 -30
- package/ios/Layouts/Screen/ANNScreenInsets.mm +0 -70
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#import "ANNReloadCoordinator.h"
|
|
2
|
-
#import "ANNArrangementState.h"
|
|
3
2
|
#import "ANNAssert.h"
|
|
4
3
|
#import "ANNErrorTexts.h"
|
|
5
4
|
#import "ANNHinge.h"
|
|
@@ -12,9 +11,8 @@
|
|
|
12
11
|
#import "ANNPreviewInteraction.h"
|
|
13
12
|
#import "ANNPreviewPush.h"
|
|
14
13
|
#import "ANNRootCoordinator.h"
|
|
15
|
-
#import "
|
|
14
|
+
#import "ANNScreenEnvironment.h"
|
|
16
15
|
#import "ANNScreenReadiness.h"
|
|
17
|
-
#import "ANNScreenRegions.h"
|
|
18
16
|
#import "ANNSplashViewController.h"
|
|
19
17
|
#import "ANNSplitViewCommands.h"
|
|
20
18
|
#import "ANNTabsPreloadWindow.h"
|
|
@@ -154,12 +152,10 @@ static BOOL ANNWatchingBundles;
|
|
|
154
152
|
[oldScreens addObjectsFromArray:[ANNSplitViewCommands takeAllForReload]];
|
|
155
153
|
// The new JavaScript counts its ids anew: nothing the safe area views of the old screens said stays ready for a new
|
|
156
154
|
// screen with the same id. The old surfaces are stopped or silent. The same for what the old screens kept for
|
|
157
|
-
// JavaScript
|
|
158
|
-
// insets or regions again: its emitter is not current).
|
|
155
|
+
// JavaScript, their environment: insets and padding, regions and arrangements (an old screen still in a window
|
|
156
|
+
// writes no insets or regions again: its emitter is not current).
|
|
159
157
|
[ANNScreenReadiness reset];
|
|
160
|
-
[
|
|
161
|
-
[ANNScreenRegions removeAll];
|
|
162
|
-
[ANNArrangementState removeAll];
|
|
158
|
+
[ANNScreenEnvironment removeAll];
|
|
163
159
|
// 6. The window shows the blank screen, which counts as the splash: a reload is not a launch. The
|
|
164
160
|
// reset runs from the second bundle of the process on.
|
|
165
161
|
if (window) {
|
|
@@ -18,7 +18,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
18
18
|
+ (nullable ANNNativeScreenCreator)nativeScreenCreatorForName:(NSString *)name;
|
|
19
19
|
|
|
20
20
|
/// Whether a native screen is registered under the name by `registerNativeScreen:creator:` (the registration of 8.x is
|
|
21
|
-
/// gone). Any thread
|
|
21
|
+
/// gone). Any thread (the layout check of a command reads it on the main queue).
|
|
22
22
|
+ (BOOL)hasNativeScreenNamed:(NSString *)name;
|
|
23
23
|
|
|
24
24
|
@end
|
|
@@ -1,30 +1,15 @@
|
|
|
1
|
-
#import "ANNEventEmitter.h"
|
|
2
1
|
#import <UIKit/UIKit.h>
|
|
3
2
|
|
|
4
3
|
NS_ASSUME_NONNULL_BEGIN
|
|
5
4
|
|
|
6
|
-
/// The state of each screen of
|
|
7
|
-
///
|
|
8
|
-
/// thread for a screen's first render, and gets every change as `ANN.ArrangementChanged`.
|
|
5
|
+
/// Whether the app can show pairs at all. The state of each screen of a pair (`useArrangement`) is kept for JavaScript
|
|
6
|
+
/// by `ANNScreenEnvironment`.
|
|
9
7
|
@interface ANNArrangementState : NSObject
|
|
10
8
|
|
|
11
9
|
/// Whether the app shows arrangements (`getStartInfo().arrangements`): built with the iOS 27.1 SDK and running on
|
|
12
10
|
/// iOS 27.1 or later. Without them JavaScript sends only the primary, as a screen. Any thread.
|
|
13
11
|
+ (BOOL)canShowArrangements;
|
|
14
12
|
|
|
15
|
-
/// Keeps the screen's state and tells when it changed; nothing goes without an emitter. Main queue.
|
|
16
|
-
+ (void)setState:(NSDictionary<NSString *, id> *)state
|
|
17
|
-
forScreenId:(NSString *)screenId
|
|
18
|
-
eventEmitter:(nullable ANNEventEmitter *)eventEmitter;
|
|
19
|
-
|
|
20
|
-
/// The screen's state, or nil for a screen outside a pair. Any thread.
|
|
21
|
-
+ (nullable NSDictionary<NSString *, id> *)stateForScreenId:(NSString *)screenId;
|
|
22
|
-
|
|
23
|
-
+ (void)removeScreenId:(NSString *)screenId;
|
|
24
|
-
|
|
25
|
-
/// Forgets every screen: after a reload the new JavaScript counts its ids anew. Main queue.
|
|
26
|
-
+ (void)removeAll;
|
|
27
|
-
|
|
28
13
|
@end
|
|
29
14
|
|
|
30
15
|
NS_ASSUME_NONNULL_END
|
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
#import "ANNArrangementState.h"
|
|
2
2
|
#import "ANNArrangementViewController.h"
|
|
3
3
|
|
|
4
|
-
static NSMutableDictionary<NSString *, NSDictionary<NSString *, id> *> *ANNArrangementStateById;
|
|
5
|
-
|
|
6
4
|
@implementation ANNArrangementState
|
|
7
5
|
|
|
8
|
-
+ (void)initialize {
|
|
9
|
-
if (self == ANNArrangementState.class) {
|
|
10
|
-
ANNArrangementStateById = [NSMutableDictionary new];
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
6
|
+ (BOOL)canShowArrangements {
|
|
15
7
|
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 270100
|
|
16
8
|
if (@available(iOS 27.1, *)) {
|
|
@@ -20,34 +12,4 @@ static NSMutableDictionary<NSString *, NSDictionary<NSString *, id> *> *ANNArran
|
|
|
20
12
|
return NO;
|
|
21
13
|
}
|
|
22
14
|
|
|
23
|
-
+ (void)setState:(NSDictionary<NSString *, id> *)state
|
|
24
|
-
forScreenId:(NSString *)screenId
|
|
25
|
-
eventEmitter:(ANNEventEmitter *)eventEmitter {
|
|
26
|
-
@synchronized(ANNArrangementStateById) {
|
|
27
|
-
if ([ANNArrangementStateById[screenId] isEqualToDictionary:state]) {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
ANNArrangementStateById[screenId] = state;
|
|
31
|
-
}
|
|
32
|
-
[eventEmitter sendArrangementChanged:screenId state:state];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
+ (NSDictionary<NSString *, id> *)stateForScreenId:(NSString *)screenId {
|
|
36
|
-
@synchronized(ANNArrangementStateById) {
|
|
37
|
-
return ANNArrangementStateById[screenId];
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
+ (void)removeScreenId:(NSString *)screenId {
|
|
42
|
-
@synchronized(ANNArrangementStateById) {
|
|
43
|
-
[ANNArrangementStateById removeObjectForKey:screenId];
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
+ (void)removeAll {
|
|
48
|
-
@synchronized(ANNArrangementStateById) {
|
|
49
|
-
[ANNArrangementStateById removeAllObjects];
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
15
|
@end
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 270100
|
|
4
4
|
|
|
5
|
-
#import "ANNArrangementState.h"
|
|
6
5
|
#import "ANNComponentViewController.h"
|
|
7
6
|
#import "ANNEventContract.h"
|
|
8
7
|
#import "ANNExternalViewController.h"
|
|
8
|
+
#import "ANNScreenEnvironment.h"
|
|
9
9
|
#import "ANNShownScreens.h"
|
|
10
10
|
#import "ANNUtils.h"
|
|
11
11
|
|
|
@@ -298,9 +298,9 @@ static BOOL ANNIsShown(UIViewController *screen, UIArrangementViewState *state,
|
|
|
298
298
|
ofScreen:(UIViewController *)screen
|
|
299
299
|
eventEmitter:(ANNEventEmitter *)eventEmitter {
|
|
300
300
|
if (ANNKeepsState(screen)) {
|
|
301
|
-
[
|
|
302
|
-
|
|
303
|
-
|
|
301
|
+
[ANNScreenEnvironment setArrangement:state
|
|
302
|
+
forScreenId:screen.layoutInfo.componentId
|
|
303
|
+
eventEmitter:eventEmitter];
|
|
304
304
|
}
|
|
305
305
|
}
|
|
306
306
|
|
|
@@ -7,9 +7,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
7
7
|
/// either at once. The status bar follows the Info.plist keys `UIStatusBarStyle` and `UIStatusBarHidden`. Main queue.
|
|
8
8
|
@interface ANNSplashViewController : UIViewController
|
|
9
9
|
|
|
10
|
-
/// The copy of the launch screen
|
|
11
|
-
///
|
|
12
|
-
///
|
|
10
|
+
/// The copy of the launch screen. When iOS builds it from the Info.plist (`UILaunchScreen`, or the default of
|
|
11
|
+
/// `UILaunchScreens`), the same color, image and bars as plain views; else the initial view controller of
|
|
12
|
+
/// `UILaunchStoryboardName` as a child (a storyboard that doesn't load: the first view of the nib of that name), else
|
|
13
|
+
/// the legacy launch images, full screen. Nil when the app has none of these: the window then has no root view
|
|
14
|
+
/// controller until the first root.
|
|
13
15
|
+ (nullable instancetype)launchScreenCopyWithBounds:(CGRect)bounds;
|
|
14
16
|
|
|
15
17
|
/// The blank screen after a reload: clear when the default options give the window a color (`window.backgroundColor`),
|
|
@@ -34,9 +34,63 @@ static NSArray<NSString *> *ANNLegacyLaunchImageNames(CGFloat height) {
|
|
|
34
34
|
return names;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
static NSDictionary *ANNDictionaryOrNil(id value) {
|
|
38
|
+
return [value isKindOfClass:NSDictionary.class] ? value : nil;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static NSString *ANNStringOrNil(id value) {
|
|
42
|
+
return [value isKindOfClass:NSString.class] && [value length] > 0 ? value : nil;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// The definition that a plural key (`UILaunchScreens`, `UILaunchStoryboards`) marks as its default.
|
|
46
|
+
static NSDictionary *ANNDefaultDefinition(id plural, NSString *definitionsKey,
|
|
47
|
+
NSString *identifierKey, NSString *defaultKey) {
|
|
48
|
+
NSDictionary *dictionary = ANNDictionaryOrNil(plural);
|
|
49
|
+
NSArray *definitions = dictionary[definitionsKey];
|
|
50
|
+
NSString *defaultIdentifier = ANNStringOrNil(dictionary[defaultKey]);
|
|
51
|
+
if (![definitions isKindOfClass:NSArray.class] || !defaultIdentifier) {
|
|
52
|
+
return nil;
|
|
53
|
+
}
|
|
54
|
+
for (id definition in definitions) {
|
|
55
|
+
NSDictionary *entry = ANNDictionaryOrNil(definition);
|
|
56
|
+
if ([entry[identifierKey] isEqual:defaultIdentifier]) {
|
|
57
|
+
return entry;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return nil;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// The launch screen that iOS builds from the Info.plist without a storyboard, or nil when it shows a storyboard. Of
|
|
64
|
+
// `UILaunchStoryboardName`, `UILaunchScreen`, the default of `UILaunchStoryboards` and the default of
|
|
65
|
+
// `UILaunchScreens`, the last one present wins (SplashBoard's `XBApplicationLaunchCompatibilityInfo`). A launch by a
|
|
66
|
+
// link that `UILaunchScreens` maps to a screen of its own still gets the default here.
|
|
67
|
+
static NSDictionary *ANNLaunchScreenConfiguration(NSBundle *bundle) {
|
|
68
|
+
NSDictionary *screen =
|
|
69
|
+
ANNDefaultDefinition([bundle objectForInfoDictionaryKey:@"UILaunchScreens"],
|
|
70
|
+
@"UILaunchScreenDefinitions", @"UILaunchScreenIdentifier",
|
|
71
|
+
@"UIDefaultLaunchScreen");
|
|
72
|
+
if (screen) {
|
|
73
|
+
return screen;
|
|
74
|
+
}
|
|
75
|
+
if (ANNDefaultDefinition([bundle objectForInfoDictionaryKey:@"UILaunchStoryboards"],
|
|
76
|
+
@"UILaunchStoryboardDefinitions", @"UILaunchStoryboardIdentifier",
|
|
77
|
+
@"UIDefaultLaunchStoryboard")) {
|
|
78
|
+
return nil;
|
|
79
|
+
}
|
|
80
|
+
return ANNDictionaryOrNil([bundle objectForInfoDictionaryKey:@"UILaunchScreen"]);
|
|
81
|
+
}
|
|
82
|
+
|
|
37
83
|
@implementation ANNSplashViewController {
|
|
38
84
|
UIViewController *_content;
|
|
39
85
|
UIView *_contentView;
|
|
86
|
+
NSDictionary *_launchScreen;
|
|
87
|
+
// The parts of a launch screen built from `UILaunchScreen`, laid out once the safe area is known.
|
|
88
|
+
UIImageView *_imageView;
|
|
89
|
+
BOOL _imageRespectsSafeArea;
|
|
90
|
+
UINavigationBar *_navigationBar;
|
|
91
|
+
UIImageView *_navigationBarImageView;
|
|
92
|
+
UIView *_bottomBar;
|
|
93
|
+
UIImageView *_bottomBarImageView;
|
|
40
94
|
}
|
|
41
95
|
|
|
42
96
|
- (instancetype)initWithContent:(UIViewController *)content
|
|
@@ -52,6 +106,12 @@ static NSArray<NSString *> *ANNLegacyLaunchImageNames(CGFloat height) {
|
|
|
52
106
|
|
|
53
107
|
+ (instancetype)launchScreenCopyWithBounds:(CGRect)bounds {
|
|
54
108
|
NSBundle *bundle = NSBundle.mainBundle;
|
|
109
|
+
NSDictionary *launchScreen = ANNLaunchScreenConfiguration(bundle);
|
|
110
|
+
if (launchScreen) {
|
|
111
|
+
ANNSplashViewController *copy = [[self alloc] initWithContent:nil contentView:nil blank:NO];
|
|
112
|
+
copy->_launchScreen = launchScreen;
|
|
113
|
+
return copy;
|
|
114
|
+
}
|
|
55
115
|
NSString *storyboardName = [bundle objectForInfoDictionaryKey:@"UILaunchStoryboardName"];
|
|
56
116
|
if ([storyboardName isKindOfClass:NSString.class] && storyboardName.length > 0) {
|
|
57
117
|
UIViewController *initial = nil;
|
|
@@ -97,6 +157,10 @@ static NSArray<NSString *> *ANNLegacyLaunchImageNames(CGFloat height) {
|
|
|
97
157
|
view.backgroundColor = windowHasColor ? UIColor.clearColor : UIColor.systemBackgroundColor;
|
|
98
158
|
return;
|
|
99
159
|
}
|
|
160
|
+
if (_launchScreen) {
|
|
161
|
+
[self buildLaunchScreen];
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
100
164
|
UIView *contentView = _contentView;
|
|
101
165
|
if (_content) {
|
|
102
166
|
[self addChildViewController:_content];
|
|
@@ -109,6 +173,89 @@ static NSArray<NSString *> *ANNLegacyLaunchImageNames(CGFloat height) {
|
|
|
109
173
|
[_content didMoveToParentViewController:self];
|
|
110
174
|
}
|
|
111
175
|
|
|
176
|
+
#pragma mark - The launch screen of `UILaunchScreen`
|
|
177
|
+
|
|
178
|
+
// Built as SplashBoard builds it: the named color over the whole screen, else the system background; the named image
|
|
179
|
+
// at its own size in the middle of the screen or of its safe area, stretched instead when the asset has slicing; a tab
|
|
180
|
+
// bar or a toolbar at the bottom and a navigation bar at the top, the system's own bars, or the bar's named image
|
|
181
|
+
// stretched over the bar's place. A name the asset catalog doesn't have leaves its part out, as iOS does.
|
|
182
|
+
- (void)buildLaunchScreen {
|
|
183
|
+
UIView *view = self.view;
|
|
184
|
+
NSString *colorName = ANNStringOrNil(_launchScreen[@"UIColorName"]);
|
|
185
|
+
UIColor *color = colorName ? [UIColor colorNamed:colorName] : nil;
|
|
186
|
+
view.backgroundColor = color ?: UIColor.systemBackgroundColor;
|
|
187
|
+
|
|
188
|
+
NSString *imageName = ANNStringOrNil(_launchScreen[@"UIImageName"]);
|
|
189
|
+
UIImage *image = imageName ? [UIImage imageNamed:imageName] : nil;
|
|
190
|
+
if (image) {
|
|
191
|
+
_imageView = [[UIImageView alloc] initWithImage:image];
|
|
192
|
+
_imageView.contentMode = UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero)
|
|
193
|
+
? UIViewContentModeCenter
|
|
194
|
+
: UIViewContentModeScaleToFill;
|
|
195
|
+
id respects = _launchScreen[@"UIImageRespectsSafeAreaInsets"];
|
|
196
|
+
_imageRespectsSafeArea = [respects respondsToSelector:@selector(boolValue)] &&
|
|
197
|
+
[respects boolValue];
|
|
198
|
+
[view addSubview:_imageView];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// A toolbar takes the bottom before a tab bar; a navigation bar comes on top of either.
|
|
202
|
+
id toolbar = _launchScreen[@"UIToolbar"];
|
|
203
|
+
id tabBar = _launchScreen[@"UITabBar"];
|
|
204
|
+
if (toolbar || tabBar) {
|
|
205
|
+
_bottomBar = toolbar ? [UIToolbar new] : [UITabBar new];
|
|
206
|
+
_bottomBarImageView = [self addBar:_bottomBar configuration:toolbar ?: tabBar];
|
|
207
|
+
}
|
|
208
|
+
id navigationBar = _launchScreen[@"UINavigationBar"];
|
|
209
|
+
if (navigationBar) {
|
|
210
|
+
_navigationBar = [UINavigationBar new];
|
|
211
|
+
_navigationBarImageView = [self addBar:_navigationBar configuration:navigationBar];
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Adds the bar, or its image in its place when its configuration names one that loads.
|
|
216
|
+
- (UIImageView *)addBar:(UIView *)bar configuration:(id)configuration {
|
|
217
|
+
NSString *name = ANNStringOrNil(ANNDictionaryOrNil(configuration)[@"UIImageName"]);
|
|
218
|
+
UIImage *image = name ? [UIImage imageNamed:name] : nil;
|
|
219
|
+
UIImageView *imageView = image ? [[UIImageView alloc] initWithImage:image] : nil;
|
|
220
|
+
bar.hidden = imageView != nil;
|
|
221
|
+
[self.view addSubview:bar];
|
|
222
|
+
if (imageView) {
|
|
223
|
+
[self.view addSubview:imageView];
|
|
224
|
+
}
|
|
225
|
+
return imageView;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// The safe area is known only in the window, so the parts are laid out here, after every change of it.
|
|
229
|
+
- (void)viewDidLayoutSubviews {
|
|
230
|
+
[super viewDidLayoutSubviews];
|
|
231
|
+
if (!_launchScreen) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
CGRect bounds = self.view.bounds;
|
|
235
|
+
UIEdgeInsets safeArea = self.view.safeAreaInsets;
|
|
236
|
+
_imageView.frame =
|
|
237
|
+
_imageRespectsSafeArea ? UIEdgeInsetsInsetRect(bounds, safeArea) : bounds;
|
|
238
|
+
[self layOutBar:_bottomBar imageView:_bottomBarImageView atBottom:YES];
|
|
239
|
+
[self layOutBar:_navigationBar imageView:_navigationBarImageView atBottom:NO];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// A bar is as tall as UIKit makes it, plus the safe area on its edge of the screen, and as wide as the screen.
|
|
243
|
+
- (void)layOutBar:(UIView *)bar
|
|
244
|
+
imageView:(UIImageView *)imageView
|
|
245
|
+
atBottom:(BOOL)atBottom {
|
|
246
|
+
if (!bar) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
CGRect bounds = self.view.bounds;
|
|
250
|
+
UIEdgeInsets safeArea = self.view.safeAreaInsets;
|
|
251
|
+
CGFloat height = [bar sizeThatFits:bounds.size].height +
|
|
252
|
+
(atBottom ? safeArea.bottom : safeArea.top);
|
|
253
|
+
CGFloat y = atBottom ? CGRectGetMaxY(bounds) - height : CGRectGetMinY(bounds);
|
|
254
|
+
CGRect frame = CGRectMake(CGRectGetMinX(bounds), y, CGRectGetWidth(bounds), height);
|
|
255
|
+
bar.frame = frame;
|
|
256
|
+
imageView.frame = frame;
|
|
257
|
+
}
|
|
258
|
+
|
|
112
259
|
#pragma mark - The status bar, as the launch screen had it
|
|
113
260
|
|
|
114
261
|
- (UIStatusBarStyle)preferredStatusBarStyle {
|
|
@@ -11,8 +11,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
11
11
|
///
|
|
12
12
|
/// Its view has the size of the screen of the navigation window's scene; the content sits in the root frame, by the one
|
|
13
13
|
/// "under bars" rule (`ANNScreenBars`). Every recompute of the frame (a layout, a change of the safe area or the
|
|
14
|
-
/// traits, `mergeOptions`, an appearance) gives the
|
|
15
|
-
/// (`
|
|
14
|
+
/// traits, `mergeOptions`, an appearance) gives the store the screen's insets, padding and regions
|
|
15
|
+
/// (`ANNScreenEnvironment`). Its presenter is an `ANNComponentPresenter`.
|
|
16
16
|
///
|
|
17
17
|
/// The screen answers UIKit's questions about the system UI (native screens too): the status bar's style and visibility
|
|
18
18
|
/// (never animated), the orientations, the home indicator, the deferred edges and the vertical bars, from its resolved
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
#import "ANNComponentViewController.h"
|
|
2
2
|
#import "ANNAppearanceDispatcher.h"
|
|
3
|
-
#import "ANNArrangementState.h"
|
|
4
3
|
#import "ANNBottomTabsController.h"
|
|
5
4
|
#import "ANNComponentPresenter.h"
|
|
6
|
-
#import "ANNEventContract.h"
|
|
7
5
|
#import "ANNHinge.h"
|
|
8
6
|
#import "ANNKeyCommands.h"
|
|
9
7
|
#import "ANNNodeRegistry.h"
|
|
@@ -11,7 +9,7 @@
|
|
|
11
9
|
#import "ANNPendingLayers.h"
|
|
12
10
|
#import "ANNPreviewPush.h"
|
|
13
11
|
#import "ANNScreenBars.h"
|
|
14
|
-
#import "
|
|
12
|
+
#import "ANNScreenEnvironment.h"
|
|
15
13
|
#import "ANNScreenReadiness.h"
|
|
16
14
|
#import "ANNScreenRegions.h"
|
|
17
15
|
#import "ANNScreenSafeArea.h"
|
|
@@ -432,7 +430,7 @@
|
|
|
432
430
|
return UIRectEdgeNone;
|
|
433
431
|
}
|
|
434
432
|
|
|
435
|
-
// The insets and the padding of a React screen or a slot screen go to the
|
|
433
|
+
// The insets and the padding of a React screen or a slot screen go to the store, which keeps them, gives JavaScript
|
|
436
434
|
// what still covers the content and tells only of changes; the regions in the same pass. Only in a window and with an
|
|
437
435
|
// id; never after the destroy, which removed them (a late layout would bring them back), nor for the JavaScript that
|
|
438
436
|
// went (an old screen held after a reload: the new JavaScript may give its id to a new screen).
|
|
@@ -453,22 +451,18 @@
|
|
|
453
451
|
UINavigationController *stack = [self shownNavigation];
|
|
454
452
|
ANNScreenPaddingContext context = {
|
|
455
453
|
.inStack = stack != nil,
|
|
456
|
-
// The stack's toolbar, by the rule the stack shows it with: one that `topBar.hideOnScroll`
|
|
457
|
-
// hid with the bar still counts.
|
|
458
|
-
.stackShowsToolbar = stack != nil && options.toolbar.shouldShow,
|
|
459
|
-
.windowBottomSafeArea = window.safeAreaInsets.bottom,
|
|
460
454
|
.verticalBarSide = [self verticalBarSide],
|
|
461
455
|
};
|
|
462
456
|
UIEdgeInsets padding = ANNScreenPadding(insets, options, context);
|
|
463
|
-
[
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
457
|
+
[ANNScreenEnvironment setInsets:insets
|
|
458
|
+
padding:padding
|
|
459
|
+
forScreenId:componentId
|
|
460
|
+
eventEmitter:eventEmitter];
|
|
467
461
|
[self updateRegions];
|
|
468
462
|
}
|
|
469
463
|
|
|
470
464
|
// The regions in the same pass, and on the next turn after a change of the hinge's state (`hingeDidChange:`). Nil
|
|
471
|
-
// before iOS 27.1: nothing to keep.
|
|
465
|
+
// before iOS 27.1: nothing to keep. The store neither keeps nor sends an empty first result.
|
|
472
466
|
- (void)updateRegions {
|
|
473
467
|
UIView<ANNComponentProtocol> *content = _reactView;
|
|
474
468
|
NSString *componentId = self.layoutInfo.componentId;
|
|
@@ -483,12 +477,7 @@
|
|
|
483
477
|
if (!regions) {
|
|
484
478
|
return;
|
|
485
479
|
}
|
|
486
|
-
|
|
487
|
-
BOOL empty = [regions[keys.division] count] == 0 && [regions[keys.occlusion] count] == 0;
|
|
488
|
-
if (empty && ![ANNScreenRegions regionsForScreenId:componentId]) {
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
[ANNScreenRegions setRegions:regions forScreenId:componentId eventEmitter:eventEmitter];
|
|
480
|
+
[ANNScreenEnvironment setRegions:regions forScreenId:componentId eventEmitter:eventEmitter];
|
|
492
481
|
}
|
|
493
482
|
|
|
494
483
|
- (void)hingeDidChange:(NSNotification *)notification {
|
|
@@ -690,9 +679,7 @@
|
|
|
690
679
|
// its destroy, as at a reload) keeps it.
|
|
691
680
|
NSString *componentId = self.layoutInfo.componentId;
|
|
692
681
|
if (componentId && ![ANNNodeRegistry nodeForComponentId:componentId]) {
|
|
693
|
-
[
|
|
694
|
-
[ANNScreenRegions removeScreenId:componentId];
|
|
695
|
-
[ANNArrangementState removeScreenId:componentId];
|
|
682
|
+
[ANNScreenEnvironment removeScreenId:componentId];
|
|
696
683
|
[ANNScreenReadiness removeScreenId:componentId];
|
|
697
684
|
}
|
|
698
685
|
}
|
|
@@ -9,15 +9,18 @@
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Previews `preview` from `view`, the component view with the tag `reactTag`, and replaces an earlier
|
|
12
|
-
* preview of the view. `
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* preview of the view. `prepare` runs as each press starts, before the menu shows the preview: it
|
|
13
|
+
* renders the screen if it hasn't yet, and returns NO when the preview went instead (no menu shows
|
|
14
|
+
* then). `commit` runs on a tap on the preview and returns whether it showed the screen; the
|
|
15
|
+
* preview goes then, as the push it stands for is done. `dismissed` runs once when the preview
|
|
16
|
+
* goes without being shown as a screen: replaced by another preview of the view, after Fabric gave
|
|
17
|
+
* the view to another component, or when `source`, the screen of the view, goes.
|
|
16
18
|
*/
|
|
17
19
|
+ (void)attachPreview:(UIViewController *)preview
|
|
18
20
|
toView:(UIView *)view
|
|
19
21
|
reactTag:(NSInteger)reactTag
|
|
20
22
|
source:(UIViewController *)source
|
|
23
|
+
prepare:(BOOL (^)(void))prepare
|
|
21
24
|
commit:(BOOL (^)(void))commit
|
|
22
25
|
dismissed:(void (^)(void))dismissed;
|
|
23
26
|
|
|
@@ -24,15 +24,23 @@ static NSMutableSet *ANNPreviews(void) {
|
|
|
24
24
|
@implementation ANNPreviewInteraction {
|
|
25
25
|
UIViewController *_preview;
|
|
26
26
|
NSInteger _reactTag;
|
|
27
|
+
__weak UIViewController *_source;
|
|
28
|
+
BOOL (^_prepare)(void);
|
|
27
29
|
BOOL (^_commit)(void);
|
|
28
30
|
void (^_dismissed)(void);
|
|
29
31
|
UIContextMenuInteraction *_interaction;
|
|
32
|
+
// The snapshots of the view that the menu of this press lifts and puts back, over the view until
|
|
33
|
+
// the menu is gone. Each press has its own list: a new press may start while the last menu goes.
|
|
34
|
+
NSMutableArray<UIView *> *_snapshots;
|
|
35
|
+
// The list of the menu that goes now: its end removes them.
|
|
36
|
+
__weak NSMutableArray<UIView *> *_endingSnapshots;
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
+ (void)attachPreview:(UIViewController *)preview
|
|
33
40
|
toView:(UIView *)view
|
|
34
41
|
reactTag:(NSInteger)reactTag
|
|
35
42
|
source:(UIViewController *)source
|
|
43
|
+
prepare:(BOOL (^)(void))prepare
|
|
36
44
|
commit:(BOOL (^)(void))commit
|
|
37
45
|
dismissed:(void (^)(void))dismissed {
|
|
38
46
|
if (!view || !preview) {
|
|
@@ -45,6 +53,8 @@ static NSMutableSet *ANNPreviews(void) {
|
|
|
45
53
|
ANNPreviewInteraction *previewInteraction = [ANNPreviewInteraction new];
|
|
46
54
|
previewInteraction->_preview = preview;
|
|
47
55
|
previewInteraction->_reactTag = reactTag;
|
|
56
|
+
previewInteraction->_source = source;
|
|
57
|
+
previewInteraction->_prepare = [prepare copy];
|
|
48
58
|
previewInteraction->_commit = [commit copy];
|
|
49
59
|
previewInteraction->_dismissed = [dismissed copy];
|
|
50
60
|
previewInteraction->_interaction =
|
|
@@ -91,7 +101,9 @@ static NSMutableSet *ANNPreviews(void) {
|
|
|
91
101
|
[view removeInteraction:_interaction];
|
|
92
102
|
// A tap's commit animation may still run: a preview that went, its screen destroyed and its push failed, is not
|
|
93
103
|
// pushed when the animation ends.
|
|
104
|
+
_prepare = nil;
|
|
94
105
|
_commit = nil;
|
|
106
|
+
[ANNPreviewInteraction removeSnapshots:_snapshots];
|
|
95
107
|
void (^dismissed)(void) = _dismissed;
|
|
96
108
|
_dismissed = nil;
|
|
97
109
|
if (dismissed) {
|
|
@@ -116,6 +128,18 @@ static NSMutableSet *ANNPreviews(void) {
|
|
|
116
128
|
[self detach];
|
|
117
129
|
return nil;
|
|
118
130
|
}
|
|
131
|
+
// UIKit ends a lift that never became a menu too (the touch ended before the long press); should
|
|
132
|
+
// it not, the snapshot of that press goes now.
|
|
133
|
+
if (_snapshots != _endingSnapshots) {
|
|
134
|
+
[ANNPreviewInteraction removeSnapshots:_snapshots];
|
|
135
|
+
}
|
|
136
|
+
_snapshots = [NSMutableArray new];
|
|
137
|
+
// UIKit asks for the configuration as the press starts, about half a second before it shows the preview: the
|
|
138
|
+
// screen renders now, so that its content is there by then.
|
|
139
|
+
if (_prepare && !_prepare()) {
|
|
140
|
+
[self detach];
|
|
141
|
+
return nil;
|
|
142
|
+
}
|
|
119
143
|
UIViewController *preview = _preview;
|
|
120
144
|
NSArray *actions = preview.resolveOptions.preview.actions;
|
|
121
145
|
return [UIContextMenuConfiguration
|
|
@@ -129,6 +153,23 @@ static NSMutableSet *ANNPreviews(void) {
|
|
|
129
153
|
}];
|
|
130
154
|
}
|
|
131
155
|
|
|
156
|
+
// The menu goes: the snapshots of its press go once it is gone. UIKit asks for the one
|
|
157
|
+
// it goes back to after this call, so the list is read at the end, not now.
|
|
158
|
+
- (void)contextMenuInteraction:(UIContextMenuInteraction *)interaction
|
|
159
|
+
willEndForConfiguration:(UIContextMenuConfiguration *)configuration
|
|
160
|
+
animator:(id<UIContextMenuInteractionAnimating>)animator {
|
|
161
|
+
NSMutableArray<UIView *> *snapshots = _snapshots;
|
|
162
|
+
_endingSnapshots = snapshots;
|
|
163
|
+
void (^remove)(void) = ^{
|
|
164
|
+
[ANNPreviewInteraction removeSnapshots:snapshots];
|
|
165
|
+
};
|
|
166
|
+
if (animator) {
|
|
167
|
+
[animator addCompletion:remove];
|
|
168
|
+
} else {
|
|
169
|
+
dispatch_async(dispatch_get_main_queue(), remove);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
132
173
|
- (void)contextMenuInteraction:(UIContextMenuInteraction *)interaction
|
|
133
174
|
willPerformPreviewActionForMenuWithConfiguration:(UIContextMenuConfiguration *)configuration
|
|
134
175
|
animator:(id<UIContextMenuInteractionCommitAnimating>)
|
|
@@ -148,24 +189,45 @@ static NSMutableSet *ANNPreviews(void) {
|
|
|
148
189
|
|
|
149
190
|
#pragma mark - Targeted previews
|
|
150
191
|
|
|
151
|
-
// The view the menu lifts from and goes back to: a snapshot of the source view
|
|
152
|
-
//
|
|
153
|
-
//
|
|
192
|
+
// The view the menu lifts from and goes back to: a snapshot of the source view, laid over it in the
|
|
193
|
+
// view of its screen, the core's own (in its window when the view is elsewhere), so that it goes with
|
|
194
|
+
// the screen (a pop right after the menu). With the source view itself UIKit adds its views next to
|
|
195
|
+
// it, among the React views of its parent, and Fabric then mounts and unmounts at the wrong places.
|
|
196
|
+
// A snapshot out of the window, with only a target there, iOS 27 doesn't lift: it dims it in place.
|
|
154
197
|
- (UITargetedPreview *)snapshotPreviewOf:(UIView *)view {
|
|
155
|
-
|
|
198
|
+
UIView *container = _source.viewIfLoaded;
|
|
199
|
+
if (!container || ![view isDescendantOfView:container]) {
|
|
200
|
+
container = view.window;
|
|
201
|
+
}
|
|
202
|
+
// A screen out of its window (a command took it away while the menu showed) has no place for it:
|
|
203
|
+
// UIKit needs the view in a window.
|
|
204
|
+
if (!container.window) {
|
|
205
|
+
return nil;
|
|
206
|
+
}
|
|
156
207
|
UIView *snapshot = [view snapshotViewAfterScreenUpdates:NO];
|
|
157
|
-
if (!
|
|
208
|
+
if (!snapshot) {
|
|
158
209
|
return nil;
|
|
159
210
|
}
|
|
211
|
+
snapshot.frame = [view convertRect:view.bounds toView:container];
|
|
212
|
+
snapshot.userInteractionEnabled = NO;
|
|
213
|
+
snapshot.accessibilityElementsHidden = YES;
|
|
214
|
+
[container addSubview:snapshot];
|
|
215
|
+
if (!_snapshots) {
|
|
216
|
+
_snapshots = [NSMutableArray new];
|
|
217
|
+
}
|
|
218
|
+
[_snapshots addObject:snapshot];
|
|
160
219
|
UIPreviewParameters *parameters = [UIPreviewParameters new];
|
|
161
220
|
parameters.backgroundColor = UIColor.clearColor;
|
|
162
221
|
parameters.visiblePath = [UIBezierPath bezierPathWithRoundedRect:view.bounds
|
|
163
222
|
cornerRadius:view.layer.cornerRadius];
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
223
|
+
return [[UITargetedPreview alloc] initWithView:snapshot parameters:parameters];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
+ (void)removeSnapshots:(NSMutableArray<UIView *> *)snapshots {
|
|
227
|
+
for (UIView *snapshot in snapshots) {
|
|
228
|
+
[snapshot removeFromSuperview];
|
|
229
|
+
}
|
|
230
|
+
[snapshots removeAllObjects];
|
|
169
231
|
}
|
|
170
232
|
|
|
171
233
|
- (UITargetedPreview *)contextMenuInteraction:(UIContextMenuInteraction *)interaction
|