@capgo/capacitor-native-navigation 8.0.12 → 8.0.14

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,10 @@ 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 originalWebViewSuperview: UIView?
48
+ private var originalWebViewIndex: Int?
49
+ private var originalWebViewAutoresizingMask: UIView.AutoresizingMask?
50
+ private var isWebViewHostedInSystemTabController = false
47
51
  private var navbarHeight: CGFloat = 44
48
52
  private var tabbarHeight: CGFloat = 64
49
53
  private let floatingTabbarHorizontalMargin: CGFloat = 24
@@ -96,6 +100,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
96
100
  if !self.isEnabled {
97
101
  self.navContainer?.isHidden = true
98
102
  self.tabContainer?.isHidden = true
103
+ self.restoreWebViewFromSystemTabController()
99
104
  self.tabBarController?.view.isHidden = true
100
105
  self.tabBar?.isHidden = true
101
106
  }
@@ -170,9 +175,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
170
175
  self.tabbarVisible = !hidden
171
176
 
172
177
  guard !hidden else {
173
- self.tabContainer?.isHidden = true
174
- self.tabBarController?.view.isHidden = true
175
- self.tabBar?.isHidden = true
178
+ self.hideTabBarChrome()
176
179
  self.updateInsetsAndNotify()
177
180
  call.resolve(self.insetsResult())
178
181
  return
@@ -204,9 +207,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
204
207
  }
205
208
 
206
209
  self.applyTabBarAppearance(tabBar: tabBar, options: call)
207
- self.tabContainer?.isHidden = false
208
- self.tabBarController?.view.isHidden = false
209
- tabBar.isHidden = false
210
+ self.showTabBarChrome(tabBar)
210
211
  self.layoutChrome()
211
212
  self.updateInsetsAndNotify()
212
213
  call.resolve(self.insetsResult())
@@ -215,7 +216,8 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
215
216
 
