@capgo/capacitor-native-navigation 8.0.15-beta.pr12.18.1 → 8.0.16

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.
@@ -438,6 +438,13 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
438
438
  notifyTabSelect(index: item.tag)
439
439
  }
440
440
 
441
+ public func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
442
+ if usesSystemLiquidGlass {
443
+ hostWebView(in: viewController)
444
+ }
445
+ return true
446
+ }
447
+
441
448
  public func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
442
449
  guard !suppressTabSelectEvent else {
443
450
  hostWebViewInSelectedSystemTab()
@@ -580,8 +587,8 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
580
587
  let previousFrame = webView.frame
581
588
  let previousAutoresizingMask = webView.autoresizingMask
582
589
  let container = UIView(frame: previousFrame)
583
- container.backgroundColor = .clear
584
- container.isOpaque = false
590
+ container.backgroundColor = nativeNavigationFallbackBackground(for: webView)
591
+ container.isOpaque = true
585
592
  container.autoresizingMask = previousAutoresizingMask.isEmpty ? [.flexibleWidth, .flexibleHeight] : previousAutoresizingMask
586
593
 
587
594
  if let previousSuperview = previousSuperview {
@@ -614,26 +621,45 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
614
621
  }
615
622
 
616
623
  let previousSelectedIndex = tabBarController.selectedIndex
617
- let controllers = items.map { item -> UIViewController in
618
- let controller = NativeNavigationTabContentController()
619
- controller.tabBarItem = item
620
- return controller
621
- }
624
+ let controllers = systemTabContentControllers(for: items)
625
+ let currentControllers = tabBarController.viewControllers ?? []
626
+ let shouldUpdateControllers = currentControllers.count != controllers.count
627
+ || zip(currentControllers, controllers).contains { currentController, nextController in
628
+ currentController !== nextController
629
+ }
622
630
  let shouldAnimate = animated && tabBarController.viewControllers?.count == controllers.count
623
631
 
624
632
  suppressTabSelectEvent = true
625
- tabBarController.setViewControllers(controllers, animated: shouldAnimate)
633
+ if shouldUpdateControllers {
634
+ tabBarController.setViewControllers(controllers, animated: shouldAnimate)
635
+ }
626
636
  if !controllers.isEmpty {
627
637
  let fallbackIndex = selectedIndex ?? previousSelectedIndex
628
638
  let index = min(max(fallbackIndex, 0), controllers.count - 1)
639
+ hostWebView(in: controllers[index])
629
640
  tabBarController.selectedIndex = index
630
641
  }
631
- hostWebViewInSelectedSystemTab()
632
642
  suppressTabSelectEvent = false
633
643
 
634
644
  tabViewControllers = controllers
635
645
  }
636
646
 
