@capgo/capacitor-native-navigation 8.2.1 → 8.2.3

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.
@@ -63,6 +63,9 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
63
63
  private var navbarItemTitle: [String: String] = [:]
64
64
  private var tabIds: [String] = []
65
65
  private var tabTitles: [String] = []
66
+ private var tabDisplayTitles: [String?] = []
67
+ private var tabBaseImages: [UIImage?] = []
68
+ private var tabSelectedImages: [UIImage?] = []
66
69
  private var suppressTabSelectEvent = false
67
70
  private var transitionSnapshot: UIView?
68
71
  private var activeTransitionId: String?
@@ -207,7 +210,13 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
207
210
  self.tabContainer?.isHidden = true
208
211
  self.applySystemTabBarItems(items, selectedIndex: selectedIndex, animated: call.getBool("animated", false))
209
212
  self.applyTabBarAppearance(tabBar: tabBar, options: call)
210
- self.showTabBarChrome(tabBar)
213
+ if items.isEmpty {
214
+ self.tabbarVisible = false
215
+ self.setSystemTabBarHidden(true)
216
+ self.tabBarController?.view.isHidden = true
217
+ } else {
218
+ self.showTabBarChrome(tabBar)
219
+ }
211
220
  } else {
212
221
  self.restoreWebViewFromSystemTabController()
213
222
  self.setSystemTabBarHidden(true)
@@ -218,23 +227,36 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
218
227
  selectedId: selectedId,
219
228
  icons: icons
220
229
  )
221
- self.applyFloatingTabBarAppearance(tabBar: tabBar, options: call)
222
- let resolvedSelectedIndex = selectedIndex ?? (items.indices.contains(tabBar.selectedIndex) ? tabBar.selectedIndex : 0)
223
- tabBar.configure(
224
- items: items,
225
- selectedIndex: resolvedSelectedIndex,
226
- labelVisibilityMode: labelVisibilityMode,
227
- icons: icons,
228
- style: self.tabbarStyle
229
- )
230
- tabBar.onSelect = { [weak self] index, item in
231
- self?.notifyListeners("tabSelect", data: [
232
- "id": item.id,
233
- "index": index,
234
- "title": item.title
235
- ])
230
+ if items.isEmpty {
231
+ self.tabbarVisible = false
232
+ tabBar.configure(
233
+ items: [],
234
+ selectedIndex: 0,
235
+ labelVisibilityMode: labelVisibilityMode,
236
+ icons: icons,
237
+ style: self.tabbarStyle
238
+ )
239
+ self.tabContainer?.isHidden = true
240
+ tabBar.isHidden = true
241
+ } else {
242
+ self.applyFloatingTabBarAppearance(tabBar: tabBar, options: call)
243
+ let resolvedSelectedIndex = selectedIndex ?? (items.indices.contains(tabBar.selectedIndex) ? tabBar.selectedIndex : 0)
244
+ tabBar.configure(
245
+ items: items,
246
+ selectedIndex: resolvedSelectedIndex,
247
+ labelVisibilityMode: labelVisibilityMode,
248
+ icons: icons,
249
+ style: self.tabbarStyle
250
+ )
251
+ tabBar.onSelect = { [weak self] _, item in
252
+ self?.notifyListeners("tabSelect", data: [
253
+ "id": item.id,
254
+ "index": item.sourceIndex,
255
+ "title": item.title
256
+ ])
257
+ }
258
+ self.showFloatingTabBarChrome(tabBar)
236
259
  }
237
- self.showFloatingTabBarChrome(tabBar)
238
260
  }
239
261
  self.layoutChrome()
240
262
  self.updateInsetsAndNotify()
@@ -507,13 +529,15 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
507
529
  hostWebViewInSelectedSystemTab()
508
530
  return
509
531
  }
510
- let index = tabBarController.viewControllers?.firstIndex(of: viewController) ?? viewController.tabBarItem.tag
532
+ let index = viewController.tabBarItem.tag
511
533
  hostWebViewInSelectedSystemTab()
