Вступ
iOS 26 приніс, мабуть, найбільшу візуальну зміну в екосистемі Apple з часів iOS 7 — дизайн-систему Liquid Glass. І це не просто черговий ефект прозорості, яких ми бачили чимало. Liquid Glass — динамічний матеріал, який заломлює та відбиває вміст під ним, реагує на рух пристрою, адаптується до освітлення і створює справжнє відчуття глибини між елементами навігації та контентом.
Apple представила Liquid Glass на WWDC 2025, і ця мова дизайну охоплює всі платформи: iOS 26, iPadOS 26, macOS Tahoe, watchOS 26, tvOS 26 та visionOS 26. Для нас, SwiftUI-розробників, це нові API, нові патерни побудови інтерфейсу та, чесно кажучи, нові підходи до візуальної ієрархії, які варто опанувати якомога раніше.
У цьому посібнику ми крок за кроком розберемо все, що потрібно знати про Liquid Glass: від базового модифікатора glassEffect до складних сценаріїв з контейнерами, морфінг-анімаціями, tab bar і навігацією. На кожному етапі — робочі приклади коду, які можна скопіювати у свій проєкт.
Що таке Liquid Glass і коли його використовувати
Liquid Glass — це не просте розмиття фону на кшталт .ultraThinMaterial зі старих версій SwiftUI. Це повноцінний динамічний матеріал із кількома ключовими характеристиками:
- Лінзування (lensing) — заломлення світла у реальному часі, яке спотворює контент під скляним елементом
- Блікування (specular highlights) — відблиски, що реагують на нахил пристрою
- Адаптивні тіні — автоматичне налаштування тіней під навколишній контент
- Інтерактивність — реакція на дотик, перетягування та жести
Коли я вперше побачив це в дії, чесно, був вражений рівнем деталізації. Але є важливі правила.
Принцип дизайн-ієрархії
Головне правило Apple: Liquid Glass призначений виключно для шару навігації, який «плаває» над основним контентом. Ніколи не застосовуйте скляний ефект до самого контенту — списків, таблиць, зображень, медіа.
Правильне використання:
- Панелі навігації та тулбари
- Tab bar та його аксесуари
- Плаваючі кнопки дій (floating action buttons)
- Sheets, popovers, контекстні меню
- Системні сповіщення та контроли
Неправильне використання — це, наприклад, додавання glassEffect до кожного рядка списку. Результат буде виглядати дивно і перевантажено, адже скло не може коректно «зчитувати» інше скло під собою. Повірте, я спробував — і швидко повернув все назад.
Базовий API: модифікатор glassEffect
Отже, переходимо до коду. Основний спосіб додати Liquid Glass до будь-якого view — модифікатор glassEffect(_:in:isEnabled:). Його повна сигнатура:
func glassEffect<S: Shape>(
_ glass: Glass = .regular,
in shape: S = DefaultGlassEffectShape,
isEnabled: Bool = true
) -> some View
Найпростіший приклад:
import SwiftUI
struct BasicGlassView: View {
var body: some View {
Text("Привіт, Liquid Glass!")
.padding()
.glassEffect()
}
}
Усього один рядок — і ви отримуєте скляний ефект. За замовчуванням використовується варіант .regular та форма капсули (Capsule). SwiftUI автоматично застосовує яскравий колір тексту для збереження читабельності на кольоровому фоні.
Три варіанти Glass
Структура Glass надає три статичні варіанти, і кожен має своє призначення:
.regular— стандартний скляний ефект з повним лінзуванням та відблисками. Використовуйте для більшості елементів інтерфейсу.clear— прозоріший варіант з мінімальним спотворенням. Підходить для фонових панелей, де контент під склом має залишатися читабельним.identity— «нульовий» ефект, який фактично вимикає скло. Ідеально підходить для умовного вимкнення ефекту без зміни структури view
struct GlassVariantsView: View {
@State private var isGlassEnabled = true
var body: some View {
VStack(spacing: 20) {
Text("Regular")
.padding()
.glassEffect(.regular)
Text("Clear")
.padding()
.glassEffect(.clear)
Text("Умовне скло")
.padding()
.glassEffect(isGlassEnabled ? .regular : .identity)
}
}
}
Зверніть увагу на трюк з .identity — це набагато елегантніше, ніж обгортати view в if-else.
Кастомні форми
За замовчуванням скляний ефект має форму капсули, але ви можете задати будь-яку форму через параметр in::
// Закруглений прямокутник
Text("Кнопка")
.padding()
.glassEffect(.regular, in: RoundedRectangle(cornerRadius: 12))
// Коло
Image(systemName: "plus")
.padding()
.glassEffect(.regular, in: Circle())
// Прямокутник
Text("Банер")
.padding()
.glassEffect(.regular, in: Rectangle())
Тонування та інтерактивність
Liquid Glass підтримує два важливі модифікатори через методи структури Glass: тонування кольором та інтерактивність. Давайте розберемо обидва.
Тонування (.tint)
Метод .tint(_:) змішує колір зі скляним ефектом. Важливий момент: тонування слід використовувати для передачі семантичного значення (основна дія, стан, попередження), а не просто для декору.
VStack(spacing: 16) {
// Основна дія
Button("Зберегти") { }
.padding()
.glassEffect(.regular.tint(.blue))
// Небезпечна дія
Button("Видалити") { }
.padding()
.glassEffect(.regular.tint(.red))
// Тонування з прозорістю
Text("Інформація")
.padding()
.glassEffect(.regular.tint(.purple.opacity(0.6)))
}
Інтерактивність (.interactive)
Метод .interactive() робить скляний ефект більш «агресивним» до фонового контенту та додає реакцію на жести. За замовчуванням скляні елементи просто відображають контент за ними, але з .interactive() вони помітно реагують на взаємодію користувача.
Button(action: { }) {
Label("Відтворити", systemImage: "play.fill")
.padding()
}
.glassEffect(.regular.interactive())
На практиці різниця відчутна — елемент ніби «оживає» під пальцем.
GlassEffectContainer: координація скляних елементів
А тепер одна з найважливіших концепцій. Коли у вашому інтерфейсі кілька скляних елементів поруч, вони можуть виглядати «окремо» один від одного — як розрізнені шматочки скла. GlassEffectContainer вирішує цю проблему, об'єднуючи скляні фігури в єдину групу:
- Автоматично змішує сусідні скляні форми
- Забезпечує консистентний ефект розмиття та освітлення
- Увімкнює плавні морфінг-переходи між елементами
- Покращує продуктивність рендерингу (і це приємний бонус)
struct GlassContainerExample: View {
var body: some View {
GlassEffectContainer {
HStack(spacing: 12) {
Button("Головна") { }
.padding(.horizontal, 16)
.padding(.vertical, 10)
.glassEffect()
Button("Налаштування") { }
.padding(.horizontal, 16)
.padding(.vertical, 10)
.glassEffect()
Button("Профіль") { }
.padding(.horizontal, 16)
.padding(.vertical, 10)
.glassEffect()
}
.padding()
}
}
}
Параметр spacing: поріг морфінгу
Контейнер має опціональний параметр spacing, який визначає поріг морфінгу — мінімальну відстань, на якій елементи починають візуально зливатися. Елементи ближче за цю відстань зливатимуться в єдину скляну фігуру.
// Елементи зливаються на відстані 8 точок
GlassEffectContainer(spacing: 8) {
HStack(spacing: 4) {
ForEach(0..<3) { index in
Button("Tab \(index)") { }
.padding()
.glassEffect()
}
}
}
Погратися зі значенням spacing варто — ефект злиття виглядає дуже круто, коли відстань підібрана правильно.
Морфінг-анімації з glassEffectID
Чесно кажучи, це моя улюблена частина Liquid Glass. Морфінг-анімації дозволяють скляним елементам плавно трансформуватися між станами, і виглядає це неймовірно. Для реалізації використовується модифікатор .glassEffectID(_:in:) разом із @Namespace.
struct MorphingExample: View {
@State private var isExpanded = false
@Namespace private var glassNamespace
var body: some View {
GlassEffectContainer {
VStack {
if isExpanded {
VStack(spacing: 12) {
Button("Камера") { }
.padding()
.glassEffect()
.glassEffectID("camera", in: glassNamespace)
Button("Галерея") { }
.padding()
.glassEffect()
.glassEffectID("gallery", in: glassNamespace)
Button("Файли") { }
.padding()
.glassEffect()
.glassEffectID("files", in: glassNamespace)
}
} else {
Button(action: { }) {
Image(systemName: "plus")
.font(.title2)
.padding()
}
.glassEffect()
.glassEffectID("camera", in: glassNamespace)
}
}
.animation(.spring(duration: 0.4), value: isExpanded)
.onTapGesture {
isExpanded.toggle()
}
}
}
}
Ідентифікатори glassEffectID гарантують, що SwiftUI правильно анімує фігури під час появи та зникнення елементів. Система використовує значення spacing контейнера та геометрію фігур, щоб визначити, які форми мають морфувати одна в одну.
Типова помилка з морфінг-анімаціями
Якщо бачите глітчі при морфінгу — форма стрибає від кола до прямокутника без плавного переходу — перевірте, чи ви додали glassEffect до зовнішнього view. Наприклад, для Menu модифікатор має бути на самому Menu, а не на його внутрішньому label.
Ця помилка трапляється частіше, ніж здається. Якщо щось морфує «криво» — перше, що варто перевірити.
Liquid Glass у Tab Bar
iOS 26 приніс суттєві зміни до TabView, і Liquid Glass є центральною частиною цих оновлень. Нові ролі табів, нижні аксесуари, поведінка мінімізації — усе це тепер зі «скляним» характером.
Базовий скляний Tab Bar
Гарна новина: у iOS 26 TabView автоматично отримує Liquid Glass стиль. Вам не потрібно додавати glassEffect вручну — система робить це за вас:
struct MainTabView: View {
var body: some View {
TabView {
Tab("Головна", systemImage: "house") {
HomeView()
}
Tab("Пошук", systemImage: "magnifyingglass") {
SearchView()
}
Tab("Профіль", systemImage: "person") {
ProfileView()
}
}
}
}
Так, ось і весь код. Жодних додаткових модифікаторів.
Аксесуар нижнього Tab Bar
Одна з найцікавіших нових можливостей — .tabViewBottomAccessory. Він дозволяє додати view над tab bar, і це ідеально підходить для «Now Playing» панелей у музичних додатках або швидких дій:
struct MusicApp: View {
var body: some View {
TabView {
Tab("Бібліотека", systemImage: "music.note.list") {
LibraryView()
}
Tab("Пошук", systemImage: "magnifyingglass") {
SearchView()
}
}
.tabViewBottomAccessory {
NowPlayingAccessory()
}
}
}
struct NowPlayingAccessory: View {
var body: some View {
HStack {
Image(systemName: "music.note")
.font(.title3)
VStack(alignment: .leading, spacing: 2) {
Text("Назва пісні")
.font(.subheadline.bold())
Text("Виконавець")
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
Button(action: { }) {
Image(systemName: "play.fill")
}
.buttonStyle(.glass)
.controlSize(.small)
}
.padding(.horizontal)
}
}
Зверніть увагу на .buttonStyle(.glass) — ще один новий стиль кнопки в iOS 26, який додає скляний вигляд до стандартних контролів.
Liquid Glass у тулбарах та навігації
У навігаційних панелях та тулбарах Liquid Glass застосовується автоматично. Але є цікавий нюанс: ToolbarItemPlacement тепер впливає не лише на розташування, а й на зовнішній вигляд елемента:
struct DetailView: View {
var body: some View {
ScrollView {
// Контент
}
.navigationTitle("Деталі")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button("Зберегти", systemImage: "checkmark") { }
// Автоматично отримує glass стиль
}
ToolbarItem(placement: .confirmationAction) {
Button("Готово") { }
// Автоматично отримує .glassProminent стиль
}
}
}
}
Важливий нюанс: якщо помічаєте дезорієнтуючі анімації при навігації, додайте константний id до ToolbarItem. Це запобігає перестворенню елементів:
ToolbarItem(id: "save-button", placement: .primaryAction) {
Button("Зберегти") { }
}
Маленький трюк, але він рятує від неприємних візуальних артефактів.
Sheets і Liquid Glass
Sheets в iOS 26 теж отримують скляний вигляд, але тут є підводний камінь, на який натикаються багато розробників.
Якщо sheet містить Form або NavigationStack, скляний ефект може просто зникнути. Проблема в тому, що Form та List мають власний непрозорий фон, який перекриває скляну поверхню sheet.
Рішення досить просте:
struct SettingsSheet: View {
var body: some View {
NavigationStack {
Form {
Section("Загальні") {
Toggle("Сповіщення", isOn: .constant(true))
Toggle("Темна тема", isOn: .constant(false))
}
Section("Про додаток") {
LabeledContent("Версія", value: "1.0.0")
}
}
.scrollContentBackground(.hidden) // Ключовий рядок!
.navigationTitle("Налаштування")
}
.presentationDetents([.medium, .large])
}
}
Бачите .scrollContentBackground(.hidden)? Саме цей модифікатор прибирає непрозорий фон форми та дозволяє скляному ефекту sheet просвічуватися. Також потрібно вказати хоча б одну часткову висоту через .presentationDetents — інакше ефект теж не працюватиме.
Доступність та Reduce Transparency
Liquid Glass зазнав певної критики за потенційні проблеми з читабельністю — і, відверто кажучи, частково обґрунтовано. Apple відповіла на це, давши користувачам більше контролю над ефектом. Як розробники, ми зобов'язані це враховувати.
Підтримка Reduce Transparency
Використовуйте змінну оточення accessibilityReduceTransparency для умовного зменшення прозорості:
struct AccessibleGlassView: View {
@Environment(\.accessibilityReduceTransparency)
var reduceTransparency
var body: some View {
Text("Доступна кнопка")
.padding()
.glassEffect(
reduceTransparency ? .identity : .regular
)
}
}
У більшості випадків SwiftUI автоматично обробляє Reduce Transparency для стандартних компонентів. Але якщо ви створюєте кастомні скляні елементи — обов'язково додайте цю перевірку. Це не опція, а необхідність.
Контрастність тексту
SwiftUI автоматично застосовує vibrant text style до контенту під склом, але на яскравому або строкатому фоні текст може втратити читабельність. Для критичних елементів допомагає тонування:
// Тонування покращує контрастність
Text("Важлива дія")
.padding()
.glassEffect(.regular.tint(.blue))
Типові помилки та їх вирішення
За час роботи з Liquid Glass я зібрав найпоширеніші проблеми, на які натикаються розробники. Ось як їх уникнути.
Скло на склі (Glass on Glass)
Ніколи не накладайте скляні елементи один на одний. Скло не може коректно «зчитувати» інше скло — результат виглядає як розмита каша. Замість цього оберніть кілька елементів у один GlassEffectContainer.
Проблеми з .clear на яскравому фоні
Варіант .clear чудово виглядає поверх зображень та відео, але читабельність може постраждати. Простий фікс — додати шар затемнення:
ZStack {
Image("background")
.resizable()
.scaledToFill()
Color.black.opacity(0.25) // Шар затемнення
Text("Читабельний текст")
.padding()
.glassEffect(.clear)
}
Зворотна сумісність
API Liquid Glass доступні лише з iOS 26. Якщо ваш додаток підтримує старіші версії, огортайте код перевіркою доступності:
if #available(iOS 26, *) {
Text("Сучасне скло")
.padding()
.glassEffect()
} else {
Text("Класичний стиль")
.padding()
.background(.ultraThinMaterial, in: Capsule())
}
Не найелегантніше рішення, але поки що — єдиний надійний спосіб підтримувати обидві версії.
Повний приклад: картка з скляними контролами
Тепер зберемо все разом. Ось повний приклад, що демонструє контейнер, морфінг, тонування та інтерактивність в одному view:
struct GlassCardExample: View {
@State private var isFavorite = false
@State private var showActions = false
@Namespace private var cardNamespace
var body: some View {
ZStack(alignment: .bottom) {
// Фоновий контент
Image("landscape")
.resizable()
.scaledToFill()
.ignoresSafeArea()
// Скляні контроли
GlassEffectContainer(spacing: 8) {
VStack(spacing: 12) {
if showActions {
HStack(spacing: 8) {
Button(action: { }) {
Label("Поділитися",
systemImage: "square.and.arrow.up")
}
.glassEffect()
.glassEffectID("share", in: cardNamespace)
Button(action: {
isFavorite.toggle()
}) {
Label(
isFavorite ? "В обраному" : "Додати",
systemImage: isFavorite
? "heart.fill"
: "heart"
)
}
.glassEffect(
.regular.tint(
isFavorite ? .red : .clear
)
)
.glassEffectID("favorite", in: cardNamespace)
}
}
Button(action: {
showActions.toggle()
}) {
Image(systemName: showActions
? "xmark"
: "ellipsis")
.font(.title3)
.padding()
}
.glassEffect(.regular.interactive())
.glassEffectID("toggle", in: cardNamespace)
}
.animation(.spring(duration: 0.35), value: showActions)
.padding(.bottom, 40)
}
}
}
}
Тут ми використовуємо GlassEffectContainer для координації скляних елементів, морфінг через glassEffectID, умовне тонування для стану «обране», інтерактивний ефект для кнопки-перемикача та пружинну анімацію. Спробуйте адаптувати цей приклад під свій проєкт — він добре демонструє можливості Liquid Glass на практиці.
Поширені запитання (FAQ)
Чи можна використовувати Liquid Glass у додатках, що підтримують iOS 17 або старіші версії?
Так, але з обмеженнями. API glassEffect доступні лише з iOS 26, тому потрібна перевірка #available(iOS 26, *) та альтернативний вигляд для старіших версій. Для фолбеку підійде .background(.ultraThinMaterial) — схожий, хоча і значно простіший ефект прозорості.
Чим Liquid Glass відрізняється від .ultraThinMaterial у SwiftUI?
.ultraThinMaterial — це статичне розмиття фону без лінзування, бліків та адаптивних тіней. Liquid Glass — динамічний матеріал, який заломлює контент у реальному часі, реагує на рух пристрою та підтримує морфінг-анімації. Різниця приблизно як між фото і відео — принципово різні рівні глибини.
Чи впливає Liquid Glass на продуктивність додатку?
Apple оптимізувала рендеринг на апаратному рівні, тому для стандартних випадків вплив мінімальний. Але якщо у вас десятки скляних елементів без GlassEffectContainer — це вже може створити навантаження. Контейнер об'єднує рендеринг кількох фігур, що суттєво покращує продуктивність.
Як працює Liquid Glass з темною темою?
Liquid Glass автоматично адаптується до поточної колірної схеми. У темній темі ефект стає темнішим, зберігаючи відповідний контраст. SwiftUI навіть може динамічно перемикати колірну схему вмісту скляного елемента з світлої на темну залежно від контенту під ним — досить розумна поведінка.
Що робити, якщо скляний ефект зникає у Sheet з Form?
Це відома поведінка: Form та List мають власний непрозорий фон, який перекриває скляну поверхню sheet. Додайте .scrollContentBackground(.hidden) до Form та вкажіть хоча б одну часткову висоту через .presentationDetents([.medium, .large]). Це вирішує проблему у переважній більшості випадків.