216
217
  @objc func beginTransition(_ call: CAPPluginCall) {
217
218
  DispatchQueue.main.async {
218
- guard let webView = self.webView, let rootView = self.bridge?.viewController?.view else {
219
+ guard let webView = self.webView,
220
+ let transitionContainer = webView.superview else {
219
221
  call.reject("WebView unavailable")
220
222
  return
221
223
  }
@@ -224,7 +226,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
224
226
  let direction = call.getString("direction") ?? "forward"
225
227
  let durationMs = Int((call.getDouble("duration") ?? self.defaultTransitionDuration * 1_000).rounded())
226
228
  let zoomSourceRect = direction == "zoom" ? self.transitionRect(call.getObject("sourceRect")) : nil
227
- let zoomSourceFrame = zoomSourceRect.map { self.rootFrame(for: $0, webView: webView) }
229
+ let zoomSourceFrame = zoomSourceRect.map { self.transitionFrame(for: $0, webView: webView) }
228
230
  let cornerRadius = CGFloat(call.getDouble("cornerRadius") ?? 0)
229
231
 
230
232
  self.transitionSnapshot?.removeFromSuperview()
@@ -233,7 +235,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
233
235
  snapshot.autoresizingMask = zoomSourceFrame == nil ? [.flexibleWidth, .flexibleHeight] : []
234
236
  snapshot.layer.cornerRadius = cornerRadius
235
237
  snapshot.clipsToBounds = cornerRadius > 0
236
- rootView.insertSubview(snapshot, aboveSubview: webView)
238
+ transitionContainer.insertSubview(snapshot, aboveSubview: webView)
237
239
  self.bringChromeToFront()
238
240
  self.transitionSnapshot = snapshot
239
241
  self.activeTransitionId = transitionId
@@ -274,8 +276,8 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
274
276
  let targetRect = self.transitionRect(call.getObject("targetRect"))
275
277
  self.finishZoomTransition(NativeNavigationZoomTransitionContext(
276
278
  transition: transition,
277
- sourceFrame: sourceRect.map { self.rootFrame(for: $0, webView: webView) },
278
- targetFrame: targetRect.map { self.rootFrame(for: $0, webView: webView) },
279
+ sourceFrame: sourceRect.map { self.transitionFrame(for: $0, webView: webView) },
280
+ targetFrame: targetRect.map { self.transitionFrame(for: $0, webView: webView) },
279
281
  cornerRadius: CGFloat(call.getDouble("cornerRadius") ?? Double(self.activeZoomCornerRadius))
280
282
  ))
281
283
  return
@@ -437,9 +439,11 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
437
439
 
438
440
  public func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
439
441
  guard !suppressTabSelectEvent else {
442
+ hostWebViewInSelectedSystemTab()
440
443
  return
441
444
  }
442
445
  let index = tabBarController.viewControllers?.firstIndex(of: viewController) ?? viewController.tabBarItem.tag
446
+ hostWebViewInSelectedSystemTab()
443
447
  notifyTabSelect(index: index)
444
448
  }
445
449
 
@@ -538,6 +542,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
538
542
 
539
543
  private func ensureSystemTabBar() -> UITabBar {
540
544
  if let tabBarController = tabBarController {
545
+ hostWebViewInSelectedSystemTab()
541
546
  return tabBarController.tabBar
542
547
  }
543
548
 
@@ -548,12 +553,13 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
548
553
 
549
554
  if let parent = bridge?.viewController {
550
555
  parent.addChild(controller)
551
- parent.view.addSubview(controller.view)
556
+ insertSystemTabControllerView(controller.view, in: parent.view)
552
557
  controller.didMove(toParent: parent)
553
558
  }
554
559
 
555
560
  self.tabBarController = controller
556
561
  self.tabBar = controller.tabBar
562
+ hostWebViewInSelectedSystemTab()
557
563
  return controller.tabBar
558
564
  }
559
565
 
@@ -577,11 +583,130 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
577
583
  let index = min(max(fallbackIndex, 0), controllers.count - 1)
578
584
  tabBarController.selectedIndex = index
579
585
  }
586
+ hostWebViewInSelectedSystemTab()
580
587
  suppressTabSelectEvent = false
581
588
 
582
589
  tabViewControllers = controllers
583
590
  }
584
591
 
