@ajuarezso/capacitor-liquid-glass 0.3.4 → 0.3.6
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.
|
@@ -152,7 +152,12 @@ public class LiquidGlassPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
private func presentTabBar(items: [LiquidGlassTabItem], selectedIndex: Int, tintHex: String?, styleRaw: String) {
|
|
155
|
-
|
|
155
|
+
// CRÍTICO: usar `bridge?.viewController` (el VC que contiene el
|
|
156
|
+
// WKWebView de Capacitor) en lugar del `rootViewController` del
|
|
157
|
+
// window. iOS 26 aplica Liquid Glass automáticamente al UITabBar
|
|
158
|
+
// solo cuando está en la jerarquía del VC del webview. El rootVC
|
|
159
|
+
// del window puede ser un container distinto y romper el adopt.
|
|
160
|
+
guard let hostVC = bridge?.viewController else { return }
|
|
156
161
|
|
|
157
162
|
if tabBarOverlay == nil {
|
|
158
163
|
let overlay = LiquidGlassTabBarOverlay()
|
|
@@ -169,7 +174,7 @@ public class LiquidGlassPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
169
174
|
}
|
|
170
175
|
|
|
171
176
|
let style = LiquidGlassTabBarStyle(rawValue: styleRaw) ?? .default
|
|
172
|
-
tabBarOverlay?.attach(to:
|
|
177
|
+
tabBarOverlay?.attach(to: hostVC)
|
|
173
178
|
tabBarOverlay?.configure(items: items, selectedIndex: selectedIndex, tintHex: tintHex, style: style)
|
|
174
179
|
tabBarOverlay?.show()
|
|
175
180
|
}
|
|
@@ -42,14 +42,18 @@ enum LiquidGlassTabBarStyle: String {
|
|
|
42
42
|
/// app se compila contra el SDK iOS 26.
|
|
43
43
|
///
|
|
44
44
|
/// **Decisión arquitectónica crítica**: este view controller se adjunta como
|
|
45
|
-
/// CHILD del
|
|
46
|
-
///
|
|
47
|
-
///
|
|
45
|
+
/// CHILD del view controller que contiene el WKWebView de Capacitor
|
|
46
|
+
/// (`bridge?.viewController`), y el `UITabBar` vive como subview de
|
|
47
|
+
/// `self.view`. Esto crea el view controller hierarchy completo que iOS 26
|
|
48
|
+
/// necesita para aplicar el material Liquid Glass automáticamente al
|
|
49
|
+
/// UITabBar.
|
|
48
50
|
///
|
|
49
|
-
///
|
|
50
|
-
///
|
|
51
|
-
///
|
|
52
|
-
///
|
|
51
|
+
/// Por qué NO usar `window.rootViewController`: en Capacitor el rootVC del
|
|
52
|
+
/// window puede ser un container distinto al VC del webview. iOS 26 solo
|
|
53
|
+
/// aplica Liquid Glass auto cuando el UITabBar vive en la jerarquía del VC
|
|
54
|
+
/// que tiene el webview activo. Agregarlo al rootVC del window puede
|
|
55
|
+
/// romper el auto-adopt y dejarlo con material translúcido genérico opaco
|
|
56
|
+
/// (no el real de Music / App Store).
|
|
53
57
|
///
|
|
54
58
|
/// Patrón inspirado en `stay-liquid` (alistairheath/stay-liquid GitHub).
|
|
55
59
|
final class LiquidGlassTabBarOverlay: UIViewController {
|
|
@@ -66,7 +70,7 @@ final class LiquidGlassTabBarOverlay: UIViewController {
|
|
|
66
70
|
override func viewDidLoad() {
|
|
67
71
|
super.viewDidLoad()
|
|
68
72
|
// Background transparente. El `self.view` está sizado SOLO al rect
|
|
69
|
-
// del tab bar (leading/trailing/bottom al
|
|
73
|
+
// del tab bar (leading/trailing/bottom al hostVC, sin top — la
|
|
70
74
|
// altura la determina el intrinsic content size del UITabBar dentro).
|
|
71
75
|
// Por eso NO necesitamos `hitTest` override: el view en sí solo
|
|
72
76
|
// existe sobre el área de la pill, los taps fuera no llegan.
|
|
@@ -75,9 +79,13 @@ final class LiquidGlassTabBarOverlay: UIViewController {
|
|
|
75
79
|
tabBar.translatesAutoresizingMaskIntoConstraints = false
|
|
76
80
|
tabBar.delegate = self
|
|
77
81
|
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
|
|
82
|
+
// CRÍTICO: NO setear appearance acá. iOS 26 adopta Liquid Glass
|
|
83
|
+
// automáticamente SOLO si el UITabBar mantiene su appearance default
|
|
84
|
+
// intacto. Tocar `standardAppearance` / `scrollEdgeAppearance` en
|
|
85
|
+
// CUALQUIER forma — incluso con `configureWithDefaultBackground()` —
|
|
86
|
+
// anula el adopt y deja un material translúcido genérico opaco.
|
|
87
|
+
// Solo los overrides intencionales (.ultraThin, .transparent) tocan
|
|
88
|
+
// el appearance, vía `applyAppearance(...)` desde `configure(...)`.
|
|
81
89
|
|
|
82
90
|
view.addSubview(tabBar)
|
|
83
91
|
|
|
@@ -91,12 +99,13 @@ final class LiquidGlassTabBarOverlay: UIViewController {
|
|
|
91
99
|
])
|
|
92
100
|
}
|
|
93
101
|
|
|
94
|
-
/// Adjunta este view controller como child del
|
|
95
|
-
///
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
102
|
+
/// Adjunta este view controller como child del view controller que
|
|
103
|
+
/// contiene el WKWebView de Capacitor (`bridge?.viewController`).
|
|
104
|
+
/// Idempotente: si ya está adjuntado al mismo host, no-op.
|
|
105
|
+
func attach(to hostVC: UIViewController?) {
|
|
106
|
+
guard let hostVC else { return }
|
|
107
|
+
if self.parent === hostVC { return }
|
|
108
|
+
// Si está adjuntado a otro VC (cambio de host entre sesiones),
|
|
100
109
|
// detachar primero antes de re-adjuntar.
|
|
101
110
|
if self.parent != nil {
|
|
102
111
|
self.willMove(toParent: nil)
|
|
@@ -104,37 +113,45 @@ final class LiquidGlassTabBarOverlay: UIViewController {
|
|
|
104
113
|
self.removeFromParent()
|
|
105
114
|
}
|
|
106
115
|
|
|
107
|
-
|
|
116
|
+
hostVC.addChild(self)
|
|
117
|
+
hostVC.view.addSubview(self.view)
|
|
108
118
|
self.view.translatesAutoresizingMaskIntoConstraints = false
|
|
109
|
-
|
|
119
|
+
// CRÍTICO: `didMove(toParent:)` ANTES de activar constraints. iOS 26
|
|
120
|
+
// ejecuta el primer layout pass al didMove; tener el parenting
|
|
121
|
+
// completo antes de que el sistema mida el UITabBar es lo que el
|
|
122
|
+
// heurístico de Liquid Glass auto-adopt espera (patrón stay-liquid).
|
|
123
|
+
self.didMove(toParent: hostVC)
|
|
110
124
|
// SIN top constraint — la altura del view se deriva del intrinsic
|
|
111
125
|
// content size del UITabBar dentro (~50pt + safe-area-bottom).
|
|
112
|
-
// Replica del patrón stay-liquid (validado en producción) que
|
|
113
|
-
// permite a iOS 26 detectar este UITabBar como "floating tab bar"
|
|
114
|
-
// y aplicarle Liquid Glass automáticamente.
|
|
115
126
|
NSLayoutConstraint.activate([
|
|
116
|
-
self.view.leadingAnchor.constraint(equalTo:
|
|
117
|
-
self.view.trailingAnchor.constraint(equalTo:
|
|
118
|
-
self.view.bottomAnchor.constraint(equalTo:
|
|
127
|
+
self.view.leadingAnchor.constraint(equalTo: hostVC.view.leadingAnchor),
|
|
128
|
+
self.view.trailingAnchor.constraint(equalTo: hostVC.view.trailingAnchor),
|
|
129
|
+
self.view.bottomAnchor.constraint(equalTo: hostVC.view.bottomAnchor),
|
|
119
130
|
])
|
|
120
|
-
self.
|
|
121
|
-
self.hostVC = rootVC
|
|
131
|
+
self.hostVC = hostVC
|
|
122
132
|
emitLayout()
|
|
123
133
|
}
|
|
124
134
|
|
|
125
135
|
/// Aplica el appearance correspondiente al estilo solicitado.
|
|
126
|
-
/// - `default` / `liquidGlass`:
|
|
127
|
-
///
|
|
136
|
+
/// - `default` / `liquidGlass`: **NO-OP** — el UITabBar mantiene su
|
|
137
|
+
/// appearance default sin tocar para que iOS 26 aplique Liquid Glass
|
|
138
|
+
/// automáticamente. Cualquier touch al appearance (incluso
|
|
139
|
+
/// `configureWithDefaultBackground()`) rompe el auto-adopt.
|
|
128
140
|
/// - `ultraThin`: blur mínimo `systemUltraThinMaterial` — más ver-through.
|
|
141
|
+
/// Override intencional que CANCELA Liquid Glass.
|
|
129
142
|
/// - `transparent`: sin background ni blur — content behind se ve completo.
|
|
143
|
+
/// Override intencional que CANCELA Liquid Glass.
|
|
130
144
|
private func applyAppearance(_ tabBar: UITabBar, style: LiquidGlassTabBarStyle) {
|
|
145
|
+
// Early-return para los estilos que confían en el adopt automático
|
|
146
|
+
// del sistema. Tocar appearance acá anula Liquid Glass.
|
|
147
|
+
if style == .default || style == .liquidGlass { return }
|
|
148
|
+
|
|
131
149
|
let appearance = UITabBarAppearance()
|
|
132
150
|
switch style {
|
|
133
151
|
case .default, .liquidGlass:
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
appearance.configureWithDefaultBackground()
|
|
152
|
+
// Unreachable — early-return arriba. Mantenido por exhaustividad
|
|
153
|
+
// del switch sobre el enum.
|
|
154
|
+
return
|
|
138
155
|
case .ultraThin:
|
|
139
156
|
appearance.configureWithDefaultBackground()
|
|
140
157
|
appearance.backgroundEffect = UIBlurEffect(style: .systemUltraThinMaterial)
|
|
@@ -174,7 +191,12 @@ final class LiquidGlassTabBarOverlay: UIViewController {
|
|
|
174
191
|
return tab
|
|
175
192
|
}
|
|
176
193
|
|
|
177
|
-
tabBar.
|
|
194
|
+
// CRÍTICO: setter directo `tabBar.items = ...` en lugar de
|
|
195
|
+
// `tabBar.setItems(_, animated: false)`. En iOS 26 el método
|
|
196
|
+
// `setItems(animated:)` toma un path interno distinto que puede
|
|
197
|
+
// invalidar el adopt automático de Liquid Glass. El setter directo
|
|
198
|
+
// es el que stay-liquid usa (línea 98 de su TabsBarOverlay).
|
|
199
|
+
tabBar.items = uiItems
|
|
178
200
|
|
|
179
201
|
if selectedIndex < 0 {
|
|
180
202
|
tabBar.selectedItem = nil
|
|
@@ -198,41 +220,23 @@ final class LiquidGlassTabBarOverlay: UIViewController {
|
|
|
198
220
|
tabBarItems[idx].badgeValue = value
|
|
199
221
|
}
|
|
200
222
|
|
|
223
|
+
/// CRÍTICO: NO tocar `tabBar.alpha`, `tabBar.transform` ni `tabBar.isHidden`.
|
|
224
|
+
/// iOS 26 inspecciona el UITabBar en el primer layout pass para decidir si
|
|
225
|
+
/// adopta Liquid Glass. Si en ese momento el bar está con `alpha=0` o un
|
|
226
|
+
/// `transform` distinto de `.identity`, el sistema descarta el adopt y NO
|
|
227
|
+
/// re-evalúa aunque después vuelva a valores normales.
|
|
228
|
+
///
|
|
229
|
+
/// Para animar la aparición/desaparición sin perder el adopt, se manipula
|
|
230
|
+
/// `self.view.isHidden` (el contenedor del VC), no el UITabBar mismo.
|
|
231
|
+
/// Patrón validado en stay-liquid (su `update()` simplemente hace
|
|
232
|
+
/// `view.isHidden = !visible`).
|
|
201
233
|
func show() {
|
|
202
|
-
|
|
203
|
-
tabBar.isHidden = false
|
|
204
|
-
tabBar.alpha = 0
|
|
205
|
-
tabBar.transform = CGAffineTransform(translationX: 0, y: 20)
|
|
206
|
-
UIView.animate(
|
|
207
|
-
withDuration: 0.28,
|
|
208
|
-
delay: 0,
|
|
209
|
-
usingSpringWithDamping: 0.85,
|
|
210
|
-
initialSpringVelocity: 0,
|
|
211
|
-
options: [.curveEaseOut, .allowUserInteraction],
|
|
212
|
-
animations: {
|
|
213
|
-
self.tabBar.alpha = 1
|
|
214
|
-
self.tabBar.transform = .identity
|
|
215
|
-
},
|
|
216
|
-
completion: nil
|
|
217
|
-
)
|
|
234
|
+
view.isHidden = false
|
|
218
235
|
emitLayout()
|
|
219
236
|
}
|
|
220
237
|
|
|
221
238
|
func hide() {
|
|
222
|
-
|
|
223
|
-
UIView.animate(
|
|
224
|
-
withDuration: 0.18,
|
|
225
|
-
delay: 0,
|
|
226
|
-
options: [.curveEaseIn, .allowUserInteraction],
|
|
227
|
-
animations: {
|
|
228
|
-
self.tabBar.alpha = 0
|
|
229
|
-
self.tabBar.transform = CGAffineTransform(translationX: 0, y: 20)
|
|
230
|
-
},
|
|
231
|
-
completion: { _ in
|
|
232
|
-
self.tabBar.isHidden = true
|
|
233
|
-
self.tabBar.transform = .identity
|
|
234
|
-
}
|
|
235
|
-
)
|
|
239
|
+
view.isHidden = true
|
|
236
240
|
}
|
|
237
241
|
|
|
238
242
|
func setSelectedIndex(_ index: Int) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ajuarezso/capacitor-liquid-glass",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "iOS 26 Liquid Glass native chrome (TabBar, NavigationBar, Alerts, Sheets) for Capacitor apps. Falls back gracefully on iOS < 26 and Android.",
|
|
5
5
|
"main": "dist/plugin.cjs.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|