@craft-native/ios 0.0.201 → 0.0.202
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/package.json
CHANGED
package/templates/CraftApp.swift
CHANGED
|
@@ -792,10 +792,12 @@ final class CraftChrome: ObservableObject {
|
|
|
792
792
|
withAnimation(nil) { selected = id }
|
|
793
793
|
}
|
|
794
794
|
|
|
795
|
-
func cancelTabSwipe() {
|
|
795
|
+
func cancelTabSwipe(restoringSelection: Bool = true) {
|
|
796
796
|
guard let previous = tabSwipePrevious else { return }
|
|
797
797
|
tabSwipePrevious = nil
|
|
798
|
-
|
|
798
|
+
if restoringSelection {
|
|
799
|
+
withAnimation(nil) { selected = previous }
|
|
800
|
+
}
|
|
799
801
|
}
|
|
800
802
|
|
|
801
803
|
func commitTabSwipe(to id: String) {
|
|
@@ -1023,6 +1023,7 @@ final class CraftHybridController: UIViewController, UINavigationControllerDeleg
|
|
|
1023
1023
|
private var tabSwipeRecognizers: [UIGestureRecognizer] = []
|
|
1024
1024
|
private var activeTabSwipeTarget: String?
|
|
1025
1025
|
private var tabSwipeSettling = false
|
|
1026
|
+
private var tabSwipeGeneration: UInt = 0
|
|
1026
1027
|
/// The edge the incoming tab started from for the current gesture. Keep
|
|
1027
1028
|
/// this separate from the live frame: deriving it after the drag begins
|
|
1028
1029
|
/// makes the target jump when the translation crosses the midpoint.
|
|
@@ -1195,10 +1196,13 @@ final class CraftHybridController: UIViewController, UINavigationControllerDeleg
|
|
|
1195
1196
|
let end = completed ? 0 : direction * width
|
|
1196
1197
|
let settlingDuration = completed ? 0.22 : 0.18
|
|
1197
1198
|
let duration = CraftChrome.motion(.easeOut(duration: settlingDuration)) == nil ? 0 : settlingDuration
|
|
1199
|
+
let generation = tabSwipeGeneration
|
|
1198
1200
|
UIView.animate(withDuration: duration, delay: 0, options: [.curveEaseOut, .beginFromCurrentState], animations: {
|
|
1199
1201
|
target.view.frame.origin.x = end
|
|
1200
1202
|
current.view.frame.origin.x = completed ? -direction * width : 0
|
|
1201
1203
|
}, completion: { _ in
|
|
1204
|
+
guard self.tabSwipeGeneration == generation,
|
|
1205
|
+
self.activeTabSwipeTarget == targetId else { return }
|
|
1202
1206
|
if completed {
|
|
1203
1207
|
current.view.isHidden = true
|
|
1204
1208
|
current.view.frame = self.view.bounds
|
|
@@ -1216,6 +1220,29 @@ final class CraftHybridController: UIViewController, UINavigationControllerDeleg
|
|
|
1216
1220
|
})
|
|
1217
1221
|
}
|
|
1218
1222
|
|
|
1223
|
+
/// A newer navigation action wins immediately, including a tap back on
|
|
1224
|
+
/// the outgoing tab. Invalidate the old animation before removing it:
|
|
1225
|
+
/// UIKit may deliver its completion while the replacement is starting.
|
|
1226
|
+
private func interruptTabSwipe(restoringSelection: Bool) {
|
|
1227
|
+
guard let targetId = activeTabSwipeTarget else { return }
|
|
1228
|
+
tabSwipeGeneration &+= 1
|
|
1229
|
+
activeTabSwipeTarget = nil
|
|
1230
|
+
tabSwipeSettling = false
|
|
1231
|
+
for nav in [visibleStack, stacks[targetId]].compactMap({ $0 }) {
|
|
1232
|
+
nav.view.layer.removeAllAnimations()
|
|
1233
|
+
nav.view.frame = view.bounds
|
|
1234
|
+
nav.view.isHidden = nav !== visibleStack
|
|
1235
|
+
}
|
|
1236
|
+
// Cancel the current touch stream, so its later moved/ended events
|
|
1237
|
+
// cannot claim another tab after the explicit navigation.
|
|
1238
|
+
for recognizer in tabSwipeRecognizers {
|
|
1239
|
+
recognizer.isEnabled = false
|
|
1240
|
+
recognizer.isEnabled = true
|
|
1241
|
+
}
|
|
1242
|
+
CraftChrome.shared.cancelTabSwipe(restoringSelection: restoringSelection)
|
|
1243
|
+
if visibleStack?.topViewController is CraftNativeScreenController { parkWeb() }
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1219
1246
|
private func horizontalScrollAt(_ point: CGPoint, in root: UIView) -> Bool {
|
|
1220
1247
|
let local = root.convert(point, from: view)
|
|
1221
1248
|
guard root.bounds.contains(local), let hit = root.hitTest(local, with: nil) else { return false }
|
|
@@ -1365,6 +1392,7 @@ final class CraftHybridController: UIViewController, UINavigationControllerDeleg
|
|
|
1365
1392
|
}
|
|
1366
1393
|
|
|
1367
1394
|
private func show(tab id: String, rootPath: String? = nil) {
|
|
1395
|
+
interruptTabSwipe(restoringSelection: false)
|
|
1368
1396
|
cancelPendingTab()
|
|
1369
1397
|
let nav = stacks[id] ?? makeStack(tab: id, rootPath: rootPath ?? (id.isEmpty ? hybrid.startPath : id))
|
|
1370
1398
|
// A stack is hidden and shown rather than moved, so UIKit tells its
|
|
@@ -1448,6 +1476,7 @@ final class CraftHybridController: UIViewController, UINavigationControllerDeleg
|
|
|
1448
1476
|
/// it), a tab's root, or a new one pushed.
|
|
1449
1477
|
func showNative(path raw: String, replace: Bool, fromLink: Bool = false) {
|
|
1450
1478
|
guard let match = hybrid.routes.match(raw) else { return }
|
|
1479
|
+
interruptTabSwipe(restoringSelection: true)
|
|
1451
1480
|
let tabs = hybrid.tabIds
|
|
1452
1481
|
if let tab = tabs.first(where: { CraftHybridRoutes.normalize($0) == match.path }) {
|
|
1453
1482
|
if tab != currentTab {
|
|
@@ -1483,6 +1512,7 @@ final class CraftHybridController: UIViewController, UINavigationControllerDeleg
|
|
|
1483
1512
|
/// slide, then the entry slides in over the screen that opened it.
|
|
1484
1513
|
private func pushWeb(_ path: String) {
|
|
1485
1514
|
guard hybrid.hasWeb, !openingWeb, let nav = visibleStack else { return }
|
|
1515
|
+
interruptTabSwipe(restoringSelection: true)
|
|
1486
1516
|
openingWeb = true
|
|
1487
1517
|
hybrid.navigatePage(path) { [weak self, weak nav] depth in
|
|
1488
1518
|
guard let self else { return }
|
|
@@ -1557,7 +1587,11 @@ final class CraftHybridController: UIViewController, UINavigationControllerDeleg
|
|
|
1557
1587
|
// MARK: Tabs
|
|
1558
1588
|
|
|
1559
1589
|
func tabTapped(_ id: String, reselect: Bool) -> Bool {
|
|
1560
|
-
|
|
1590
|
+
let interruptedSwipe = activeTabSwipeTarget != nil
|
|
1591
|
+
interruptTabSwipe(restoringSelection: false)
|
|
1592
|
+
// The incoming tab is only a preview until the swipe commits, so a
|
|
1593
|
+
// tap on its already-lit indicator is a selection, not a reselect.
|
|
1594
|
+
if (reselect && !interruptedSwipe) || id == currentTab {
|
|
1561
1595
|
guard let nav = stacks[id] else { return true }
|
|
1562
1596
|
if nav.viewControllers.count > 1 {
|
|
1563
1597
|
nav.popToRootViewController(animated: true)
|