647
+ private func systemTabContentControllers(for items: [UITabBarItem]) -> [UIViewController] {
648
+ let existingControllers = tabViewControllers.compactMap { $0 as? NativeNavigationTabContentController }
649
+ if existingControllers.count == items.count {
650
+ zip(existingControllers, items).forEach { controller, item in
651
+ controller.tabBarItem = item
652
+ }
653
+ return existingControllers
654
+ }
655
+
656
+ return items.map { item -> UIViewController in
657
+ let controller = NativeNavigationTabContentController()
658
+ controller.tabBarItem = item
659
+ return controller
660
+ }
661
+ }
662
+
637
663
  private func setSystemTabBarHidden(_ hidden: Bool) {
638
664
  guard let tabBarController = tabBarController else {
639
665
  return
@@ -716,14 +742,18 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
716
742
  }
717
743
 
718
744
  private func hostWebViewInSelectedSystemTab() {
745
+ hostWebView(in: tabBarController?.selectedViewController)
746
+ }
747
+
748
+ private func hostWebView(in viewController: UIViewController?) {
719
749
  guard usesSystemLiquidGlass,
720
750
  let webView = webView,
721
- let selectedController = tabBarController?.selectedViewController as? NativeNavigationTabContentController else {
751
+ let selectedController = viewController as? NativeNavigationTabContentController else {
722
752
  return
723
753
  }
724
754
 
725
755
  captureOriginalWebViewPlacementIfNeeded(webView)
726
- clearHostedWebViews(matching: webView, except: selectedController)
756
+ clearHostedWebViews(matching: webView, except: selectedController, preservingSnapshots: true)
727
757
  guard selectedController.host(webView: webView) else {
728
758
  isWebViewHostedInSystemTabController = false
729
759
  return
@@ -748,11 +778,15 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
748
778
  isWebViewHostedInSystemTabController = false
749
779
  }
750
780
 
751
- private func clearHostedWebViews(matching webView: UIView, except owner: NativeNavigationTabContentController? = nil) {
781
+ private func clearHostedWebViews(
782
+ matching webView: UIView,
783
+ except owner: NativeNavigationTabContentController? = nil,
784
+ preservingSnapshots: Bool = false
785
+ ) {
752
786
  tabViewControllers
753
787
  .compactMap { $0 as? NativeNavigationTabContentController }
754
788
  .filter { $0 !== owner }
755
- .forEach { $0.clearHostedWebView(ifMatching: webView) }
789
+ .forEach { $0.clearHostedWebView(ifMatching: webView, preservingSnapshot: preservingSnapshots) }
756
790
  }
757
791
 
758
792
  private func makeBarButtonItems(_ rawItems: [[String: Any]], placement: String) -> [UIBarButtonItem] {
@@ -948,12 +982,12 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
948
982
 
949
983
  private func transitionSnapshotView(from webView: UIView, sourceRect: CGRect?) -> UIView {
950
984
  guard let sourceRect = sourceRect else {
951
- return webView.snapshotView(afterScreenUpdates: false) ?? UIView(frame: webView.frame)
985
+ return webView.snapshotView(afterScreenUpdates: false) ?? nativeNavigationSnapshotPlaceholder(for: webView)
952
986
  }
953
987
 
954
988
  let cropRect = sourceRect.intersection(webView.bounds)
955
989
  guard cropRect.width > 0, cropRect.height > 0 else {
956
- return webView.snapshotView(afterScreenUpdates: false) ?? UIView(frame: webView.frame)
990
+ return webView.snapshotView(afterScreenUpdates: false) ?? nativeNavigationSnapshotPlaceholder(for: webView)
957
991
  }
958
992
 
959
993
  let renderer = UIGraphicsImageRenderer(bounds: webView.bounds)
@@ -969,7 +1003,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
969
1003
  ).integral
970
1004
 
971
1005
  guard let croppedImage = image.cgImage?.cropping(to: scaledCropRect) else {
972
- return webView.snapshotView(afterScreenUpdates: false) ?? UIView(frame: webView.frame)
1006
+ return webView.snapshotView(afterScreenUpdates: false) ?? nativeNavigationSnapshotPlaceholder(for: webView)
973
1007
  }
974
1008
 
975
1009
  let imageView = UIImageView(image: UIImage(cgImage: croppedImage, scale: scale, orientation: image.imageOrientation))
@@ -1418,19 +1452,20 @@ private final class NativeNavigationBar: UINavigationBar {
1418
1452
  final class NativeNavigationTabController: UITabBarController {
1419
1453
  override func viewDidLoad() {
1420
1454
  super.viewDidLoad()
1421
- view.backgroundColor = .clear
1422
- view.isOpaque = false
1455
+ view.backgroundColor = .systemBackground
1456
+ view.isOpaque = true
1423
1457
  tabBar.isTranslucent = true
1424
1458
  }
1425
1459
  }
1426
1460
 
1427
1461
  final class NativeNavigationTabContentController: UIViewController {
1428
1462
  private weak var hostedWebView: UIView?
1463
+ private var snapshotPlaceholder: UIView?
1429
1464
 
1430
1465
  override func loadView() {
1431
1466
  let view = UIView()
1432
- view.backgroundColor = .clear
1433
- view.isOpaque = false
1467
+ view.backgroundColor = .systemBackground
1468
+ view.isOpaque = true
1434
1469
  self.view = view
1435
1470
  }
1436
1471
 
@@ -1443,10 +1478,20 @@ final class NativeNavigationTabContentController: UIViewController {
1443
1478
  hostedWebView?.frame = view.bounds
1444
1479
  }
1445
1480
 
1446
- func clearHostedWebView(ifMatching webView: UIView? = nil) {
1481
+ func clearHostedWebView(ifMatching webView: UIView? = nil, preservingSnapshot: Bool = false) {
1447
1482
  guard webView == nil || hostedWebView === webView else {
1448
1483
  return
1449
1484
  }
1485
+
1486
+ if preservingSnapshot, let hostedWebView = hostedWebView, hostedWebView.superview === view {
1487
+ let placeholder = hostedWebView.snapshotView(afterScreenUpdates: false) ?? nativeNavigationSnapshotPlaceholder(for: hostedWebView)
1488
+ placeholder.frame = hostedWebView.frame
1489
+ placeholder.autoresizingMask = [.flexibleWidth, .flexibleHeight]
1490
+ snapshotPlaceholder?.removeFromSuperview()
1491
+ view.insertSubview(placeholder, belowSubview: hostedWebView)
1492
+ snapshotPlaceholder = placeholder
1493
+ }
1494
+
1450
1495
  hostedWebView = nil
1451
1496
  }
1452
1497
 
@@ -1457,6 +1502,8 @@ final class NativeNavigationTabContentController: UIViewController {
1457
1502
  return false
1458
1503
  }
1459
1504
 
1505
+ snapshotPlaceholder?.removeFromSuperview()
1506
+ snapshotPlaceholder = nil
1460
1507
  hostedWebView = webView
1461
1508
  if webView.superview !== view {
1462
1509
  webView.removeFromSuperview()
@@ -1468,6 +1515,21 @@ final class NativeNavigationTabContentController: UIViewController {
1468
1515
  }
1469
1516
  }
1470
1517
 
1518
+ private func nativeNavigationFallbackBackground(for view: UIView) -> UIColor {
1519
+ if let color = view.backgroundColor,
1520
+ color.cgColor.alpha > 0 {
1521
+ return color
1522
+ }
1523
+ return .systemBackground
1524
+ }
1525
+
1526
+ private func nativeNavigationSnapshotPlaceholder(for view: UIView) -> UIView {
1527
+ let placeholder = UIView(frame: view.frame)
1528
+ placeholder.backgroundColor = nativeNavigationFallbackBackground(for: view)
1529
+ placeholder.isOpaque = true
1530
+ return placeholder
1531
+ }
1532
+
1471
1533
  private final class SVGIconRenderer: NSObject, XMLParserDelegate {
1472
1534
  private let context: CGContext
1473
1535
  private var styleStack = [SVGRenderStyle()]
@@ -23,6 +23,34 @@ class NativeNavigationTests: XCTestCase {
23
23
  XCTAssertEqual(webView.frame, controller.view.bounds)
24
24
  }
25
25
 
26
+ func testTabContentControllerKeepsSnapshotPlaceholderWhenWebViewMoves() {
27
+ let webView = UIView()
28
+ let firstController = NativeNavigationTabContentController()
29
+ let secondController = NativeNavigationTabContentController()
30
+ _ = firstController.view
31
+ _ = secondController.view
32
+
33
+ firstController.view.frame = CGRect(x: 0, y: 0, width: 320, height: 480)
34
+ secondController.view.frame = CGRect(x: 0, y: 0, width: 320, height: 480)
35
+ webView.backgroundColor = .systemBackground
36
+
37
+ XCTAssertTrue(firstController.host(webView: webView))
38
+ XCTAssertEqual(firstController.view.subviews.count, 1)
39
+
40
+ firstController.clearHostedWebView(ifMatching: webView, preservingSnapshot: true)
41
+ XCTAssertEqual(firstController.view.subviews.count, 2)
42
+
43
+ XCTAssertTrue(secondController.host(webView: webView))
44
+ XCTAssertEqual(webView.superview, secondController.view)
45
+ XCTAssertEqual(firstController.view.subviews.count, 1)
46
+ XCTAssertFalse(firstController.view.subviews.contains(webView))
47
+
48
+ XCTAssertTrue(firstController.host(webView: webView))
49
+ XCTAssertEqual(webView.superview, firstController.view)
50
+ XCTAssertEqual(firstController.view.subviews.count, 1)
51
+ XCTAssertTrue(firstController.view.subviews.first === webView)
52
+ }
53
+
26
54
  func testTabContentControllerRejectsLayerCycle() {
27
55
  let webView = UIView()
28
56
  let controller = NativeNavigationTabContentController()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-native-navigation",
3
- "version": "8.0.15-beta.pr12.18.1",
3
+ "version": "8.0.16",
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",
@@ -26,6 +26,9 @@
26
26
  "url": "https://github.com/Cap-go/capacitor-native-navigation/issues"
27
27
  },
28
28
  "homepage": "https://capgo.app/docs/plugins/native-navigation/",
29
+ "engines": {
30
+ "node": ">=22.0.0"
31
+ },
29
32
  "keywords": [
30
33
  "capacitor",
31
34
  "native-navigation",