@apexnative/navigation 1.0.0-alpha.4 → 1.0.0-alpha.5
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 +13 -0
- package/android/CLAUDE.md +9 -2
- package/android/CORE.md +25 -15
- package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +25 -9
- package/android/src/main/java/dev/apexnative/navigation/options/AnimationsOptions.kt +2 -2
- 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/ArrangementStates.kt +6 -6
- 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/ReactView.kt +39 -20
- package/android/src/main/java/dev/apexnative/navigation/react/ReloadCoordinator.kt +7 -4
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +6 -6
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +10 -5
- 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 +6 -4
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +12 -12
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/ArrangementController.kt +3 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +9 -11
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentViewController.kt +6 -2
- 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/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 +21 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +3 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +9 -5
- 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/views/bottomtabs/CustomBottomTabItemView.kt +0 -5
- package/cli/doctor/checks/splash.js +4 -22
- package/cli/splash/index.js +3 -1
- package/docs-md/containers/modals.md +2 -2
- package/docs-md/guides/cli.md +1 -2
- package/docs-md/help/troubleshooting.md +2 -2
- package/docs-md/screen-ui/back-and-gestures.md +22 -0
- package/docs-md/start/installation.md +1 -1
- package/ios/CORE.md +6 -6
- package/ios/Contract/ANNCommandAnswer.h +9 -2
- package/ios/Contract/ANNCommandAnswer.mm +25 -12
- package/ios/Core/ANNAppearanceDispatcher.h +5 -4
- package/ios/Core/ANNAppearanceDispatcher.mm +12 -1
- package/ios/Core/ANNNavigationState.h +4 -6
- package/ios/Core/ANNNavigationState.mm +0 -4
- package/ios/Layouts/Root/ANNSplashViewController.h +5 -3
- package/ios/Layouts/Root/ANNSplashViewController.mm +147 -0
- package/ios/Layouts/Stack/ANNPreviewPush.h +5 -5
- package/ios/Layouts/Stack/ANNPreviewPush.mm +85 -22
- package/lib/module/api/commands/commands.js +54 -6
- package/lib/module/api/commands/commands.js.map +1 -1
- package/lib/module/engine/places.js +8 -0
- package/lib/module/engine/places.js.map +1 -1
- 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/engine/places.d.ts +2 -0
- package/lib/typescript/engine/places.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 +18 -2
- package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/stack.d.ts +9 -0
- package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/api/commands/commands.ts +48 -7
- package/src/api/commands/presentation.ts +4 -3
- package/src/engine/places.ts +9 -0
- package/src/interfaces/Options.ts +1 -0
- package/src/interfaces/options/animations.ts +19 -2
- package/src/interfaces/options/stack.ts +9 -0
- 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/stack/StackController.kt
CHANGED
|
@@ -270,8 +270,8 @@ class StackController(
|
|
|
270
270
|
|
|
271
271
|
/**
|
|
272
272
|
* The default pop of the top is what back does now and it can be previewed: two entries or more, nothing running
|
|
273
|
-
* or waiting, no open search, the top doesn't hold back
|
|
274
|
-
* has its view, and the stack shows.
|
|
273
|
+
* or waiting, no open search, the top doesn't hold back, pops with the default transition and doesn't turn the
|
|
274
|
+
* preview off (`animations.pop.preview: false`), the screen under it has its view, and the stack shows.
|
|
275
275
|
*/
|
|
276
276
|
override fun stackPoppedByBack(): StackController? {
|
|
277
277
|
if (topBar.isSearchOpen() || !queue.isAtRest) return null
|
|
@@ -282,9 +282,8 @@ class StackController(
|
|
|
282
282
|
if (!queue.isRendered(target) || backGesture != null) return null
|
|
283
283
|
val options = resolvedOf(top)
|
|
284
284
|
val pop = options.animations.pop
|
|
285
|
-
if (options.hardwareBackButton.popStackOnPress.isFalse || pop.enabled.isFalse
|
|
286
|
-
|
|
287
|
-
}
|
|
285
|
+
if (options.hardwareBackButton.popStackOnPress.isFalse || pop.enabled.isFalse) return null
|
|
286
|
+
if (pop.preview.isFalse || AnimationRules.stackIsOwn(pop)) return null
|
|
288
287
|
val display = queue.display
|
|
289
288
|
return this.takeIf { display.isOnScreen && display.canAnimate }
|
|
290
289
|
}
|
|
@@ -694,15 +693,20 @@ class StackController(
|
|
|
694
693
|
private fun optionsScreen(node: ViewController<*>): ViewController<*> =
|
|
695
694
|
(node as? OptionsScreen)?.optionsScreen ?: node
|
|
696
695
|
|
|
696
|
+
// What back does is computed again on each change of the queue (BackCallbackState): its entries change as an
|
|
697
|
+
// operation is accepted, the screen whose options count as its current child changes (a push that waited for its
|
|
698
|
+
// screen begins after its command), and an operation ends or rejects.
|
|
697
699
|
private inner class QueueHost : StackQueue.Host {
|
|
698
700
|
override fun operationAccepted() {
|
|
699
701
|
backGesture?.abort()
|
|
702
|
+
BackCallbackState.update()
|
|
700
703
|
}
|
|
701
704
|
|
|
702
705
|
// The bar takes the options of the new top at once, also without appearance events.
|
|
703
706
|
override fun currentChildChanged() {
|
|
704
707
|
updateBar()
|
|
705
708
|
navigator?.shownScreensChanged(this@StackController)
|
|
709
|
+
BackCallbackState.update()
|
|
706
710
|
}
|
|
707
711
|
|
|
708
712
|
// Before the bar takes the new top's options: the row's picture or the bar's travel, and how it shows or hides.
|
package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/ScreenSearch.kt
CHANGED
|
@@ -36,6 +36,8 @@ import dev.apexnative.navigation.views.stack.topbar.search.SuggestionList
|
|
|
36
36
|
class ScreenSearch(
|
|
37
37
|
private val host: Host,
|
|
38
38
|
private val screenId: String,
|
|
39
|
+
/** The sender of the JavaScript the screen was made for: silent once it went. */
|
|
40
|
+
private val emitter: EventEmitter,
|
|
39
41
|
private val context: Context,
|
|
40
42
|
private val loadIcon: (String) -> Drawable?,
|
|
41
43
|
/** A scope the user picks goes into the screen's options, so it stays when they apply again. */
|
|
@@ -45,7 +47,6 @@ class ScreenSearch(
|
|
|
45
47
|
/** The search changed what the bar shows (open or closed, its text): shown again if it is the shown one. */
|
|
46
48
|
fun refresh(search: ScreenSearch)
|
|
47
49
|
fun isShown(search: ScreenSearch): Boolean
|
|
48
|
-
fun emitter(): EventEmitter?
|
|
49
50
|
fun keyboard(view: View, show: Boolean)
|
|
50
51
|
/** The stack's view: a search view goes over it. */
|
|
51
52
|
fun stackView(): ViewGroup?
|
|
@@ -402,7 +403,7 @@ class ScreenSearch(
|
|
|
402
403
|
|
|
403
404
|
// A pick sends searchSuggestionPicked with the suggestion's id; the text stays.
|
|
404
405
|
private fun pickSuggestion(suggestionId: String) {
|
|
405
|
-
|
|
406
|
+
emitter.emitSearchSuggestionPicked(screenId, suggestionId)
|
|
406
407
|
setSuggestions(emptyList())
|
|
407
408
|
}
|
|
408
409
|
|
|
@@ -426,18 +427,15 @@ class ScreenSearch(
|
|
|
426
427
|
private fun sendUpdate(
|
|
427
428
|
focused: Boolean = isActive && (panel?.searchView?.editText ?: searchField.editText).hasFocus(),
|
|
428
429
|
) {
|
|
429
|
-
val emitter = host.emitter() ?: return
|
|
430
430
|
eventTargets().forEach { emitter.emitSearchBarUpdated(it, text, focused, scope()) }
|
|
431
431
|
}
|
|
432
432
|
|
|
433
433
|
private fun sendCancel() {
|
|
434
|
-
val emitter = host.emitter() ?: return
|
|
435
434
|
eventTargets().forEach { emitter.emitSearchBarCancelPressed(it) }
|
|
436
435
|
sendUpdate()
|
|
437
436
|
}
|
|
438
437
|
|
|
439
438
|
private fun sendSubmit() {
|
|
440
|
-
val emitter = host.emitter() ?: return
|
|
441
439
|
eventTargets().forEach { emitter.emitSearchBarSubmitted(it, text, scope()) }
|
|
442
440
|
}
|
|
443
441
|
|
|
@@ -12,7 +12,6 @@ import androidx.appcompat.content.res.AppCompatResources
|
|
|
12
12
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
13
13
|
import androidx.core.view.WindowCompat
|
|
14
14
|
import androidx.core.view.WindowInsetsCompat
|
|
15
|
-
import com.facebook.react.ReactApplication
|
|
16
15
|
import dev.apexnative.navigation.options.SearchBarOptions
|
|
17
16
|
import dev.apexnative.navigation.options.params.ThemeColour
|
|
18
17
|
import dev.apexnative.navigation.react.events.EventEmitter
|
|
@@ -137,9 +136,6 @@ class StackSearchPresenter(
|
|
|
137
136
|
refresh()
|
|
138
137
|
}
|
|
139
138
|
|
|
140
|
-
override fun emitter(): EventEmitter? =
|
|
141
|
-
(activity.application as? ReactApplication)?.reactHost?.currentReactContext?.let { EventEmitter(it) }
|
|
142
|
-
|
|
143
139
|
override fun keyboard(view: View, show: Boolean) {
|
|
144
140
|
val controller = WindowCompat.getInsetsController(activity.window, view)
|
|
145
141
|
if (show) controller.show(WindowInsetsCompat.Type.ime()) else controller.hide(WindowInsetsCompat.Type.ime())
|
|
@@ -150,7 +146,8 @@ class StackSearchPresenter(
|
|
|
150
146
|
// The search of a pair of screens is its primary's: its events name that screen, which keeps the scope in
|
|
151
147
|
// its user layer, where a merge that sends the same options again doesn't take it away.
|
|
152
148
|
val owner = (screen as? OptionsScreen)?.optionsScreen ?: screen
|
|
153
|
-
|
|
149
|
+
// Its events go to the JavaScript the screen was made for, and to no other.
|
|
150
|
+
ScreenSearch(this, owner.id, EventEmitter.of(owner.jsGeneration), styleContext(), loadIcon) { scope ->
|
|
154
151
|
owner.changeChoices { it.pickScope(scope) }
|
|
155
152
|
}
|
|
156
153
|
}
|
|
@@ -5,7 +5,6 @@ import dev.apexnative.navigation.options.Options
|
|
|
5
5
|
import dev.apexnative.navigation.options.TitleComponentOptions
|
|
6
6
|
import dev.apexnative.navigation.options.TopBarBackgroundComponentOptions
|
|
7
7
|
import dev.apexnative.navigation.options.params.Text
|
|
8
|
-
import dev.apexnative.navigation.viewcontrollers.component.ScreenReadiness
|
|
9
8
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.IReactView
|
|
10
9
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
|
|
11
10
|
|
|
@@ -15,7 +14,8 @@ import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
|
|
|
15
14
|
* same root, never made again; another replaces it, and the old one stops at once. A background with the same `name`
|
|
16
15
|
* and `componentId` on several screens is one root, shared; it stops when no screen of the stack names it any more. The
|
|
17
16
|
* roots go with their screen ([screenDestroyed]) or the stack ([destroyAll]). Each root renders at once, without
|
|
18
|
-
* waiting for its layout: [makeRoot]
|
|
17
|
+
* waiting for its layout: [makeRoot] makes it so (`ReactView`'s `rendersAtOnce`), and each answers by its own render
|
|
18
|
+
* when several live roots show one id (`ScreenReadiness`). Main thread only.
|
|
19
19
|
*/
|
|
20
20
|
internal class BarComponents(private val makeRoot: (componentId: String, name: String) -> IReactView) {
|
|
21
21
|
/** A root of the bar: what it renders, and its content. */
|
|
@@ -151,18 +151,10 @@ internal class BarComponents(private val makeRoot: (componentId: String, name: S
|
|
|
151
151
|
backgrounds.remove(key)?.let(::stop)
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
|
|
155
|
-
private fun make(key: Key): Root {
|
|
156
|
-
val root = Root(key.name, key.componentId, makeRoot(key.componentId, key.name))
|
|
157
|
-
LiveIds.add(key.componentId)
|
|
158
|
-
return root
|
|
159
|
-
}
|
|
154
|
+
private fun make(key: Key): Root = Root(key.name, key.componentId, makeRoot(key.componentId, key.name))
|
|
160
155
|
|
|
161
|
-
// Stops a root once;
|
|
162
|
-
private fun stop(root: Root)
|
|
163
|
-
root.view.destroy()
|
|
164
|
-
if (LiveIds.remove(root.componentId)) ScreenReadiness.remove(root.componentId)
|
|
165
|
-
}
|
|
156
|
+
// Stops a root once; its content takes its mark of rendering at once with it.
|
|
157
|
+
private fun stop(root: Root) = root.view.destroy()
|
|
166
158
|
|
|
167
159
|
private fun keyOf(name: Text, componentId: Text, id: Text): Key? {
|
|
168
160
|
val componentName = name.get(null)?.takeIf { it.isNotEmpty() } ?: return null
|
|
@@ -170,42 +162,4 @@ internal class BarComponents(private val makeRoot: (componentId: String, name: S
|
|
|
170
162
|
?: return null
|
|
171
163
|
return Key(componentName, rootId)
|
|
172
164
|
}
|
|
173
|
-
|
|
174
|
-
companion object {
|
|
175
|
-
/** Whether a bar root with [id] is alive now, in any stack. */
|
|
176
|
-
fun isLive(id: String): Boolean = LiveIds.has(id)
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Another root that renders at once lives with [id] (a cell of the tabs' custom row): it counts as the bar's
|
|
180
|
-
* roots do, so a root that stops clears the id's readiness only when it was the last one with it.
|
|
181
|
-
*/
|
|
182
|
-
internal fun rootMade(id: String) = LiveIds.add(id)
|
|
183
|
-
|
|
184
|
-
/** A root of [rootMade] stopped; true when no live root has [id] any more. */
|
|
185
|
-
internal fun rootStopped(id: String): Boolean = LiveIds.remove(id)
|
|
186
|
-
|
|
187
|
-
/** The JavaScript went (a reload): no id counts as held by a live root any more, as `ScreenReadiness.reset`. */
|
|
188
|
-
fun reset() = LiveIds.clear()
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// The ids of the bar roots alive now, in every stack: a root that stops clears its id's readiness only when it was
|
|
192
|
-
// the last one with it.
|
|
193
|
-
private object LiveIds {
|
|
194
|
-
private val counts = HashMap<String, Int>()
|
|
195
|
-
|
|
196
|
-
fun has(id: String): Boolean = (counts[id] ?: 0) > 0
|
|
197
|
-
|
|
198
|
-
fun clear() = counts.clear()
|
|
199
|
-
|
|
200
|
-
fun add(id: String) {
|
|
201
|
-
counts[id] = (counts[id] ?: 0) + 1
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// True when no root with the id is left.
|
|
205
|
-
fun remove(id: String): Boolean {
|
|
206
|
-
val count = (counts[id] ?: return true) - 1
|
|
207
|
-
if (count <= 0) counts.remove(id) else counts[id] = count
|
|
208
|
-
return count <= 0
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
165
|
}
|
|
@@ -41,7 +41,6 @@ import dev.apexnative.navigation.utils.ImageLoader
|
|
|
41
41
|
import dev.apexnative.navigation.utils.ImageLoadingListenerAdapter
|
|
42
42
|
import dev.apexnative.navigation.utils.TestIds
|
|
43
43
|
import dev.apexnative.navigation.utils.UiUtils
|
|
44
|
-
import dev.apexnative.navigation.viewcontrollers.component.ScreenReadiness
|
|
45
44
|
import dev.apexnative.navigation.viewcontrollers.navigator.BackCallbackState
|
|
46
45
|
import dev.apexnative.navigation.viewcontrollers.stack.StackDisplay
|
|
47
46
|
import dev.apexnative.navigation.viewcontrollers.stack.StackDisplay.BarMotion
|
|
@@ -936,14 +935,10 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
|
|
|
936
935
|
return resolved.component
|
|
937
936
|
}
|
|
938
937
|
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
// this root's, which answers by its own render.
|
|
944
|
-
if (BarComponents.isLive(componentId)) ScreenReadiness.forgetRender(componentId)
|
|
945
|
-
return ReactView(activity, componentId, name)
|
|
946
|
-
}
|
|
938
|
+
// A component of the bar renders at once, without waiting for its layout. Another live root may show the same id
|
|
939
|
+
// (a component of the stack's options on two screens): each answers by the render of its own surface.
|
|
940
|
+
private fun makeRoot(componentId: String, name: String): IReactView =
|
|
941
|
+
ReactView(activity, componentId, name, rendersAtOnce = true)
|
|
947
942
|
|
|
948
943
|
private fun loadIcon(uri: String): Drawable? {
|
|
949
944
|
var loaded: Drawable? = null
|
package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomBottomTabItemView.kt
CHANGED
|
@@ -22,9 +22,6 @@ open class CustomBottomTabItemView(context: Context) : FrameLayout(context) {
|
|
|
22
22
|
private var state: TabCellState? = null
|
|
23
23
|
private var destroyed = false
|
|
24
24
|
|
|
25
|
-
/** Runs once as the cell stops: what was kept for its content's id goes. */
|
|
26
|
-
internal var onDestroyed: (() -> Unit)? = null
|
|
27
|
-
|
|
28
25
|
/** The content the cell shows, filling it, made with [state] as its first props. */
|
|
29
26
|
internal fun show(view: IReactView, state: TabCellState) {
|
|
30
27
|
content = view
|
|
@@ -68,8 +65,6 @@ open class CustomBottomTabItemView(context: Context) : FrameLayout(context) {
|
|
|
68
65
|
didDisappear()
|
|
69
66
|
destroyed = true
|
|
70
67
|
content?.destroy()
|
|
71
|
-
onDestroyed?.invoke()
|
|
72
|
-
onDestroyed = null
|
|
73
68
|
}
|
|
74
69
|
|
|
75
70
|
/** The props of [state] as React Native takes them. */
|
|
@@ -17,34 +17,16 @@ const CIRCLE = 192;
|
|
|
17
17
|
const LOGO_HEIGHT = /<dimen name="apex_navigation_splash_logo_height">([\d.]+)dp<\/dimen>/;
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* The native splash: made from its settings in package.json on both platforms, its logo whole on Android 12
|
|
21
|
-
* settings
|
|
20
|
+
* The native splash: made from its settings in package.json on both platforms, its logo whole on Android 12+. Without
|
|
21
|
+
* settings there is nothing to check: the navigation keeps a copy of a storyboard and of `UILaunchScreen` alike.
|
|
22
22
|
* @param {string} root
|
|
23
23
|
* @param {Project} project
|
|
24
24
|
* @returns {Check[]}
|
|
25
25
|
*/
|
|
26
26
|
function splashChecks(root, project) {
|
|
27
|
-
const plist = project.infoPlist && fs.readFileSync(project.infoPlist, 'utf8');
|
|
28
27
|
const saved = readConfig(root).splash;
|
|
29
|
-
if (!saved)
|
|
30
|
-
|
|
31
|
-
if (
|
|
32
|
-
plist?.includes('<key>UILaunchScreen</key>') &&
|
|
33
|
-
!plist.includes('<key>UILaunchStoryboardName</key>')
|
|
34
|
-
) {
|
|
35
|
-
return [
|
|
36
|
-
{
|
|
37
|
-
group: 'app',
|
|
38
|
-
title: 'Launch screen',
|
|
39
|
-
status: 'warn',
|
|
40
|
-
detail:
|
|
41
|
-
'UILaunchScreen in Info.plist: an empty window shows between it and the first screen',
|
|
42
|
-
fix: SPLASH,
|
|
43
|
-
},
|
|
44
|
-
];
|
|
45
|
-
}
|
|
46
|
-
return [];
|
|
47
|
-
}
|
|
28
|
+
if (!saved) return [];
|
|
29
|
+
const plist = project.infoPlist && fs.readFileSync(project.infoPlist, 'utf8');
|
|
48
30
|
const resolved = resolveSettings(root, {}, saved);
|
|
49
31
|
if ('errors' in resolved) {
|
|
50
32
|
return [
|
package/cli/splash/index.js
CHANGED
|
@@ -88,7 +88,9 @@ async function splash({
|
|
|
88
88
|
}
|
|
89
89
|
const plist = project.infoPlist && fs.readFileSync(project.infoPlist, 'utf8');
|
|
90
90
|
if (plist?.includes('<key>UILaunchScreen</key>')) {
|
|
91
|
-
command.warn(
|
|
91
|
+
command.warn(
|
|
92
|
+
'Info.plist has UILaunchScreen too: iOS shows it instead of the storyboard; remove it, so that iOS shows the storyboard.'
|
|
93
|
+
);
|
|
92
94
|
}
|
|
93
95
|
return command.apply(
|
|
94
96
|
[
|
|
@@ -52,8 +52,8 @@ Compose shows with a close button at the leading edge of its top bar. The settin
|
|
|
52
52
|
|
|
53
53
|
| Setting | What it does |
|
|
54
54
|
| --- | --- |
|
|
55
|
-
| `closeButton` | A close button on the first screen: the system one on iOS, an icon on Android. Needs the stack. |
|
|
56
|
-
| `onDismiss` | Called once the modal is gone, whatever closed it: a swipe, a command, a new root. |
|
|
55
|
+
| `closeButton` | A close button on the first screen: the system one on iOS, an icon on Android. Needs the stack. A new first screen from `setStack` or `replace` in the modal's stack gets it too. |
|
|
56
|
+
| `onDismiss` | Called once the modal is gone, whatever closed it: a swipe, a command, a new root. `setStack` and `replace` in the modal's stack replace its first screen and call it too. |
|
|
57
57
|
| `stack` | `false`: the screen alone, without a stack and its top bar. |
|
|
58
58
|
| `style`, `sheet`, `popover`, `contentSize` | How it looks: see [How a modal looks](#styles). |
|
|
59
59
|
| `transition`, `options` | The animation, and the screen's options for this show. |
|
package/docs-md/guides/cli.md
CHANGED
|
@@ -311,7 +311,7 @@ Each file and why: [What the command writes](./splash-screen#files).
|
|
|
311
311
|
- **The settings** go into `package.json`, under `apexNavigation.splash`. An option given replaces its saved value; to drop the dark logo or color, remove `darkLogo` or `darkBackground` there and run the command again.
|
|
312
312
|
- **A logo whose corners leave the circle** of Android 12's splash icon, 192 dp, gets a warning: Android cuts them. Make `--logo-width` smaller, or give the logo room around it.
|
|
313
313
|
- **A file it can't change** is listed as `manual`, with the change to make: an app without `Images.xcassets` next to `Info.plist`, a manifest whose `MainActivity` and `<application>` have no theme to go over.
|
|
314
|
-
- **`Info.plist` with `UILaunchScreen`** as well gets a warning:
|
|
314
|
+
- **`Info.plist` with `UILaunchScreen`** as well gets a warning: iOS shows that dictionary instead of the storyboard, so remove it.
|
|
315
315
|
- **Its errors** in the JSON: `splash-settings` for a setting it can't take, `splash-image` for a logo it can't read or that doesn't fit (too tall at its width, a dark logo of other proportions), and `sharp-missing`, as for `icons`.
|
|
316
316
|
|
|
317
317
|
`doctor` checks the splash in the App group:
|
|
@@ -320,7 +320,6 @@ Each file and why: [What the command writes](./splash-screen#files).
|
|
|
320
320
|
| --- | --- |
|
|
321
321
|
| Splash | The files of iOS or Android aren't made from the settings in `package.json`: changed by hand, made on one platform only, or by an older version of the command. Or the settings themselves are wrong, such as a logo that isn't there. |
|
|
322
322
|
| Splash logo | The logo's corners leave Android 12's circle. |
|
|
323
|
-
| Launch screen | Without settings: `Info.plist` has `UILaunchScreen` and no storyboard. iOS shows it at launch, but the navigation can only keep a storyboard on screen, so an empty window shows between it and the first screen. |
|
|
324
323
|
|
|
325
324
|
Each warning names its fix, such as `npx apex-navigation splash`; `--fix` leaves these to you. What the user sees, and a splash of your own after the native one: [Splash screen](./splash-screen).
|
|
326
325
|
|
|
@@ -232,9 +232,9 @@ A signed-in user sees Login for a moment, then the app.
|
|
|
232
232
|
|
|
233
233
|
(iOS) Between the launch screen and the first screen, the window is empty for a moment.
|
|
234
234
|
|
|
235
|
-
**Why:** the
|
|
235
|
+
**Why:** the app has no launch screen the navigation can copy: `Info.plist` names no storyboard and has no `UILaunchScreen` dictionary (or `UILaunchScreens` default), and no legacy launch images.
|
|
236
236
|
|
|
237
|
-
**Fix:** run `npx apex-navigation splash`, or name a storyboard in `UILaunchStoryboardName
|
|
237
|
+
**Fix:** run `npx apex-navigation splash`, or name a storyboard in `UILaunchStoryboardName`. More: [Make the native splash](../guides/splash-screen#command).
|
|
238
238
|
|
|
239
239
|
### Android brings the app back without its pushed screens
|
|
240
240
|
|
|
@@ -106,6 +106,17 @@ export const MapScreen = defineScreen({
|
|
|
106
106
|
|
|
107
107
|
The preview shows when back pops the screen with the default transition. It doesn't when back does something else first, or when the app takes it: a menu or a search that closes, a screen that holds back (`hardwareBackButton.popStackOnPress: false`, a [`BackHandler` listener](#back-handler), `usePreventLeave`), or a pop with an animation of its own (`animations.pop`). On Android 13 and older the screen pops when the swipe ends.
|
|
108
108
|
|
|
109
|
+
`animations.pop.preview: false` turns the preview off for a screen: the gesture shows nothing, and the screen pops when the user lets go, as on Android 13. It is read from the screen that goes, so the app's `options` turn it off everywhere:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
defineScreen({
|
|
113
|
+
component: Checkout,
|
|
114
|
+
options: { animations: { pop: { preview: false } } },
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
iOS ignores it: the swipe back there is UIKit's.
|
|
119
|
+
|
|
109
120
|
### Back to the home screen
|
|
110
121
|
|
|
111
122
|
(Android) On the first screen of the app, with nothing left to go back to, the navigation leaves the back to the system. The system then plays its predictive back animation: as the user swipes, the app shrinks and the home screen shows behind it, and the user can still change their mind halfway. The system decides this as the swipe starts, so the navigation takes the back only while it has something to do with it: a screen to pop, a modal, a menu or a search to close, a tab to go to (`backBehavior`), or a [`BackHandler` listener](#back-handler) of the app.
|
|
@@ -122,6 +133,17 @@ Without it, back on the first screen still leaves the app, at once. See [What se
|
|
|
122
133
|
|
|
123
134
|
With the animation on, the system leaves the app itself: an `invokeDefaultOnBackPressed` of the app's `MainActivity` (such as `moveTaskToBack(true)`) isn't called on the first screen. Without it, and before Android 13, the back goes through the activity as before.
|
|
124
135
|
|
|
136
|
+
`hardwareBackButton.systemAnimation: false` keeps the back in the app on the first screen too, so the system plays no animation there: the gesture shows nothing, and on release the press goes to the [`BackHandler` listeners](#back-handler), then to the activity's own back, `invokeDefaultOnBackPressed` included. It is read from the screen back acts on; for the whole app, set it in the app's `options`:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
createNavigation({
|
|
140
|
+
// …
|
|
141
|
+
options: { hardwareBackButton: { systemAnimation: false } },
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
iOS ignores it.
|
|
146
|
+
|
|
125
147
|
### Handling back yourself: BackHandler
|
|
126
148
|
|
|
127
149
|
(Android) React Native's `BackHandler` works on every screen, as in any React Native app. A listener runs before the navigation's own back: returning `true` takes the press, `false` passes it on to the next listener, then to the navigation.
|
|
@@ -215,7 +215,7 @@ export {};
|
|
|
215
215
|
|
|
216
216
|
| Group | What it checks |
|
|
217
217
|
| --- | --- |
|
|
218
|
-
| App | React Native and the New Architecture, the package, the files `setup` sets up, the entry point, React Native loaded twice, the status bar of the template, the generated code of native screens. Typed commands: `navigation-env.d.ts` there, pointing to the navigation, with its `context` line, and read by `tsconfig.json`. The splash screen made from its settings, with its logo inside the circle of Android's splash icon
|
|
218
|
+
| App | React Native and the New Architecture, the package, the files `setup` sets up, the entry point, React Native loaded twice, the status bar of the template, the generated code of native screens. Typed commands: `navigation-env.d.ts` there, pointing to the navigation, with its `context` line, and read by `tsconfig.json`. The splash screen made from its settings, with its logo inside the circle of Android's splash icon. The app icon made from its settings, and an iOS icon set with no image, which the App Store refuses. |
|
|
219
219
|
| Navigation | From the app's code. Screens: no name given twice in `screens` and the groups, and no file of the screens' folder with a `defineScreen` or `nativeScreen` that no file imports, a screen made and never added. Links: each prefix of `linking` declared on both platforms, the declarations no prefix has, a main activity that isn't `singleTask`. Screen names, in a JavaScript app only: a name that `push`, `replace`, `showModal` or `showInColumn` gives and no screen has, with its file and line. |
|
|
220
220
|
| (Android) | JDK 17 in use, the Android SDK, `minSdk`, how edge-to-edge works in the app. |
|
|
221
221
|
| (iOS) | Xcode 26 or newer, CocoaPods, the pods of the navigation and whether they are out of date, the iOS version of the Podfile. |
|
package/ios/CORE.md
CHANGED
|
@@ -87,10 +87,10 @@ dispatcher.
|
|
|
87
87
|
`ANNStackAnimatingDelegate` give UIKit the `ANNTransitionAnimator`.
|
|
88
88
|
7. **The end.** `stackWhenTransitionEnds:` fires from the transition coordinator and calls `endPush:`, which calls
|
|
89
89
|
`+[ANNAppearanceDispatcher endTransitionHiding:showing:events:answer:value:]`. In this order: `ComponentDidDisappear`
|
|
90
|
-
of Home, `ComponentDidAppear` of Profile, `ANN.NavigationStateChanged` (
|
|
91
|
-
transition's `events`, then `-[ANNCommandAnswer resolve:]`: the
|
|
92
|
-
|
|
93
|
-
the next operation.
|
|
90
|
+
of Home, `ComponentDidAppear` of Profile, `ANN.NavigationStateChanged` (`-[ANNCommandAnswer reportStateNow]`, the
|
|
91
|
+
answer's one state step), the transition's `events`, then `-[ANNCommandAnswer resolve:]`: the promise with
|
|
92
|
+
Profile's id, then `ANN.CommandCompleted`. A command answered without a transition's end (`setRoot` with layout
|
|
93
|
+
modals) sends its state from `resolve:`. `finish:` frees the queue for the next operation.
|
|
94
94
|
|
|
95
95
|
Events go out through `ANNEventEmitter` on the main queue and reach JS through `ANNTurboEventEmitter`.
|
|
96
96
|
|
|
@@ -117,13 +117,13 @@ Each of these owns one rule. Call it; do not copy it.
|
|
|
117
117
|
|
|
118
118
|
- `ANNCommandRunner`: The one path of every async command: generation, check, main queue, exception guard. Called by: `ANNTurboModule`.
|
|
119
119
|
- `ANNLayoutReader`: The layout check, depth first, the same order and texts as Android. Module queue. Called by: The runner's check blocks.
|
|
120
|
-
- `ANNCommandAnswer`: The answer: state, then promise, then `ANN.CommandCompleted`; once; silent for a JavaScript that went. Called by: Every coordinator and queue.
|
|
120
|
+
- `ANNCommandAnswer`: The answer: state (one step per answer, `reportStateNow`, asked by the end of its transition or by `resolve:`), then promise, then `ANN.CommandCompleted`; once; silent for a JavaScript that went. Called by: Every coordinator and queue.
|
|
121
121
|
- `ANNNodeRegistry`: Every node by id, weakly. Filled by the factory, cleared at a reload. Called by: Commands, modules, `findViewController:`.
|
|
122
122
|
- `ANNLayoutFactory`: The core code that names module classes (`ANNSplitViewController`, `ANNSideMenuViewController`, `ANNArrangementViewController`); `ANNSideMenuPresenter` is the one other file that does. One maker per layout type; registers each node. Called by: Every command that shows a layout.
|
|
123
123
|
- `ANNOptionsCache`: The one invalidation point of resolved options. Called by: The setters of `ANNNodeHelper.options` and of its user layer (`setUserChoice:forKey:`), and `ANNDefaultOptions`, only.
|
|
124
124
|
- `ANNNodeHelper resolveOptionsWithDefault`: What a node shows with: defaults, parents' own options (each with its user layer), the node's `resolveOptions` (own, its user layer, then the current child down). Cached, read-only. Called by: Presenters, coordinators.
|
|
125
125
|
- `ANNAppearanceDispatcher`: The only sender of `ComponentWillAppear`, `ComponentDidAppear`, `ComponentDidDisappear`; the end-of-transition order; the events of a node that goes. Called by: Screens, queues, coordinators.
|
|
126
|
-
- `ANNNavigationState`: The visible screen and the nearest tabs it is in (the last tabs met walking down); coalesced updates, holds, the state
|
|
126
|
+
- `ANNNavigationState`: The visible screen and the nearest tabs it is in (the last tabs met walking down); coalesced updates, holds, the state now. Called by: The dispatcher, `ANNCommandAnswer`, `ANNRootCoordinator`, `ANNModalCoordinator`, `ANNBottomTabsController`.
|
|
127
127
|
- `ANNScreenReadiness`: The signals of a screen's safe area view (has one, laid out, content rendered), sticky, by screen id. Called by: `ANNSafeAreaViewComponentView`, `ANNReactView`.
|
|
128
128
|
- `ANNRenderWait`: The one wait for the render of what a command shows: content, bar components, limits, the unknown-content rule. Called by: Root, stack queue, modals, overlays, `showInColumn`.
|
|
129
129
|
- `ANNParking`: Where a view waits for its content unseen: the window, a stack's view, a preview's host view. Called by: Root, stack, modals, overlays, `ANNPreviewPush`.
|
|
@@ -27,8 +27,15 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
27
27
|
/// Whether the JavaScript the command came from still runs: its work goes on only then.
|
|
28
28
|
@property(nonatomic, readonly) BOOL isCurrent;
|
|
29
29
|
|
|
30
|
-
///
|
|
31
|
-
///
|
|
30
|
+
/// The navigation state now, sent when it changed, once per answer: the end of the command's transition asks for it
|
|
31
|
+
/// after the `DidAppear` it sends and before the transition's own events (`ANNAppearanceDispatcher`); otherwise
|
|
32
|
+
/// `resolve:` asks for it before the promise. A later call sends nothing, and nothing goes for a JavaScript that went.
|
|
33
|
+
/// A failure of the step is logged and costs only the state.
|
|
34
|
+
- (void)reportStateNow;
|
|
35
|
+
|
|
36
|
+
/// Resolves the promise: first the state, when the command changes what shows (`ANNCommandContract`) and its
|
|
37
|
+
/// transition didn't send it already (`reportStateNow`), then the promise, then `ANN.CommandCompleted` for a command
|
|
38
|
+
/// that shows or dismisses.
|
|
32
39
|
- (void)resolve:(nullable id)value;
|
|
33
40
|
|
|
34
41
|
/// Rejects the promise with the text alone: no code of the navigation.
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
@implementation ANNCommandAnswer {
|
|
11
11
|
RCTPromiseResolveBlock _resolve;
|
|
12
12
|
RCTPromiseRejectBlock _reject;
|
|
13
|
+
BOOL _stateReported;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
- (instancetype)initWithCommandName:(NSString *)commandName
|
|
@@ -51,6 +52,27 @@
|
|
|
51
52
|
return YES;
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
- (void)reportStateNow {
|
|
56
|
+
ANNAssertMainQueue();
|
|
57
|
+
if (_stateReported) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
_stateReported = YES;
|
|
61
|
+
// A step made for the JavaScript that went sends nothing, as its answer does.
|
|
62
|
+
if (!self.isCurrent) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
// The command did its work: a failure of the state step costs only the state, never the events and the answer.
|
|
66
|
+
@try {
|
|
67
|
+
[ANNNavigationState updateNowFor:_commandName];
|
|
68
|
+
} @catch (NSException *exception) {
|
|
69
|
+
NSString *reason = exception.reason.length > 0 ? exception.reason : exception.name;
|
|
70
|
+
[ANNErrorHandler logFailureOf:_commandName
|
|
71
|
+
text:[ANNErrorTexts couldNot:@"report the navigation state"
|
|
72
|
+
reason:reason]];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
54
76
|
- (void)resolve:(id)value {
|
|
55
77
|
if (![self takeAnswer]) {
|
|
56
78
|
return;
|
|
@@ -59,18 +81,9 @@
|
|
|
59
81
|
_resolve = nil;
|
|
60
82
|
_reject = nil;
|
|
61
83
|
// A command that may change what shows (`ANNCommandContract`): the state goes before the promise, so that
|
|
62
|
-
// `getState()` after `await` has the result.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// The command did its work: a failure of the state step costs only the state, never the answer.
|
|
66
|
-
@try {
|
|
67
|
-
[ANNNavigationState reportBeforeAnswerOf:_commandName];
|
|
68
|
-
} @catch (NSException *exception) {
|
|
69
|
-
NSString *reason = exception.reason.length > 0 ? exception.reason : exception.name;
|
|
70
|
-
[ANNErrorHandler logFailureOf:_commandName
|
|
71
|
-
text:[ANNErrorTexts couldNot:@"report the navigation state"
|
|
72
|
-
reason:reason]];
|
|
73
|
-
}
|
|
84
|
+
// `getState()` after `await` has the result. Sent at the end of its transition already, it goes no more.
|
|
85
|
+
if (ANNCommandReportsState(_commandName)) {
|
|
86
|
+
[self reportStateNow];
|
|
74
87
|
}
|
|
75
88
|
[ANNErrorHandler resolve:resolve of:_commandName value:value];
|
|
76
89
|
if (_commandId && ANNCommandSendsCompleted(_commandName)) {
|
|
@@ -58,11 +58,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
58
58
|
|
|
59
59
|
/// A transition ended, in this order: first a screen a transition began inside a node of `shown` that left the window
|
|
60
60
|
/// and isn't one of that node's shown screens gets its `DidDisappear`; then `DidDisappear` of every screen of `hidden`,
|
|
61
|
-
/// `DidAppear` of what `shown` shows, the navigation state now when it changed
|
|
61
|
+
/// `DidAppear` of what `shown` shows, the navigation state now when it changed (the answer's one state step,
|
|
62
|
+
/// `-[ANNCommandAnswer reportStateNow]`; nothing for a JavaScript that went), `events` (the transition's own events,
|
|
62
63
|
/// `ANN.ScreenPopped` and `ANN.ModalDismissed`, and what must follow them, like stopping the content that went), then
|
|
63
|
-
/// `answer` resolves with `value` (
|
|
64
|
-
///
|
|
65
|
-
///
|
|
64
|
+
/// `answer` resolves with `value` (the promise, then `ANN.CommandCompleted`; no second state). A user's action passes
|
|
65
|
+
/// no answer, and its state goes all the same. The callbacks of UIKit that come later find the phases set and send
|
|
66
|
+
/// nothing.
|
|
66
67
|
+ (void)endTransitionHiding:(NSArray<UIViewController *> *)hidden
|
|
67
68
|
showing:(NSArray<UIViewController *> *)shown
|
|
68
69
|
events:(nullable dispatch_block_t)events
|
|
@@ -305,7 +305,18 @@ static BOOL ANNIsInsideNode(UIViewController *screen, UIViewController *node) {
|
|
|
305
305
|
for (UIViewController *node in shown) {
|
|
306
306
|
[self nodeDidAppear:node];
|
|
307
307
|
}
|
|
308
|
-
|
|
308
|
+
// The state before the transition's events, once per answer: the answer's own step before its promise finds it
|
|
309
|
+
// sent. A user's transition has no answer; one whose answer went before its end (an interrupt) sends the state of
|
|
310
|
+
// its end itself, as Android does.
|
|
311
|
+
if (!answer) {
|
|
312
|
+
[ANNNavigationState updateNow];
|
|
313
|
+
} else if (answer.answered) {
|
|
314
|
+
if (answer.isCurrent) {
|
|
315
|
+
[ANNNavigationState updateNowFor:answer.commandName];
|
|
316
|
+
}
|
|
317
|
+
} else {
|
|
318
|
+
[answer reportStateNow];
|
|
319
|
+
}
|
|
309
320
|
if (events) {
|
|
310
321
|
events();
|
|
311
322
|
}
|
|
@@ -23,17 +23,15 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
23
23
|
+ (void)holdUpdates;
|
|
24
24
|
+ (void)releaseUpdates;
|
|
25
25
|
|
|
26
|
-
/// The state now, sent when it changed
|
|
27
|
-
///
|
|
28
|
-
///
|
|
26
|
+
/// The state now, sent when it changed. A command's answer asks for it once (`-[ANNCommandAnswer reportStateNow]`, at
|
|
27
|
+
/// the end of its transition or before its promise); the end of a user's transition and a tab chosen from code ask
|
|
28
|
+
/// `updateNow`. A walk that throws logs one line, `[ApexNavigation] <commandName>: could not report the navigation
|
|
29
|
+
/// state: <reason>` (as on Android; `transition` without a command), and sends nothing, so its caller always goes on.
|
|
29
30
|
+ (void)updateNowFor:(nullable NSString *)commandName;
|
|
30
31
|
|
|
31
32
|
/// `updateNowFor:` without a command.
|
|
32
33
|
+ (void)updateNow;
|
|
33
34
|
|
|
34
|
-
/// The command `commandName` is about to answer: when what shows changed, the new state goes now, before the promise.
|
|
35
|
-
+ (void)reportBeforeAnswerOf:(NSString *)commandName;
|
|
36
|
-
|
|
37
35
|
/// Forgets the last state sent: the next one goes even if it is the same (a new bundle, `AppLaunched`).
|
|
38
36
|
+ (void)reset;
|
|
39
37
|
|
|
@@ -94,10 +94,6 @@ static BOOL ANNSameValue(id a, id b) {
|
|
|
94
94
|
[ApexNavigation.eventEmitter sendNavigationStateChanged:screenId name:name tab:tab];
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
+ (void)reportBeforeAnswerOf:(NSString *)commandName {
|
|
98
|
-
[self updateNowFor:commandName];
|
|
99
|
-
}
|
|
100
|
-
|
|
101
97
|
+ (void)reset {
|
|
102
98
|
ANNAssertMainQueue();
|
|
103
99
|
ANNHasLastState = NO;
|
|
@@ -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`),
|