@capgo/capacitor-native-navigation 8.0.15 → 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.
|
@@ -44,6 +44,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
|
|
|
44
44
|
private var tabBar: UITabBar?
|
|
45
45
|
private var tabBarController: NativeNavigationTabController?
|
|
46
46
|
private var tabViewControllers: [UIViewController] = []
|
|
47
|
+
private weak var systemTabRootContainer: UIView?
|
|
47
48
|
private weak var originalWebViewSuperview: UIView?
|
|
48
49
|
private var originalWebViewIndex: Int?
|
|
49
50
|
private var originalWebViewAutoresizingMask: UIView.AutoresizingMask?
|
|
@@ -437,6 +438,13 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
|
|
|
437
438
|
notifyTabSelect(index: item.tag)
|
|
438
439
|
}
|
|
439
440
|
|
|
441
|
+
public func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
|
|
442
|
+
if usesSystemLiquidGlass {
|
|
443
|
+
hostWebView(in: viewController)
|
|
444
|
+
}
|
|
445
|
+
return true
|
|
446
|
+
}
|
|
447
|
+
|
|
440
448
|
public func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
|
|
441
449
|
guard !suppressTabSelectEvent else {
|
|
442
450
|
hostWebViewInSelectedSystemTab()
|
|
@@ -552,8 +560,9 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
|
|
|
552
560
|
controller.view.isHidden = !tabbarVisible
|
|
553
561
|
|
|
554
562
|
if let parent = bridge?.viewController {
|
|
563
|
+
let containerView = systemTabHostingContainerView(in: parent)
|
|
555
564
|
parent.addChild(controller)
|
|
556
|
-
insertSystemTabControllerView(controller.view, in:
|
|
565
|
+
insertSystemTabControllerView(controller.view, in: containerView)
|
|
557
566
|
controller.didMove(toParent: parent)
|
|
558
567
|
}
|
|
559
568
|
|
|
@@ -563,32 +572,94 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
|
|
|
563
572
|
return controller.tabBar
|
|
564
573
|
}
|
|
565
574
|
|
|
575
|
+
private func systemTabHostingContainerView(in parent: UIViewController) -> UIView {
|
|
576
|
+
if let systemTabRootContainer = systemTabRootContainer {
|
|
577
|
+
return systemTabRootContainer
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
guard let webView = webView,
|
|
581
|
+
parent.view === webView else {
|
|
582
|
+
return parent.view
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
let previousSuperview = webView.superview
|
|
586
|
+
let previousIndex = previousSuperview?.subviews.firstIndex(of: webView)
|
|
587
|
+
let previousFrame = webView.frame
|
|
588
|
+
let previousAutoresizingMask = webView.autoresizingMask
|
|
589
|
+
let container = UIView(frame: previousFrame)
|
|
590
|
+
container.backgroundColor = nativeNavigationFallbackBackground(for: webView)
|
|
591
|
+
container.isOpaque = true
|
|
592
|
+
container.autoresizingMask = previousAutoresizingMask.isEmpty ? [.flexibleWidth, .flexibleHeight] : previousAutoresizingMask
|
|
593
|
+
|
|
594
|
+
if let previousSuperview = previousSuperview {
|
|
595
|
+
previousSuperview.insertSubview(container, at: min(previousIndex ?? previousSuperview.subviews.count, previousSuperview.subviews.count))
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
parent.view = container
|
|
599
|
+
container.addSubview(webView)
|
|
600
|
+
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
601
|
+
webView.frame = container.bounds
|
|
602
|
+
moveNativeChrome(from: webView, to: container)
|
|
603
|
+
|
|
604
|
+
systemTabRootContainer = container
|
|
605
|
+
originalWebViewSuperview = container
|
|
606
|
+
originalWebViewIndex = 0
|
|
607
|
+
originalWebViewAutoresizingMask = webView.autoresizingMask
|
|
608
|
+
return container
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
private func moveNativeChrome(from webView: UIView, to container: UIView) {
|
|
612
|
+
if let navContainer = navContainer,
|
|
613
|
+
navContainer.superview === webView {
|
|
614
|
+
container.addSubview(navContainer)
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
566
618
|
private func applySystemTabBarItems(_ items: [UITabBarItem], selectedIndex: Int?, animated: Bool) {
|
|
567
619
|
guard let tabBarController = tabBarController else {
|
|
568
620
|
return
|
|
569
621
|
}
|
|
570
622
|
|
|
571
623
|
let previousSelectedIndex = tabBarController.selectedIndex
|
|
572
|
-
let controllers = items
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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
|
+
}
|
|
577
630
|
let shouldAnimate = animated && tabBarController.viewControllers?.count == controllers.count
|
|
578
631
|
|
|
579
632
|
suppressTabSelectEvent = true
|
|
580
|
-
|
|
633
|
+
if shouldUpdateControllers {
|
|
634
|
+
tabBarController.setViewControllers(controllers, animated: shouldAnimate)
|
|
635
|
+
}
|
|
581
636
|
if !controllers.isEmpty {
|
|
582
637
|
let fallbackIndex = selectedIndex ?? previousSelectedIndex
|
|
583
638
|
let index = min(max(fallbackIndex, 0), controllers.count - 1)
|
|
639
|
+
hostWebView(in: controllers[index])
|
|
584
640
|
tabBarController.selectedIndex = index
|
|
585
641
|
}
|
|
586
|
-
hostWebViewInSelectedSystemTab()
|
|
587
642
|
suppressTabSelectEvent = false
|
|
588
643
|
|
|
589
644
|
tabViewControllers = controllers
|
|
590
645
|
}
|
|
591
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
|
+
|
|
592
663
|
private func setSystemTabBarHidden(_ hidden: Bool) {
|
|
593
664
|
guard let tabBarController = tabBarController else {
|
|
594
665
|
return
|
|
@@ -671,15 +742,22 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
|
|
|
671
742
|
}
|
|
672
743
|
|
|
673
744
|
private func hostWebViewInSelectedSystemTab() {
|
|
745
|
+
hostWebView(in: tabBarController?.selectedViewController)
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
private func hostWebView(in viewController: UIViewController?) {
|
|
674
749
|
guard usesSystemLiquidGlass,
|
|
675
750
|
let webView = webView,
|
|
676
|
-
let selectedController =
|
|
751
|
+
let selectedController = viewController as? NativeNavigationTabContentController else {
|
|
677
752
|
return
|
|
678
753
|
}
|
|
679
754
|
|
|
680
755
|
captureOriginalWebViewPlacementIfNeeded(webView)
|
|
681
|
-
clearHostedWebViews(matching: webView, except: selectedController)
|
|
682
|
-
selectedController.host(webView: webView)
|
|
756
|
+
clearHostedWebViews(matching: webView, except: selectedController, preservingSnapshots: true)
|
|
757
|
+
guard selectedController.host(webView: webView) else {
|
|
758
|
+
isWebViewHostedInSystemTabController = false
|
|
759
|
+
return
|
|
760
|
+
}
|
|
683
761
|
clearHostedWebViews(matching: webView, except: selectedController)
|
|
684
762
|
isWebViewHostedInSystemTabController = true
|
|
685
763
|
}
|
|
@@ -700,11 +778,15 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
|
|
|
700
778
|
isWebViewHostedInSystemTabController = false
|
|
701
779
|
}
|
|
702
780
|
|
|
703
|
-
private func clearHostedWebViews(
|
|
781
|
+
private func clearHostedWebViews(
|
|
782
|
+
matching webView: UIView,
|
|
783
|
+
except owner: NativeNavigationTabContentController? = nil,
|
|
784
|
+
preservingSnapshots: Bool = false
|
|
785
|
+
) {
|
|
704
786
|
tabViewControllers
|
|
705
787
|
.compactMap { $0 as? NativeNavigationTabContentController }
|
|
706
788
|
.filter { $0 !== owner }
|
|
707
|
-
.forEach { $0.clearHostedWebView(ifMatching: webView) }
|
|
789
|
+
.forEach { $0.clearHostedWebView(ifMatching: webView, preservingSnapshot: preservingSnapshots) }
|
|
708
790
|
}
|
|
709
791
|
|
|
710
792
|
private func makeBarButtonItems(_ rawItems: [[String: Any]], placement: String) -> [UIBarButtonItem] {
|
|
@@ -900,12 +982,12 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
|
|
|
900
982
|
|
|
901
983
|
private func transitionSnapshotView(from webView: UIView, sourceRect: CGRect?) -> UIView {
|
|
902
984
|
guard let sourceRect = sourceRect else {
|
|
903
|
-
return webView.snapshotView(afterScreenUpdates: false) ??
|
|
985
|
+
return webView.snapshotView(afterScreenUpdates: false) ?? nativeNavigationSnapshotPlaceholder(for: webView)
|
|
904
986
|
}
|
|
905
987
|
|
|
906
988
|
let cropRect = sourceRect.intersection(webView.bounds)
|
|
907
989
|
guard cropRect.width > 0, cropRect.height > 0 else {
|
|
908
|
-
return webView.snapshotView(afterScreenUpdates: false) ??
|
|
990
|
+
return webView.snapshotView(afterScreenUpdates: false) ?? nativeNavigationSnapshotPlaceholder(for: webView)
|
|
909
991
|
}
|
|
910
992
|
|
|
911
993
|
let renderer = UIGraphicsImageRenderer(bounds: webView.bounds)
|
|
@@ -921,7 +1003,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
|
|
|
921
1003
|
).integral
|
|
922
1004
|
|
|
923
1005
|
guard let croppedImage = image.cgImage?.cropping(to: scaledCropRect) else {
|
|
924
|
-
return webView.snapshotView(afterScreenUpdates: false) ??
|
|
1006
|
+
return webView.snapshotView(afterScreenUpdates: false) ?? nativeNavigationSnapshotPlaceholder(for: webView)
|
|
925
1007
|
}
|
|
926
1008
|
|
|
927
1009
|
let imageView = UIImageView(image: UIImage(cgImage: croppedImage, scale: scale, orientation: image.imageOrientation))
|
|
@@ -1367,22 +1449,23 @@ private final class NativeNavigationBar: UINavigationBar {
|
|
|
1367
1449
|
}
|
|
1368
1450
|
}
|
|
1369
1451
|
|
|
1370
|
-
|
|
1452
|
+
final class NativeNavigationTabController: UITabBarController {
|
|
1371
1453
|
override func viewDidLoad() {
|
|
1372
1454
|
super.viewDidLoad()
|
|
1373
|
-
view.backgroundColor = .
|
|
1374
|
-
view.isOpaque =
|
|
1455
|
+
view.backgroundColor = .systemBackground
|
|
1456
|
+
view.isOpaque = true
|
|
1375
1457
|
tabBar.isTranslucent = true
|
|
1376
1458
|
}
|
|
1377
1459
|
}
|
|
1378
1460
|
|
|
1379
|
-
|
|
1461
|
+
final class NativeNavigationTabContentController: UIViewController {
|
|
1380
1462
|
private weak var hostedWebView: UIView?
|
|
1463
|
+
private var snapshotPlaceholder: UIView?
|
|
1381
1464
|
|
|
1382
1465
|
override func loadView() {
|
|
1383
1466
|
let view = UIView()
|
|
1384
|
-
view.backgroundColor = .
|
|
1385
|
-
view.isOpaque =
|
|
1467
|
+
view.backgroundColor = .systemBackground
|
|
1468
|
+
view.isOpaque = true
|
|
1386
1469
|
self.view = view
|
|
1387
1470
|
}
|
|
1388
1471
|
|
|
@@ -1395,24 +1478,56 @@ private final class NativeNavigationTabContentController: UIViewController {
|
|
|
1395
1478
|
hostedWebView?.frame = view.bounds
|
|
1396
1479
|
}
|
|
1397
1480
|
|
|
1398
|
-
func clearHostedWebView(ifMatching webView: UIView? = nil) {
|
|
1481
|
+
func clearHostedWebView(ifMatching webView: UIView? = nil, preservingSnapshot: Bool = false) {
|
|
1399
1482
|
guard webView == nil || hostedWebView === webView else {
|
|
1400
1483
|
return
|
|
1401
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
|
+
|
|
1402
1495
|
hostedWebView = nil
|
|
1403
1496
|
}
|
|
1404
1497
|
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1498
|
+
@discardableResult
|
|
1499
|
+
func host(webView: UIView) -> Bool {
|
|
1500
|
+
guard view !== webView, !view.isDescendant(of: webView) else {
|
|
1501
|
+
hostedWebView = nil
|
|
1502
|
+
return false
|
|
1408
1503
|
}
|
|
1504
|
+
|
|
1505
|
+
snapshotPlaceholder?.removeFromSuperview()
|
|
1506
|
+
snapshotPlaceholder = nil
|
|
1507
|
+
hostedWebView = webView
|
|
1409
1508
|
if webView.superview !== view {
|
|
1410
1509
|
webView.removeFromSuperview()
|
|
1411
1510
|
view.addSubview(webView)
|
|
1412
1511
|
}
|
|
1413
1512
|
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
1414
1513
|
webView.frame = view.bounds
|
|
1514
|
+
return true
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
private func nativeNavigationFallbackBackground(for view: UIView) -> UIColor {
|
|
1519
|
+
if let color = view.backgroundColor,
|
|
1520
|
+
color.cgColor.alpha > 0 {
|
|
1521
|
+
return color
|
|
1415
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
|
|
1416
1531
|
}
|
|
1417
1532
|
|
|
1418
1533
|
private final class SVGIconRenderer: NSObject, XMLParserDelegate {
|
|
@@ -1,19 +1,65 @@
|
|
|
1
1
|
import XCTest
|
|
2
|
+
import UIKit
|
|
2
3
|
@testable import NativeNavigationPlugin
|
|
3
4
|
|
|
4
5
|
class NativeNavigationTests: XCTestCase {
|
|
5
|
-
func testEcho() {
|
|
6
|
-
let implementation = NativeNavigation()
|
|
7
|
-
let value = "Hello, World!"
|
|
8
|
-
let result = implementation.echo(value)
|
|
9
|
-
|
|
10
|
-
XCTAssertEqual(value, result)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
6
|
func testGetPluginVersion() {
|
|
14
7
|
let implementation = NativeNavigation()
|
|
15
8
|
let result = implementation.getPluginVersion()
|
|
16
9
|
|
|
17
10
|
XCTAssertEqual("native", result)
|
|
18
11
|
}
|
|
12
|
+
|
|
13
|
+
func testTabContentControllerHostsWebView() {
|
|
14
|
+
let webView = UIView()
|
|
15
|
+
let originalContainer = UIView()
|
|
16
|
+
let controller = NativeNavigationTabContentController()
|
|
17
|
+
_ = controller.view
|
|
18
|
+
|
|
19
|
+
originalContainer.addSubview(webView)
|
|
20
|
+
|
|
21
|
+
XCTAssertTrue(controller.host(webView: webView))
|
|
22
|
+
XCTAssertEqual(webView.superview, controller.view)
|
|
23
|
+
XCTAssertEqual(webView.frame, controller.view.bounds)
|
|
24
|
+
}
|
|
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
|
+
|
|
54
|
+
func testTabContentControllerRejectsLayerCycle() {
|
|
55
|
+
let webView = UIView()
|
|
56
|
+
let controller = NativeNavigationTabContentController()
|
|
57
|
+
_ = controller.view
|
|
58
|
+
|
|
59
|
+
webView.addSubview(controller.view)
|
|
60
|
+
|
|
61
|
+
XCTAssertFalse(controller.host(webView: webView))
|
|
62
|
+
XCTAssertNil(webView.superview)
|
|
63
|
+
XCTAssertEqual(controller.view.superview, webView)
|
|
64
|
+
}
|
|
19
65
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/capacitor-native-navigation",
|
|
3
|
-
"version": "8.0.
|
|
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",
|