592
+ private func setSystemTabBarHidden(_ hidden: Bool) {
593
+ guard let tabBarController = tabBarController else {
594
+ return
595
+ }
596
+
597
+ if #available(iOS 18.0, *) {
598
+ tabBarController.setTabBarHidden(hidden, animated: false)
599
+ } else {
600
+ tabBarController.tabBar.isHidden = hidden
601
+ }
602
+ }
603
+
604
+ private func hideTabBarChrome() {
605
+ if usesSystemLiquidGlass {
606
+ setSystemTabBarHidden(true)
607
+ tabBarController?.view.isHidden = false
608
+ hostWebViewInSelectedSystemTab()
609
+ } else {
610
+ tabContainer?.isHidden = true
611
+ tabBar?.isHidden = true
612
+ }
613
+ }
614
+
615
+ private func showTabBarChrome(_ tabBar: UITabBar) {
616
+ tabContainer?.isHidden = false
617
+ tabBarController?.view.isHidden = false
618
+ if usesSystemLiquidGlass {
619
+ setSystemTabBarHidden(false)
620
+ hostWebViewInSelectedSystemTab()
621
+ } else {
622
+ tabBar.isHidden = false
623
+ }
624
+ }
625
+
626
+ private func captureOriginalWebViewPlacementIfNeeded(_ webView: UIView) {
627
+ guard originalWebViewSuperview == nil, let superview = webView.superview else {
628
+ return
629
+ }
630
+
631
+ originalWebViewSuperview = superview
632
+ originalWebViewIndex = superview.subviews.firstIndex(of: webView)
633
+ originalWebViewAutoresizingMask = webView.autoresizingMask
634
+ }
635
+
636
+ private func insertSystemTabControllerView(_ controllerView: UIView, in parentView: UIView) {
637
+ guard let webView = webView else {
638
+ parentView.addSubview(controllerView)
639
+ return
640
+ }
641
+
642
+ captureOriginalWebViewPlacementIfNeeded(webView)
643
+ let insertionIndex = systemTabControllerInsertionIndex(in: parentView, for: webView)
644
+ parentView.insertSubview(controllerView, at: insertionIndex)
645
+ }
646
+
647
+ private func systemTabControllerInsertionIndex(in parentView: UIView, for webView: UIView) -> Int {
648
+ if let directChild = directChild(of: parentView, containing: webView),
649
+ let index = parentView.subviews.firstIndex(of: directChild) {
650
+ return min(index, parentView.subviews.count)
651
+ }
652
+
653
+ if let originalWebViewSuperview = originalWebViewSuperview,
654
+ originalWebViewSuperview === parentView {
655
+ return min(originalWebViewIndex ?? parentView.subviews.count, parentView.subviews.count)
656
+ }
657
+
658
+ return parentView.subviews.count
659
+ }
660
+
661
+ private func directChild(of ancestor: UIView, containing descendant: UIView) -> UIView? {
662
+ var current: UIView? = descendant
663
+ while let view = current, let superview = view.superview {
664
+ if superview === ancestor {
665
+ return view
666
+ }
667
+ current = superview
668
+ }
669
+
670
+ return nil
671
+ }
672
+
673
+ private func hostWebViewInSelectedSystemTab() {
674
+ guard usesSystemLiquidGlass,
675
+ let webView = webView,
676
+ let selectedController = tabBarController?.selectedViewController as? NativeNavigationTabContentController else {
677
+ return
678
+ }
679
+
680
+ captureOriginalWebViewPlacementIfNeeded(webView)
681
+ clearHostedWebViews(matching: webView, except: selectedController)
682
+ selectedController.host(webView: webView)
683
+ clearHostedWebViews(matching: webView, except: selectedController)
684
+ isWebViewHostedInSystemTabController = true
685
+ }
686
+
687
+ private func restoreWebViewFromSystemTabController() {
688
+ guard isWebViewHostedInSystemTabController,
689
+ let webView = webView,
690
+ let targetSuperview = originalWebViewSuperview ?? bridge?.viewController?.view else {
691
+ return
692
+ }
693
+
694
+ let insertionIndex = min(originalWebViewIndex ?? targetSuperview.subviews.count, targetSuperview.subviews.count)
695
+ clearHostedWebViews(matching: webView)
696
+ webView.removeFromSuperview()
697
+ targetSuperview.insertSubview(webView, at: insertionIndex)
698
+ webView.autoresizingMask = originalWebViewAutoresizingMask ?? [.flexibleWidth, .flexibleHeight]
699
+ webView.frame = targetSuperview.bounds
700
+ isWebViewHostedInSystemTabController = false
701
+ }
702
+
703
+ private func clearHostedWebViews(matching webView: UIView, except owner: NativeNavigationTabContentController? = nil) {
704
+ tabViewControllers
705
+ .compactMap { $0 as? NativeNavigationTabContentController }
706
+ .filter { $0 !== owner }
707
+ .forEach { $0.clearHostedWebView(ifMatching: webView) }
708
+ }
709
+
585
710
  private func makeBarButtonItems(_ rawItems: [[String: Any]], placement: String) -> [UIBarButtonItem] {
586
711
  return rawItems.map { rawItem in
587
712
  let id = rawItem["id"] as? String ?? UUID().uuidString
@@ -761,13 +886,16 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
761
886
  )
762
887
  }
763
888
 
