@capgo/capacitor-native-navigation 8.0.16 → 8.0.18

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/README.md CHANGED
@@ -10,6 +10,8 @@ Native navigation chrome for Capacitor apps. Render a native navbar, native tabb
10
10
 
11
11
  ## Demo
12
12
 
13
+ <img src="./docs/demo.webp" alt="Demo of capacitor-native-navigation in action" width="300" />
14
+
13
15
  ### Native navigation tap flow
14
16
 
15
17
  <img src="./docs/demo-navigation.webp" alt="Animated native navigation tap flow showing tab selection, push transition, and native back" width="320" />
package/docs/demo.webp ADDED
Binary file
@@ -48,6 +48,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
48
48
  private weak var originalWebViewSuperview: UIView?
49
49
  private var originalWebViewIndex: Int?
50
50
  private var originalWebViewAutoresizingMask: UIView.AutoresizingMask?
51
+ private var liftedWebViewOverlays: [NativeNavigationWeakView] = []
51
52
  private var isWebViewHostedInSystemTabController = false
52
53
  private var navbarHeight: CGFloat = 44
53
54
  private var tabbarHeight: CGFloat = 64
@@ -568,6 +569,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
568
569
 
569
570
  self.tabBarController = controller
570
571
  self.tabBar = controller.tabBar
572
+ liftWebViewOverlaysAboveSystemTabs()
571
573
  hostWebViewInSelectedSystemTab()
572
574
  return controller.tabBar
573
575
  }
@@ -605,6 +607,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
605
607
  originalWebViewSuperview = container
606
608
  originalWebViewIndex = 0
607
609
  originalWebViewAutoresizingMask = webView.autoresizingMask
610
+ liftWebViewOverlaysAboveSystemTabs()
608
611
  return container
609
612
  }
610
613
 
@@ -688,6 +691,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
688
691
  tabBarController?.view.isHidden = false
689
692
  if usesSystemLiquidGlass {
690
693
  setSystemTabBarHidden(false)
694
+ liftWebViewOverlaysAboveSystemTabs()
691
695
  hostWebViewInSelectedSystemTab()
692
696
  } else {
693
697
  tabBar.isHidden = false
@@ -752,6 +756,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
752
756
  return
753
757
  }
754
758
 
759
+ liftWebViewOverlaysAboveSystemTabs()
755
760
  captureOriginalWebViewPlacementIfNeeded(webView)
756
761
  clearHostedWebViews(matching: webView, except: selectedController, preservingSnapshots: true)
757
762
  guard selectedController.host(webView: webView) else {
@@ -760,6 +765,34 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
760
765
  }
761
766
  clearHostedWebViews(matching: webView, except: selectedController)
762
767
  isWebViewHostedInSystemTabController = true
768
+ bringLiftedWebViewOverlaysToFront()
769
+ }
770
+
771
+ private func liftWebViewOverlaysAboveSystemTabs() {
772
+ guard usesSystemLiquidGlass,
773
+ let webView = webView,
774
+ let container = systemTabRootContainer else {
775
+ return
776
+ }
777
+
778
+ nativeNavigationLiftWebViewOverlaySubviews(
779
+ from: webView,
780
+ to: container,
781
+ tracking: &liftedWebViewOverlays,
782
+ excluding: [navContainer, tabContainer, tabBarController?.view]
783
+ )
784
+ }
785
+
786
+ private func bringLiftedWebViewOverlaysToFront() {
787
+ guard let container = systemTabRootContainer else {
788
+ return
789
+ }
790
+
791
+ liftedWebViewOverlays = liftedWebViewOverlays.filter { $0.value != nil }
792
+ liftedWebViewOverlays
793
+ .compactMap(\.value)
794
+ .filter { $0.superview === container }
795
+ .forEach { container.bringSubviewToFront($0) }
763
796
  }
764
797
 
765
798
  private func restoreWebViewFromSystemTabController() {
@@ -1224,6 +1257,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
1224
1257
  if let navContainer = navContainer {
1225
1258
  bridge?.viewController?.view.bringSubviewToFront(navContainer)
1226
1259
  }
1260
+ bringLiftedWebViewOverlaysToFront()
1227
1261
  return
1228
1262
  }
1229
1263
 
@@ -1449,6 +1483,65 @@ private final class NativeNavigationBar: UINavigationBar {
1449
1483
  }
1450
1484
  }
1451
1485
 