512
534
  notifyTabSelect(index: index)
513
535
  }
514
536
 
515
537
  private func notifyTabSelect(index: Int) {
516
- guard index >= 0 && index < tabIds.count else {
538
+ guard index >= 0,
539
+ index < tabIds.count,
540
+ !tabIds[index].isEmpty else {
517
541
  return
518
542
  }
519
543
  notifyListeners("tabSelect", data: [
@@ -723,17 +747,25 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
723
747
  let shouldAnimate = animated && tabBarController.viewControllers?.count == controllers.count
724
748
 
725
749
  suppressTabSelectEvent = true
750
+ defer { suppressTabSelectEvent = false }
751
+
752
+ guard !controllers.isEmpty else {
753
+ restoreWebViewFromSystemTabController()
754
+ if shouldUpdateControllers {
755
+ tabBarController.setViewControllers([], animated: false)
756
+ }
757
+ tabViewControllers = []
758
+ return
759
+ }
760
+
726
761
  if shouldUpdateControllers {
727
762
  tabBarController.setViewControllers(controllers, animated: shouldAnimate)
728
763
  }
729
- if !controllers.isEmpty {
730
- let fallbackIndex = selectedIndex ?? previousSelectedIndex
731
- let index = min(max(fallbackIndex, 0), controllers.count - 1)
732
- hostWebView(in: controllers[index])
733
- tabBarController.selectedIndex = index
734
- }
735
- suppressTabSelectEvent = false
736
764
 
765
+ let fallbackIndex = selectedIndex ?? previousSelectedIndex
766
+ let index = min(max(fallbackIndex, 0), controllers.count - 1)
767
+ hostWebView(in: controllers[index])
768
+ tabBarController.selectedIndex = index
737
769
  tabViewControllers = controllers
738
770
  }
739
771
 
@@ -948,63 +980,89 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
948
980
  ) -> ([UITabBarItem], Int?) {
949
981
  tabIds = []
950
982
  tabTitles = []
983
+ tabDisplayTitles = []
984
+ tabBaseImages = []
985
+ tabSelectedImages = []
951
986
  var selectedIndex: Int?
987
+ var items: [UITabBarItem] = []
988
+
989
+ for (sourceIndex, tab) in tabs.enumerated() {
990
+ let id = tab["id"] as? String ?? "tab-\(sourceIndex)"
991
+ let isHidden = tab["hidden"] as? Bool ?? false
992
+ if isHidden && id != selectedId {
993
+ continue
994
+ }
952
995
 
953
- let items = tabs.enumerated().map { index, tab -> UITabBarItem in
954
- let id = tab["id"] as? String ?? "tab-\(index)"
996
+ let visibleIndex = items.count
997
+ let rawTitle = tab["title"] as? String ?? ""
955
998
  let title = tabTitle(
956
- tab["title"] as? String,
999
+ rawTitle,
957
1000
  id: id,
958
- index: index,
1001
+ index: visibleIndex,
959
1002
  selectedId: selectedId,
960
1003
  labelVisibilityMode: labelVisibilityMode
961
1004
  )
962
1005
  let image = icons ? self.image(from: tab["icon"] as? [String: Any]) : nil
963
1006
  let selectedImage = icons ? self.image(from: tab["selectedIcon"] as? [String: Any]) : nil
964
1007
  let item = UITabBarItem(title: title, image: image, selectedImage: selectedImage)
965
- item.tag = index
1008
+ item.tag = sourceIndex
966
1009
  item.isEnabled = tab["enabled"] as? Bool ?? true
967
1010
  if let badge = tab["badge"] {
968
1011
  item.badgeValue = String(describing: badge)
969
1012
  }
970
- tabIds.append(id)
971
- tabTitles.append(tab["title"] as? String ?? "")
1013
+ while tabIds.count <= sourceIndex {
1014
+ tabIds.append("")
1015
+ tabTitles.append("")
1016
+ }
1017
+ tabIds[sourceIndex] = id
1018
+ tabTitles[sourceIndex] = rawTitle
1019
+ tabDisplayTitles.append(title)
1020
+ tabBaseImages.append(image)
1021
+ tabSelectedImages.append(selectedImage ?? image)
972
1022
  if id == selectedId {
973
- selectedIndex = index
1023
+ selectedIndex = visibleIndex
974
1024
  }
975
- return item
1025
+ items.append(item)
976
1026
  }
977
1027
 
978
1028
  return (items, selectedIndex)
979
1029
  }
980
-
981
1030
  private func makeFloatingTabBarItems(
982
1031
  _ tabs: [[String: Any]],
983
1032
  selectedId: String?,
984
1033
  icons: Bool
985
1034
  ) -> ([NativeNavigationFloatingTabItem], Int?) {
986
1035
  var selectedIndex: Int?
987
- let items = tabs.enumerated().map { index, tab -> NativeNavigationFloatingTabItem in
988
- let id = tab["id"] as? String ?? "tab-\(index)"
1036
+ var items: [NativeNavigationFloatingTabItem] = []
1037
+
1038
+ for (sourceIndex, tab) in tabs.enumerated() {
1039
+ let id = tab["id"] as? String ?? "tab-\(sourceIndex)"
1040
+ let isHidden = tab["hidden"] as? Bool ?? false
1041
+ if isHidden && id != selectedId {
1042
+ continue
1043
+ }
1044
+
1045
+ let visibleIndex = items.count
989
1046
  let title = tab["title"] as? String ?? ""
990
1047
  let image = icons ? self.image(from: tab["icon"] as? [String: Any]) : nil
991
1048
  let selectedImage = icons ? self.image(from: tab["selectedIcon"] as? [String: Any]) : nil
992
1049
  if id == selectedId {
993
- selectedIndex = index
1050
+ selectedIndex = visibleIndex
994
1051
  }
995
- return NativeNavigationFloatingTabItem(
1052
+ items.append(NativeNavigationFloatingTabItem(
996
1053
  id: id,
997
1054
  title: title,
998
1055
  accessibilityTitle: title.isEmpty ? id : title,
999
1056
  image: image,
1000
1057
  selectedImage: selectedImage,
1001
1058
  badge: tab["badge"].map { String(describing: $0) },
1002
- enabled: tab["enabled"] as? Bool ?? true
1003
- )
1059
+ enabled: tab["enabled"] as? Bool ?? true,
1060
+ sourceIndex: sourceIndex
1061
+ ))
1004
1062
  }
1063
+
1005
1064
  return (items, selectedIndex)
1006
1065
  }
1007
-
1008
1066
  private func tabTitle(
1009
1067
  _ title: String?,
1010
1068
  id: String,
@@ -1266,6 +1324,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
1266
1324
  item.scrollEdgeAppearance = scrollEdgeAppearance
1267
1325
  }
1268
1326
  }
1327
+ applyExperimentalBakedTintColors(tabBar: tabBar, options: call)
1269
1328
  return
1270
1329
  }
1271
1330
 
@@ -1318,7 +1377,7 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
1318
1377
  itemAppearance.selected.titleTextAttributes = [.foregroundColor: color]
1319
1378
  }
1320
1379
  }