764
- private func rootFrame(for viewportRect: CGRect, webView: UIView) -> CGRect {
765
- return CGRect(
766
- x: webView.frame.minX + viewportRect.minX,
767
- y: webView.frame.minY + viewportRect.minY,
768
- width: viewportRect.width,
769
- height: viewportRect.height
770
- )
889
+ private func transitionFrame(for viewportRect: CGRect, webView: UIView) -> CGRect {
890
+ guard let transitionContainer = webView.superview else {
891
+ return CGRect(
892
+ x: webView.frame.minX + viewportRect.minX,
893
+ y: webView.frame.minY + viewportRect.minY,
894
+ width: viewportRect.width,
895
+ height: viewportRect.height
896
+ )
897
+ }
898
+ return webView.convert(viewportRect, to: transitionContainer)
771
899
  }
772
900
 
773
901
  private func transitionSnapshotView(from webView: UIView, sourceRect: CGRect?) -> UIView {
@@ -1010,6 +1138,13 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
1010
1138
  }
1011
1139
 
1012
1140
  private func bringChromeToFront() {
1141
+ if usesSystemLiquidGlass {
1142
+ if let navContainer = navContainer {
1143
+ bridge?.viewController?.view.bringSubviewToFront(navContainer)
1144
+ }
1145
+ return
1146
+ }
1147
+
1013
1148
  if let navContainer = navContainer {
1014
1149
  bridge?.viewController?.view.bringSubviewToFront(navContainer)
1015
1150
  }
@@ -1233,41 +1368,51 @@ private final class NativeNavigationBar: UINavigationBar {
1233
1368
  }
1234
1369
 
1235
1370
  private final class NativeNavigationTabController: UITabBarController {
1236
- override func loadView() {
1237
- let view = NativeNavigationTabControllerView()
1238
- view.backgroundColor = .clear
1239
- view.isOpaque = false
1240
- self.view = view
1241
- }
1242
-
1243
1371
  override func viewDidLoad() {
1244
1372
  super.viewDidLoad()
1245
1373
  view.backgroundColor = .clear
1246
1374
  view.isOpaque = false
1247
1375
  tabBar.isTranslucent = true
1248
- (view as? NativeNavigationTabControllerView)?.tabBar = tabBar
1249
- }
1250
- }
1251
-
1252
- private final class NativeNavigationTabControllerView: UIView {
1253
- weak var tabBar: UITabBar?
1254
-
1255
- override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
1256
- guard let tabBar = tabBar, !tabBar.isHidden, tabBar.alpha > 0.01 else {
1257
- return false
1258
- }
1259
- let tabPoint = convert(point, to: tabBar)
1260
- return tabBar.point(inside: tabPoint, with: event)
1261
1376
  }
1262
1377
  }
1263
1378
 
1264
1379
  private final class NativeNavigationTabContentController: UIViewController {
1380
+ private weak var hostedWebView: UIView?
1381
+
1265
1382
  override func loadView() {
1266
1383
  let view = UIView()
1267
1384
  view.backgroundColor = .clear
1268
1385
  view.isOpaque = false
1269
1386
  self.view = view
1270
1387
  }
1388
+
1389
+ override func viewDidLayoutSubviews() {
1390
+ super.viewDidLayoutSubviews()
1391
+ guard hostedWebView?.superview === view else {
1392
+ hostedWebView = nil
1393
+ return
1394
+ }
1395
+ hostedWebView?.frame = view.bounds
1396
+ }
1397
+
1398
+ func clearHostedWebView(ifMatching webView: UIView? = nil) {
1399
+ guard webView == nil || hostedWebView === webView else {
1400
+ return
1401
+ }
1402
+ hostedWebView = nil
1403
+ }
1404
+
1405
+ func host(webView: UIView) {
1406
+ if hostedWebView !== webView {
1407
+ hostedWebView = webView
1408
+ }
1409
+ if webView.superview !== view {
1410
+ webView.removeFromSuperview()
1411
+ view.addSubview(webView)
1412
+ }
1413
+ webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
1414
+ webView.frame = view.bounds
1415
+ }
1271
1416
  }
1272
1417
 
1273
1418
  private final class SVGIconRenderer: NSObject, XMLParserDelegate {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-native-navigation",
3
- "version": "8.0.12",
3
+ "version": "8.0.14",
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",