1486
+ final class NativeNavigationWeakView {
1487
+ weak var value: UIView?
1488
+
1489
+ init(_ value: UIView) {
1490
+ self.value = value
1491
+ }
1492
+ }
1493
+
1494
+ func nativeNavigationLiftWebViewOverlaySubviews(
1495
+ from webView: UIView,
1496
+ to container: UIView,
1497
+ tracking liftedOverlays: inout [NativeNavigationWeakView],
1498
+ excluding excludedViews: [UIView?] = []
1499
+ ) {
1500
+ webView.subviews
1501
+ .filter { nativeNavigationShouldLiftWebViewOverlay($0, excluding: excludedViews) }
1502
+ .forEach { overlay in
1503
+ let frame = overlay.convert(overlay.bounds, to: container)
1504
+ let hadParentConstraints = nativeNavigationDeactivateParentConstraints(in: webView, involving: overlay)
1505
+ overlay.removeFromSuperview()
1506
+ overlay.frame = frame
1507
+ if hadParentConstraints {
1508
+ overlay.translatesAutoresizingMaskIntoConstraints = true
1509
+ }
1510
+ overlay.autoresizingMask = overlay.autoresizingMask.isEmpty
1511
+ ? [.flexibleWidth, .flexibleHeight]
1512
+ : overlay.autoresizingMask
1513
+ container.addSubview(overlay)
1514
+ liftedOverlays.append(NativeNavigationWeakView(overlay))
1515
+ }
1516
+
1517
+ liftedOverlays = liftedOverlays.filter { $0.value != nil }
1518
+ liftedOverlays
1519
+ .compactMap(\.value)
1520
+ .filter { $0.superview === container }
1521
+ .forEach { container.bringSubviewToFront($0) }
1522
+ }
1523
+
1524
+ func nativeNavigationShouldLiftWebViewOverlay(_ view: UIView, excluding excludedViews: [UIView?] = []) -> Bool {
1525
+ if excludedViews.contains(where: { $0 === view }) {
1526
+ return false
1527
+ }
1528
+
1529
+ if view is UIScrollView {
1530
+ return false
1531
+ }
1532
+
1533
+ let className = NSStringFromClass(type(of: view))
1534
+ return !className.contains("WK")
1535
+ }
1536
+
1537
+ private func nativeNavigationDeactivateParentConstraints(in parent: UIView, involving view: UIView) -> Bool {
1538
+ let constraints = parent.constraints.filter { constraint in
1539
+ constraint.firstItem === view || constraint.secondItem === view
1540
+ }
1541
+ NSLayoutConstraint.deactivate(constraints)
1542
+ return !constraints.isEmpty
1543
+ }
1544
+
1452
1545
  final class NativeNavigationTabController: UITabBarController {
1453
1546
  override func viewDidLoad() {
1454
1547
  super.viewDidLoad()
@@ -51,6 +51,33 @@ class NativeNavigationTests: XCTestCase {
51
51
  XCTAssertTrue(firstController.view.subviews.first === webView)
52
52
  }
53
53
 
54
+ func testLiftWebViewOverlaySubviewsMovesSplashOverlayAboveContainerContent() {
55
+ let webView = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
56
+ let container = UIView(frame: webView.frame)
57
+ let tabControllerView = UIView(frame: webView.frame)
58
+ let scrollView = UIScrollView(frame: webView.bounds)
59
+ let splashOverlay = UIView(frame: webView.bounds)
60
+ var liftedOverlays: [NativeNavigationWeakView] = []
61
+
62
+ container.addSubview(webView)
63
+ container.addSubview(tabControllerView)
64
+ webView.addSubview(scrollView)
65
+ webView.addSubview(splashOverlay)
66
+
67
+ nativeNavigationLiftWebViewOverlaySubviews(
68
+ from: webView,
69
+ to: container,
70
+ tracking: &liftedOverlays,
71
+ excluding: [tabControllerView]
72
+ )
73
+
74
+ XCTAssertEqual(scrollView.superview, webView)
75
+ XCTAssertEqual(splashOverlay.superview, container)
76
+ XCTAssertTrue(container.subviews.last === splashOverlay)
77
+ XCTAssertEqual(liftedOverlays.count, 1)
78
+ XCTAssertTrue(liftedOverlays.first?.value === splashOverlay)
79
+ }
80
+
54
81
  func testTabContentControllerRejectsLayerCycle() {
55
82
  let webView = UIView()
56
83
  let controller = NativeNavigationTabContentController()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-native-navigation",
3
- "version": "8.0.16",
3
+ "version": "8.0.18",
4
4
  "description": "Capacitor plugin for native navbar, tabbar, and route transition chrome over a WebView.",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",