@craft-native/ios 0.0.242 → 0.0.243

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@craft-native/ios",
3
- "version": "0.0.242",
3
+ "version": "0.0.243",
4
4
  "homepage": "https://github.com/craft-native/craft/tree/main/packages/ios#readme",
5
5
  "repository": {
6
6
  "type": "git",
@@ -896,12 +896,9 @@ final class CraftChrome: ObservableObject {
896
896
  // stack. Cancellation belongs to the stack the finger left.
897
897
  tabSwipePrevious = previous
898
898
  tabSwipeTarget = id
899
- // Only the indicator moves now. The highlight follows it across the
900
- // midpoint (updateTabSwipe), as iOS's own tab bar lens does; turning
901
- // it at once rebuilt every tab in the swipe's first frame, which is
902
- // also when UIKit puts the incoming screen on screen.
903
899
  withAnimation(nil) {
904
900
  tabSwipePosition = tabs.firstIndex(where: { $0.id == previous }).map { CGFloat($0) }
901
+ selected = id
905
902
  }
906
903
  }
907
904
 
@@ -913,14 +910,11 @@ final class CraftChrome: ObservableObject {
913
910
  let to = tabs.firstIndex(where: { $0.id == target }) else { return }
914
911
  let position = CGFloat(from) + CGFloat(to - from) * min(1, max(0, progress))
915
912
  if tabSwipePosition != position { withAnimation(nil) { tabSwipePosition = position } }
916
- let lit = progress >= 0.5 ? target : previous
917
- if selected != lit { withAnimation(nil) { selected = lit } }
918
913
  }
919
914
 
920
915
  func settleTabSwipe(completed: Bool, duration: TimeInterval) {
921
916
  guard let id = completed ? tabSwipeTarget : tabSwipePrevious,
922
917
  let index = tabs.firstIndex(where: { $0.id == id }) else { return }
923
- if selected != id { withAnimation(nil) { selected = id } }
924
918
  withAnimation(Self.motion(.easeOut(duration: duration))) { tabSwipePosition = CGFloat(index) }
925
919
  }
926
920
 
@@ -1402,7 +1402,10 @@ final class CraftHybridController: UIViewController, UINavigationControllerDeleg
1402
1402
 
1403
1403
  // MARK: Stacks
1404
1404
 
1405
- private func makeStack(tab: String, rootPath: String) -> UINavigationController {
1405
+ /// `hidden`: a stack prepared off screen. It is hidden before it joins
1406
+ /// the view hierarchy, so its screen never takes the shell's chrome (the
1407
+ /// bar, the tab accessory) from the one on screen.
1408
+ private func makeStack(tab: String, rootPath: String, hidden: Bool = false) -> UINavigationController {
1406
1409
  let nav = UINavigationController(rootViewController: entry(for: rootPath, isTabRoot: true, depth: nil))
1407
1410
  nav.delegate = self
1408
1411
  nav.view.backgroundColor = config.resolvedBackgroundColor
@@ -1411,6 +1414,7 @@ final class CraftHybridController: UIViewController, UINavigationControllerDeleg
1411
1414
  addChild(nav)
1412
1415
  nav.view.frame = view.bounds
1413
1416
  nav.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
1417
+ nav.view.isHidden = hidden
1414
1418
  view.addSubview(nav.view)
1415
1419
  nav.didMove(toParent: self)
1416
1420
  // Only once the stack's view has loaded: the recognizer is made then,
@@ -1515,8 +1519,7 @@ final class CraftHybridController: UIViewController, UINavigationControllerDeleg
1515
1519
  let id = tabs[next]
1516
1520
  guard self.stacks[id] == nil else { self.prepareAdjacentNativeTabs(); return }
1517
1521
  let nav = CraftFramePacing.shared.measure("prepare \(id)") { () -> UINavigationController in
1518
- let nav = self.makeStack(tab: id, rootPath: id)
1519
- nav.view.isHidden = true
1522
+ let nav = self.makeStack(tab: id, rootPath: id, hidden: true)
1520
1523
  nav.view.layoutIfNeeded()
1521
1524
  // Draw its text and pictures now. A hidden layer is not
1522
1525
  // drawn at commit, so the first swipe to this tab drew
@@ -1752,7 +1755,7 @@ final class CraftHybridController: UIViewController, UINavigationControllerDeleg
1752
1755
  // Keep this screen up until the page has switched, rather than
1753
1756
  // showing it half way.
1754
1757
  cancelPendingTab()
1755
- if stacks[id] == nil { _ = makeStack(tab: id, rootPath: id); stacks[id]?.view.isHidden = true; view.bringSubviewToFront(visibleStack?.view ?? view) }
1758
+ if stacks[id] == nil { _ = makeStack(tab: id, rootPath: id, hidden: true); view.bringSubviewToFront(visibleStack?.view ?? view) }
1756
1759
  let deadline = DispatchWorkItem { [weak self] in self?.show(tab: id) }
1757
1760
  pendingTab = (id, deadline)
1758
1761
  DispatchQueue.main.asyncAfter(deadline: .now() + 0.45, execute: deadline)
@@ -4598,11 +4598,25 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
4598
4598
  }
4599
4599
  applyNavigationBarAppearance()
4600
4600
  // Each screen says whether it wants the tab bar, so one that hid it
4601
- // never leaves it hidden for the next.
4602
- restoreShellChrome()
4601
+ // never leaves it hidden for the next. Only the screen on screen:
4602
+ // UIKit tells a tab's screen prepared in a hidden stack that it
4603
+ // appears too, and that one took the accessory of the screen showing.
4604
+ if isShownOnScreen { restoreShellChrome() }
4603
4605
  refreshTraitDefaults()
4604
4606
  }
4605
4607
 
4608
+ /// In the window with nothing above it hidden: the screen the person
4609
+ /// sees, not a tab prepared behind it.
4610
+ private var isShownOnScreen: Bool {
4611
+ guard let window = view.window else { return false }
4612
+ var candidate: UIView? = view
4613
+ while let current = candidate, current !== window {
4614
+ if current.isHidden { return false }
4615
+ candidate = current.superview
4616
+ }
4617
+ return true
4618
+ }
4619
+
4606
4620
  override func viewWillDisappear(_ animated: Bool) {
4607
4621
  super.viewWillDisappear(animated)
4608
4622
  screenVisibilityChanged(false)
@@ -4620,11 +4634,15 @@ final class CraftNativeScreenController: UIViewController, UIScrollViewDelegate,
4620
4634
 
4621
4635
  override func viewDidAppear(_ animated: Bool) {
4622
4636
  super.viewDidAppear(animated)
4623
- screenVisibilityChanged(true)
4624
- // A navigation breadcrumb for the crash reporter: the trail a crash
4625
- // report carries is the screens that led to it.
4626
- if !isAppScript, let routeName, !routeName.isEmpty {
4627
- CraftCrashReporter.shared.screenAppeared(routeName)
4637
+ // A tab prepared in a hidden stack "appears" to UIKit as well; the
4638
+ // shell says when it really shows (screenVisibilityChanged).
4639
+ if isShownOnScreen {
4640
+ screenVisibilityChanged(true)
4641
+ // A navigation breadcrumb for the crash reporter: the trail a
4642
+ // crash report carries is the screens that led to it.
4643
+ if !isAppScript, let routeName, !routeName.isEmpty {
4644
+ CraftCrashReporter.shared.screenAppeared(routeName)
4645
+ }
4628
4646
  }
4629
4647
  // onLayout reports views that are on screen: a pushed screen lays
4630
4648
  // out before it has a window, and nothing moved again once it had