@ajuarezso/capacitor-liquid-glass 0.2.0 → 0.3.1
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.
|
@@ -16,8 +16,11 @@ export interface LiquidGlassTabItem {
|
|
|
16
16
|
* more see-through than default.
|
|
17
17
|
* - `'transparent'`: no background, no blur — content behind shows through 100%.
|
|
18
18
|
* Trade-off: legibilidad puede sufrir sobre contenido caótico.
|
|
19
|
+
* - `'liquidGlass'`: REAL iOS 26 `UIGlassEffect` — el mismo material que usan
|
|
20
|
+
* Music y App Store. En iOS < 26 cae a `UIBlurEffect.systemThinMaterial`
|
|
21
|
+
* (aproximación cercana con system vibrancy).
|
|
19
22
|
*/
|
|
20
|
-
export type TabBarStyle = 'default' | 'ultraThin' | 'transparent';
|
|
23
|
+
export type TabBarStyle = 'default' | 'ultraThin' | 'transparent' | 'liquidGlass';
|
|
21
24
|
export interface ShowTabBarOptions {
|
|
22
25
|
items: LiquidGlassTabItem[];
|
|
23
26
|
/** Index of the initially selected item. Defaults to 0. */
|
|
@@ -29,6 +29,9 @@ enum LiquidGlassTabBarStyle: String {
|
|
|
29
29
|
case ultraThin
|
|
30
30
|
/// No background, no blur — content behind shows through 100%.
|
|
31
31
|
case transparent
|
|
32
|
+
/// REAL iOS 26 `UIGlassEffect` (Music + App Store material).
|
|
33
|
+
/// Fallback en iOS < 26 → `UIBlurEffect.systemThinMaterial`.
|
|
34
|
+
case liquidGlass
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
/// A floating UITabBar overlay that adopts iOS 26 Liquid Glass automatically
|
|
@@ -44,6 +47,11 @@ final class LiquidGlassTabBarOverlay: NSObject {
|
|
|
44
47
|
private weak var window: UIWindow?
|
|
45
48
|
private var tabBar: UITabBar?
|
|
46
49
|
private var items: [LiquidGlassTabItem] = []
|
|
50
|
+
/// Overlay `UIVisualEffectView` que provee el material cuando el style es
|
|
51
|
+
/// `.liquidGlass`. Solo se monta cuando el estilo lo requiere, y se
|
|
52
|
+
/// destruye al cambiar a otro estilo. Vive como subview del window justo
|
|
53
|
+
/// DEBAJO del `tabBar` (que en este modo queda 100% transparente).
|
|
54
|
+
private var glassOverlayView: UIVisualEffectView?
|
|
47
55
|
|
|
48
56
|
func attach(to window: UIWindow) {
|
|
49
57
|
if self.window === window, tabBar != nil { return }
|
|
@@ -73,20 +81,36 @@ final class LiquidGlassTabBarOverlay: NSObject {
|
|
|
73
81
|
/// - `default`: Liquid Glass (iOS 26+) o blur translúcido como fallback.
|
|
74
82
|
/// - `ultraThin`: blur mínimo `systemUltraThinMaterial` — más ver-through.
|
|
75
83
|
/// - `transparent`: sin background ni blur — content behind se ve completo.
|
|
84
|
+
/// - `liquidGlass`: tab bar 100% transparente + overlay `UIVisualEffectView`
|
|
85
|
+
/// con `UIGlassEffect` (iOS 26+) o `systemThinMaterial` fallback.
|
|
76
86
|
private func applyAppearance(_ tabBar: UITabBar, style: LiquidGlassTabBarStyle) {
|
|
77
87
|
let appearance = UITabBarAppearance()
|
|
78
88
|
switch style {
|
|
79
89
|
case .default:
|
|
80
90
|
appearance.configureWithDefaultBackground()
|
|
91
|
+
removeGlassOverlay()
|
|
81
92
|
case .ultraThin:
|
|
82
93
|
appearance.configureWithDefaultBackground()
|
|
83
94
|
appearance.backgroundEffect = UIBlurEffect(style: .systemUltraThinMaterial)
|
|
84
95
|
appearance.backgroundColor = UIColor.clear
|
|
96
|
+
removeGlassOverlay()
|
|
85
97
|
case .transparent:
|
|
86
98
|
appearance.configureWithTransparentBackground()
|
|
87
99
|
appearance.backgroundEffect = nil
|
|
88
100
|
appearance.backgroundColor = UIColor.clear
|
|
89
101
|
appearance.shadowColor = UIColor.clear
|
|
102
|
+
removeGlassOverlay()
|
|
103
|
+
case .liquidGlass:
|
|
104
|
+
// Tab bar 100% transparente — el material lo aporta el overlay
|
|
105
|
+
// `UIVisualEffectView` con `UIGlassEffect` montado debajo del bar.
|
|
106
|
+
// Es el patrón canon de iOS 26 para aplicar Liquid Glass real a
|
|
107
|
+
// controles que no exponen API directa para asignarlo (UITabBar
|
|
108
|
+
// solo acepta `UIBlurEffect` en `backgroundEffect`).
|
|
109
|
+
appearance.configureWithTransparentBackground()
|
|
110
|
+
appearance.backgroundEffect = nil
|
|
111
|
+
appearance.backgroundColor = UIColor.clear
|
|
112
|
+
appearance.shadowColor = UIColor.clear
|
|
113
|
+
installGlassOverlay(behind: tabBar)
|
|
90
114
|
}
|
|
91
115
|
tabBar.standardAppearance = appearance
|
|
92
116
|
if #available(iOS 15.0, *) {
|
|
@@ -94,6 +118,50 @@ final class LiquidGlassTabBarOverlay: NSObject {
|
|
|
94
118
|
}
|
|
95
119
|
}
|
|
96
120
|
|
|
121
|
+
/// Monta un `UIVisualEffectView` con `UIGlassEffect` (iOS 26+) o
|
|
122
|
+
/// `systemThinMaterial` como fallback, justo DEBAJO del `tabBar` en el
|
|
123
|
+
/// z-order del window. `tabBar` está en modo transparente, así que todo
|
|
124
|
+
/// el material visible viene del overlay.
|
|
125
|
+
///
|
|
126
|
+
/// El overlay queda con `isUserInteractionEnabled = false` para no
|
|
127
|
+
/// interferir con los taps que el `tabBar` consume por encima.
|
|
128
|
+
private func installGlassOverlay(behind tabBar: UITabBar) {
|
|
129
|
+
guard let parent = tabBar.superview else { return }
|
|
130
|
+
// Idempotente: si ya existe overlay no recrearlo (evita flicker en
|
|
131
|
+
// re-configs por badge updates u otros cambios sin style change).
|
|
132
|
+
if glassOverlayView != nil { return }
|
|
133
|
+
|
|
134
|
+
let effect: UIVisualEffect
|
|
135
|
+
#if compiler(>=6.1)
|
|
136
|
+
if #available(iOS 26.0, *) {
|
|
137
|
+
effect = UIGlassEffect()
|
|
138
|
+
} else {
|
|
139
|
+
effect = UIBlurEffect(style: .systemThinMaterial)
|
|
140
|
+
}
|
|
141
|
+
#else
|
|
142
|
+
effect = UIBlurEffect(style: .systemThinMaterial)
|
|
143
|
+
#endif
|
|
144
|
+
|
|
145
|
+
let overlay = UIVisualEffectView(effect: effect)
|
|
146
|
+
overlay.translatesAutoresizingMaskIntoConstraints = false
|
|
147
|
+
overlay.isUserInteractionEnabled = false
|
|
148
|
+
parent.insertSubview(overlay, belowSubview: tabBar)
|
|
149
|
+
|
|
150
|
+
NSLayoutConstraint.activate([
|
|
151
|
+
overlay.leadingAnchor.constraint(equalTo: tabBar.leadingAnchor),
|
|
152
|
+
overlay.trailingAnchor.constraint(equalTo: tabBar.trailingAnchor),
|
|
153
|
+
overlay.topAnchor.constraint(equalTo: tabBar.topAnchor),
|
|
154
|
+
overlay.bottomAnchor.constraint(equalTo: tabBar.bottomAnchor),
|
|
155
|
+
])
|
|
156
|
+
|
|
157
|
+
self.glassOverlayView = overlay
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private func removeGlassOverlay() {
|
|
161
|
+
glassOverlayView?.removeFromSuperview()
|
|
162
|
+
glassOverlayView = nil
|
|
163
|
+
}
|
|
164
|
+
|
|
97
165
|
func configure(items: [LiquidGlassTabItem], selectedIndex: Int, tintHex: String?, style: LiquidGlassTabBarStyle) {
|
|
98
166
|
guard let tabBar else { return }
|
|
99
167
|
|
|
@@ -153,6 +221,12 @@ final class LiquidGlassTabBarOverlay: NSObject {
|
|
|
153
221
|
tabBar.isHidden = false
|
|
154
222
|
tabBar.alpha = 0
|
|
155
223
|
tabBar.transform = CGAffineTransform(translationX: 0, y: 20)
|
|
224
|
+
// Sincronizar el overlay glass (si está montado) con la misma curva,
|
|
225
|
+
// sino el material queda visible mientras el bar fade-out/in.
|
|
226
|
+
let overlay = glassOverlayView
|
|
227
|
+
overlay?.isHidden = false
|
|
228
|
+
overlay?.alpha = 0
|
|
229
|
+
overlay?.transform = CGAffineTransform(translationX: 0, y: 20)
|
|
156
230
|
// Fade-in + slide-up (curva native iOS para apariciones)
|
|
157
231
|
UIView.animate(
|
|
158
232
|
withDuration: 0.28,
|
|
@@ -163,6 +237,8 @@ final class LiquidGlassTabBarOverlay: NSObject {
|
|
|
163
237
|
animations: {
|
|
164
238
|
tabBar.alpha = 1
|
|
165
239
|
tabBar.transform = .identity
|
|
240
|
+
overlay?.alpha = 1
|
|
241
|
+
overlay?.transform = .identity
|
|
166
242
|
},
|
|
167
243
|
completion: nil
|
|
168
244
|
)
|
|
@@ -173,6 +249,7 @@ final class LiquidGlassTabBarOverlay: NSObject {
|
|
|
173
249
|
guard let tabBar = tabBar else { return }
|
|
174
250
|
if tabBar.isHidden { return }
|
|
175
251
|
// Fade-out + slide-down (más rápido que el show — el exit es snappy)
|
|
252
|
+
let overlay = glassOverlayView
|
|
176
253
|
UIView.animate(
|
|
177
254
|
withDuration: 0.18,
|
|
178
255
|
delay: 0,
|
|
@@ -180,10 +257,14 @@ final class LiquidGlassTabBarOverlay: NSObject {
|
|
|
180
257
|
animations: {
|
|
181
258
|
tabBar.alpha = 0
|
|
182
259
|
tabBar.transform = CGAffineTransform(translationX: 0, y: 20)
|
|
260
|
+
overlay?.alpha = 0
|
|
261
|
+
overlay?.transform = CGAffineTransform(translationX: 0, y: 20)
|
|
183
262
|
},
|
|
184
263
|
completion: { _ in
|
|
185
264
|
tabBar.isHidden = true
|
|
186
265
|
tabBar.transform = .identity
|
|
266
|
+
overlay?.isHidden = true
|
|
267
|
+
overlay?.transform = .identity
|
|
187
268
|
}
|
|
188
269
|
)
|
|
189
270
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ajuarezso/capacitor-liquid-glass",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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",
|