1321
- if let color = colorValue(colors["inactiveTint"]) {
1380
+ if let color = colorValue(colors["inactiveTint"]), !usesSystemLiquidGlass {
1322
1381
  tabBar.unselectedItemTintColor = color
1323
1382
  applyTabItemAppearances(appearance) { itemAppearance in
1324
1383
  itemAppearance.normal.iconColor = color
@@ -1391,6 +1450,136 @@ public class NativeNavigationPlugin: CAPPlugin, CAPBridgedPlugin, UITabBarContro
1391
1450
  }
1392
1451
  }
1393
1452
 
1453
+ private func applyExperimentalBakedTintColors(tabBar: UITabBar, options call: CAPPluginCall) {
1454
+ guard shouldUseExperimentalBakedTintColors(options: call),
1455
+ let items = tabBar.items,
1456
+ let colors = call.getObject("colors") else {
1457
+ return
1458
+ }
1459
+
1460
+ let activeTint = colorValue(colors["tint"]) ?? tabBar.tintColor ?? .tintColor
1461
+ let inactiveTint = colorValue(colors["inactiveTint"]) ?? tabBar.unselectedItemTintColor ?? .secondaryLabel
1462
+ guard colorValue(colors["tint"]) != nil || colorValue(colors["inactiveTint"]) != nil else {
1463
+ return
1464
+ }
1465
+
1466
+ let labelVisibilityMode = tabLabelVisibilityMode(options: call)
1467
+ for (index, item) in items.enumerated() {
1468
+ let visibleTitle = tabDisplayTitles.indices.contains(index) ? (tabDisplayTitles[index] ?? "") : (item.title ?? "")
1469
+ let rawTitle = tabTitles.indices.contains(item.tag) ? tabTitles[item.tag] : visibleTitle
1470
+ let titles = bakedTintTitles(rawTitle: rawTitle, visibleTitle: visibleTitle, labelVisibilityMode: labelVisibilityMode)
1471
+ let icon = tabBaseImages.indices.contains(index) ? (tabBaseImages[index] ?? item.image) : item.image
1472
+ let selectedIcon = tabSelectedImages.indices.contains(index)
1473
+ ? (tabSelectedImages[index] ?? icon)
1474
+ : icon
1475
+ guard !titles.normal.isEmpty || !titles.selected.isEmpty || icon != nil || selectedIcon != nil else {
1476
+ continue
1477
+ }
1478
+
1479
+ let accessibilityTitle = rawTitle.isEmpty ? visibleTitle : rawTitle
1480
+ if !accessibilityTitle.isEmpty {
1481
+ item.accessibilityLabel = accessibilityTitle
1482
+ }
1483
+ item.title = ""
1484
+ item.titlePositionAdjustment = UIOffset(horizontal: 0, vertical: 100)
1485
+ item.image = makeTabBarItemImage(icon: icon, title: titles.normal, color: inactiveTint)
1486
+ item.selectedImage = makeTabBarItemImage(icon: selectedIcon, title: titles.selected, color: activeTint)
1487
+ }
1488
+ }
1489
+
1490
+ private func shouldUseExperimentalBakedTintColors(options call: CAPPluginCall) -> Bool {
1491
+ guard usesSystemLiquidGlass,
1492
+ call.getBool("experimentalBakedTintColors", false) else {
1493
+ return false
1494
+ }
1495
+
1496
+ return ["auto", "selected", "labeled", "unlabeled"].contains(tabLabelVisibilityMode(options: call))
1497
+ }
1498
+
1499
+ private func tabLabelVisibilityMode(options call: CAPPluginCall) -> String {
1500
+ let labels = call.getBool("labels", true)
1501
+ return call.getString("labelVisibilityMode") ?? (labels ? "labeled" : "unlabeled")
1502
+ }
1503
+
1504
+ private func bakedTintTitles(rawTitle: String, visibleTitle: String, labelVisibilityMode: String) -> (normal: String, selected: String) {
1505
+ switch labelVisibilityMode {
1506
+ case "unlabeled":
1507
+ return (normal: "", selected: "")
1508
+ case "selected":
1509
+ return (normal: "", selected: rawTitle)
1510
+ case "auto":
1511
+ let compact = bridge?.viewController?.traitCollection.horizontalSizeClass == .compact || UIDevice.current.userInterfaceIdiom == .phone
1512
+ return compact ? (normal: "", selected: rawTitle) : (normal: rawTitle, selected: rawTitle)
1513
+ case "labeled":
1514
+ return (normal: rawTitle, selected: rawTitle)
1515
+ default:
1516
+ return (normal: visibleTitle, selected: visibleTitle)
1517
+ }
1518
+ }
1519
+
1520
+ private func makeTabBarItemImage(icon: UIImage?, title: String, color: UIColor) -> UIImage {
1521
+ let iconSize = CGSize(width: 27, height: 27)
1522
+ let font = UIFont.systemFont(ofSize: 10, weight: .regular)
1523
+ let paragraphStyle = NSMutableParagraphStyle()
1524
+ paragraphStyle.alignment = .center
1525
+ let attributes: [NSAttributedString.Key: Any] = [
1526
+ .font: font,
1527
+ .foregroundColor: color,
1528
+ .paragraphStyle: paragraphStyle
1529
+ ]
1530
+ let hasTitle = !title.isEmpty
1531
+ let titleSize = hasTitle ? (title as NSString).size(withAttributes: attributes) : .zero
1532
+ let imageSize = hasTitle
1533
+ ? CGSize(width: max(iconSize.width, ceil(titleSize.width)) + 8, height: iconSize.height + 3 + ceil(titleSize.height))
1534
+ : iconSize
1535
+ let format = UIGraphicsImageRendererFormat()
1536
+ format.scale = UIScreen.main.scale
1537
+
1538
+ let image = UIGraphicsImageRenderer(size: imageSize, format: format).image { _ in
1539
+ if let icon {
1540
+ let tintedIcon = icon.withTintColor(color, renderingMode: .alwaysOriginal)
1541
+ let iconFrame = aspectFitRect(
1542
+ size: tintedIcon.size,
1543
+ in: CGRect(
1544
+ x: (imageSize.width - iconSize.width) / 2,
1545
+ y: 0,
1546
+ width: iconSize.width,
1547
+ height: iconSize.height
1548
+ )
1549
+ )
1550
+ tintedIcon.draw(in: iconFrame)
1551
+ }
1552
+ if hasTitle {
1553
+ (title as NSString).draw(
1554
+ in: CGRect(
1555
+ x: 0,
1556
+ y: iconSize.height + 3,
1557
+ width: imageSize.width,
1558
+ height: ceil(titleSize.height)
1559
+ ),
1560
+ withAttributes: attributes
1561
+ )
1562
+ }
1563
+ }
1564
+
1565
+ return image.withRenderingMode(.alwaysOriginal)
1566
+ }
1567
+
1568
+ private func aspectFitRect(size: CGSize, in rect: CGRect) -> CGRect {
1569
+ guard size.width > 0, size.height > 0 else {
1570
+ return rect
1571
+ }
1572
+
1573
+ let scale = min(rect.width / size.width, rect.height / size.height)
1574
+ let fittedSize = CGSize(width: size.width * scale, height: size.height * scale)
1575
+ return CGRect(
1576
+ x: rect.minX + (rect.width - fittedSize.width) / 2,
1577
+ y: rect.minY + (rect.height - fittedSize.height) / 2,
1578
+ width: fittedSize.width,
1579
+ height: fittedSize.height
1580
+ )
1581
+ }
1582
+
1394
1583
  private func applyTabItemAppearances(
1395
1584
  _ appearance: UITabBarAppearance,
1396
1585
  update: (UITabBarItemAppearance) -> Void
@@ -1641,7 +1830,6 @@ private struct SVGRenderStyle {
1641
1830
  }
1642
1831
  }
1643
1832
  }
1644
-
1645
1833
  private struct NativeNavigationFloatingTabItem {
1646
1834
  let id: String
1647
1835
  let title: String
@@ -1650,6 +1838,7 @@ private struct NativeNavigationFloatingTabItem {
1650
1838
  let selectedImage: UIImage?
1651
1839
  let badge: String?
1652
1840
  let enabled: Bool
1841
+ let sourceIndex: Int
1653
1842
  }
1654
1843
 
1655
1844
  private enum NativeNavigationTabbarShape {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-native-navigation",
3
- "version": "8.2.1",
3
+ "version": "8.2.3",
4
4
  "description": "Capacitor plugin for native navbar, tabbar, safe-area handling, and WebView snapshot transitions.",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",