Liquid Glass — Applen suurin muotoiluvallankumous sitten iOS 7:n
Kun Apple esitteli Liquid Glass -suunnittelukielen WWDC25:ssä kesäkuussa 2025, se ei ollut pelkkä kosmeettinen päivitys. Tämä oli kokonaisvaltainen muotoilufilosofian muutos — sellainen, joka vaikuttaa ihan kaikkeen iOS 26:sta macOS Tahoe 26:een, watchOS 26:een ja tvOS 26:een. Liquid Glass on läpikuultava, dynaaminen materiaali, joka heijastaa ja taittaa ympäristöään reaaliajassa. Ja rehellisesti sanottuna, se muuttaa perusteellisesti tapaa, jolla suunnittelemme ja rakennamme käyttöliittymiä SwiftUI:ssa.
Kehittäjänä tämä tarkoittaa uusien API-rajapintojen oppimista, suunnitteluperiaatteiden sisäistämistä ja — mikä tärkeintä — käytännön toteutustaitojen hankkimista. Tässä oppaassa käymme läpi kaiken mitä tarvitset Liquid Glassin toteuttamiseen omissa SwiftUI-sovelluksissasi: perusmodifikaattoreista edistyneisiin morffausanimaatioihin, suorituskyvyn optimoinnista saavutettavuuteen.
Jos olet jo lukenut aiemman artikkelimme Swift 6.2:n rinnakkaisuusparannuksista, tämä opas täydentää sitä hienosti — nyt kun koodisi rakenne on kunnossa, on aika tehdä siitä myös visuaalisesti upea.
Mitä Liquid Glass on teknisesti?
Liquid Glass on Applen kehittämä dynaaminen materiaali, joka simuloi lasimaista pintaa reaaliaikaisella valaistuksella ja fysiikalla. Se ei siis ole pelkkä staattinen läpinäkyvyysefekti — se reagoi aktiivisesti ympäristöönsä ja käyttäjän toimintaan.
Tekniset perusominaisuudet
Liquid Glassin ytimessä on neljä pääominaisuutta:
- Reaaliaikainen valontaitto (lensing) — materiaali taittaa taustalla olevaa sisältöä kuten aito lasi, luoden kolmiulotteisen vaikutelman
- Spekulaariheijastukset — valonheijastukset reagoivat laitteen liikkeeseen gyroskooppidatan avulla
- Adaptiiviset varjot — varjot mukautuvat automaattisesti ympäröivään sisältöön
- Interaktiiviset käyttäytymismallit — elementit reagoivat kosketukseen skaalautumisella, kimpoamisella ja värinäefekteillä
Käytännössä tämä tarkoittaa, että kun käyttäjä vierittää sisältöä lasi-elementin takana, materiaali päivittyy jokaisessa ruudunpäivityksessä. Tämä luo aidon fyysisen materiaalin tuntuman digitaaliseen käyttöliittymään — ja se on yllättävän vaikuttavaa nähdä livenä.
Arkkitehtuurinen periaate: Kerrosmalli
Liquid Glassin tärkein suunnitteluperiaate on kerrosmalli: lasi-elementit ovat aina käyttöliittymän päällimmäisellä kerroksella. Ne eivät ole osa pääsisältöä — ne kelluvat sen yläpuolella.
Tämä tarkoittaa käytännössä:
- Työkalupalkit, välilehtivalikot ja kelluvat painikkeet — kyllä
- Listarivit, tekstisisältö ja mediankatselu — ei
- Navigaatioelementit ja ohjauselementit — kyllä
- Taulukoiden solut ja korttipohjat — ei
Tämä on kriittinen ero. Jos sovellat lasiefektiä kaikkialle, lopputulos on visuaalisesti sekava ja käyttökokemukseltaan heikko. Olen nähnyt jo muutamia sovelluksia, joissa tämä meni pieleen — älä tee samaa virhettä.
Perusmodifikaattori: glassEffect()
Liquid Glassin käyttöönotto SwiftUI:ssa alkaa yhdestä modifikaattorista: .glassEffect(). Tämä on yksinkertaisin tapa lisätä lasiefekti mihin tahansa näkymään.
Funktion allekirjoitus
Täydellinen API-allekirjoitus näyttää tältä:
func glassEffect<S: Shape>(
_ glass: Glass = .regular,
in shape: S = DefaultGlassEffectShape,
isEnabled: Bool = true
) -> some View
Oletusarvoisesti modifikaattori käyttää .regular-varianttia ja .capsule-muotoa. Yksinkertaisin mahdollinen käyttö on siis:
Text("Tervetuloa")
.padding()
.glassEffect()
Tämä luo kapselinmuotoisen lasi-elementin, joka näyttää heti modernilta iOS 26 -tyyliseltä. Mutta todellinen voima piilee kustomointivaihtoehdoissa.
Kolme lasivarianttia
SwiftUI tarjoaa kolme Glass-varianttia, joilla kullakin on oma käyttötarkoituksensa:
1. Glass.regular — Oletusvariantti, joka sopii useimpiin tilanteisiin. Tarjoaa keskitasoisen läpinäkyvyyden ja mukautuu automaattisesti taustaan:
Button("Tallenna") {
saveDocument()
}
.padding(.horizontal, 20)
.padding(.vertical, 12)
.glassEffect(.regular)
2. Glass.clear — Korkean läpinäkyvyyden variantti mediarikkaiden taustojen päälle. Tämä vaatii, että taustalla oleva sisältö kestää kevyen himmennykerroksen:
// Hyvä käyttötapaus: kelluva painike kuvan päällä
Image("taustakuva")
.overlay(alignment: .bottomTrailing) {
Button {
shareImage()
} label: {
Image(systemName: "square.and.arrow.up")
.font(.title2)
.foregroundStyle(.white)
.frame(width: 50, height: 50)
}
.glassEffect(.clear, in: .circle)
.padding()
}
3. Glass.identity — Erikoisvariantti, joka poistaa lasiefektin kokonaan. Erityisen hyödyllinen ehdollisessa renderöinnissä ja saavutettavuustilanteissa:
@Environment(\.accessibilityReduceTransparency)
private var reduceTransparency
var body: some View {
controlPanel
.glassEffect(
reduceTransparency ? .identity : .regular
)
}
Tuetut muodot
Lasiefektin muoto määritellään in:-parametrilla. Kaikki Shape-protokollan mukaiset muodot toimivat:
// Kapseli (oletus)
.glassEffect(.regular, in: .capsule)
// Ympyrä
.glassEffect(.regular, in: .circle)
// Pyöristetty suorakaide
.glassEffect(.regular, in: RoundedRectangle(cornerRadius: 16))
// Automaattinen kulmien kohdistus kontainerin kanssa
.glassEffect(.regular, in: .rect(cornerRadius: .containerConcentric))
// Ellipsi
.glassEffect(.regular, in: .ellipse)
Erityisesti .containerConcentric on kätevä — se kohdistaa lasi-elementin kulmat automaattisesti ympäröivän kontainerin kanssa. Tämä luo visuaalisesti harmonisen lopputuloksen sisäkkäisissä asetteluissa.
Värjäys ja interaktiivisuus
Pelkkä lasiefekti on hyvä alku, mutta todellinen muotoiluvoima tulee värjäyksestä ja interaktiivisista ominaisuuksista.
Tint-modifikaattori
Semanttinen väri lisätään .tint()-modifikaattorilla:
// Sininen korostus
.glassEffect(.regular.tint(.blue))
// Purppura osittaisella läpinäkyvyydellä
.glassEffect(.regular.tint(.purple.opacity(0.6)))
// Punainen varoituspainike
Button("Poista") {
deleteItem()
}
.padding()
.glassEffect(.regular.tint(.red.opacity(0.7)))
Väri säilyttää lasin läpinäkyvyyden ja luo visuaalista syvyyttä ilman, että elementti muuttuu täysin opaakiksi. Erinomainen tapa luoda hierarkiaa eri toimintojen välille.
Interaktiivinen tila
iOS:ssä interaktiivinen tila otetaan käyttöön .interactive()-modifikaattorilla:
.glassEffect(.regular.interactive())
Tämä aktivoi neljä visuaalista palautemekanismia:
- Skaalautuminen — elementti pienenee hieman kosketuksessa
- Kimpoaminen — elementti palautuu joustavasti alkuperäiseen kokoonsa
- Hohtaminen — lasi välkkyy hetkellisesti kosketuspisteessä
- Kosketuspisteen valaistus — valoefekti seuraa kosketuksen sijaintia
Tämä tekee elementeistä huomattavasti responsiivisempia. Se myös vastaa iOS 26:n järjestelmäkomponenttien käyttäytymistä, joten käyttäjille tuntuma on tuttu.
Modifikaattorien ketjuttaminen
Värjäys ja interaktiivisuus voidaan ketjuttaa vapaasti — järjestyksellä ei ole merkitystä:
// Nämä ovat toiminnallisesti identtisiä
.glassEffect(.regular.tint(.orange).interactive())
.glassEffect(.regular.interactive().tint(.orange))
Kätevää, eikö? Tämä tekee API:sta intuitiivisen ja joustavan.
Valmis painikketyyli: .buttonStyle(.glass)
SwiftUI tarjoaa myös valmiin painikketyylin, joka hoitaa Liquid Glass -ulkoasun automaattisesti:
Button("Jatka") {
navigateToNext()
}
.buttonStyle(.glass)
Tämä on nopein tapa lisätä lasiefekti painikkeisiin. Se käsittelee oikean muodon, interaktiivisuuden ja tilanvaihtelut puolestasi.
Jos kuitenkin tarvitset tarkempaa kontrollia — kuten mukautettua muotoa, värjäystä tai animaatiota — kannattaa käyttää suoraan .glassEffect()-modifikaattoria.
GlassEffectContainer: Elementtien ryhmittely
Yksi Liquid Glassin tärkeimmistä käsitteistä on GlassEffectContainer. Tämä kontaineri ryhmittelee useita lasi-elementtejä yhteen, mahdollistaa morffausanimaatiot ja parantaa suorituskykyä merkittävästi.
Miksi ryhmittely on tärkeää?
Lasi ei voi näytteistää toista lasia. Jos lasi-elementtejä on vierekkäin eri kontainereissa, niiden visuaalinen käyttäytyminen on epäjohdonmukaista — kukin näytteistää taustaa itsenäisesti, mikä johtaa rumiin artefakteihin. GlassEffectContainer ratkaisee tämän jakamalla yhteisen näytteistysalueen.
Peruskäyttö
GlassEffectContainer {
HStack(spacing: 12) {
Button {
showHome()
} label: {
Image(systemName: "house")
.frame(width: 44, height: 44)
}
.glassEffect()
Button {
showSearch()
} label: {
Image(systemName: "magnifyingglass")
.frame(width: 44, height: 44)
}
.glassEffect()
Button {
showProfile()
} label: {
Image(systemName: "person")
.frame(width: 44, height: 44)
}
.glassEffect()
}
}
Kun elementit ovat samassa kontainerissa, ne sulautuvat visuaalisesti yhteen animaation aikana — ikään kuin ne olisivat muodostuneet samasta lasipisarasta. Se näyttää todella hienolta.
Välimatkan hallinta
GlassEffectContainer tukee valinnaista spacing-parametria:
GlassEffectContainer(spacing: 40.0) {
// Elementit 40 pisteen sisällä toisistaan
// morffaavat yhteen animaation aikana
navigationButtons
}
Tämä parametri määrittää, kuinka lähellä toisiaan olevat elementit reagoivat toisiinsa morffauksessa.
Suorituskykyedut
Ryhmittely ei ole pelkästään visuaalinen valinta — se on myös suorituskykyoptimointia.
Jokainen erillinen lasiefekti vaatii kolme erillistä offscreen-tekstuuria. GlassEffectContainer yhdistää nämä yhdeksi CABackdropLayer-kerrokseksi, mikä vähentää GPU-kuormaa huomattavasti.
Esimerkiksi viisi erillistä lasi-elementtiä ilman kontaineria tarkoittaa 15 offscreen-tekstuuria. Kontainerin kanssa tarvitset vain 3. Ero on merkittävä etenkin vanhemmissa laitteissa.
Morffausanimaatiot glassEffectID:llä
Nyt päästään siihen oikeasti kiinnostavaan osaan. Yksi Liquid Glassin visuaalisesti vaikuttavimmista ominaisuuksista on morffausanimaatio — mahdollisuus muuntaa lasi-elementtejä sulavasti toisiksi. Tämä saavutetaan glassEffectID-modifikaattorilla.
Kolme vaatimusta morffaukselle
Morffausanimaation toimimiseksi kolmen ehdon on täytyttävä samanaikaisesti:
- Elementtien on oltava samassa
GlassEffectContainer-kontainerissa - Jokaisella näkymällä on oltava yksilöllinen
glassEffectIDjaetussaNamespace-avaruudessa - Tilan muutokset on käärittävä animaatioon:
withAnimation { ... }
Unohda yksikin näistä ja morffaus ei toimi. Tämä on varmaan yleisin virhe, johon kehittäjät törmäävät alussa.
Käytännön esimerkki: Laajentuva toimintopaneeli
Rakennetaan kelluva toimintopaneeli, joka laajenee yhdestä painikkeesta useaksi:
struct ExpandableActionPanel: View {
@State private var isExpanded = false
@Namespace private var glassNamespace
var body: some View {
GlassEffectContainer {
if isExpanded {
// Laajennettu tila: useita painikkeita
HStack(spacing: 16) {
actionButton(
icon: "house",
id: "home"
)
actionButton(
icon: "magnifyingglass",
id: "search"
)
actionButton(
icon: "envelope",
id: "mail"
)
actionButton(
icon: "gear",
id: "settings"
)
}
.glassEffect()
.glassEffectID("panel", in: glassNamespace)
} else {
// Tiivistetty tila: yksi painike
Button {
withAnimation(.bouncy) {
isExpanded.toggle()
}
} label: {
Image(systemName: "plus")
.font(.title2)
.foregroundStyle(.white)
.frame(width: 56, height: 56)
}
.glassEffect(.regular.interactive())
.glassEffectID("panel", in: glassNamespace)
}
}
}
private func actionButton(
icon: String,
id: String
) -> some View {
Button {
handleAction(id)
} label: {
Image(systemName: icon)
.font(.title3)
.foregroundStyle(.white)
.frame(width: 44, height: 44)
}
}
private func handleAction(_ id: String) {
withAnimation(.bouncy) {
isExpanded = false
}
}
}
Huomaa, miten molemmat tilat jakavat saman glassEffectID("panel")-tunnisteen. Tämä kertoo SwiftUI:lle, että kyseessä on sama looginen elementti joka vain muuttaa muotoaan — ja järjestelmä generoi sulavan morffausanimaation automaattisesti.
Siirtymätyylit
Oletuksena morffaus käyttää matchedGeometry-siirtymää. Vaihtoehtoisesti voit käyttää materialize-siirtymää, joka käyttää häivytystä morffauksen sijaan:
.glassEffectTransition(.materialize)
Tämä on hyödyllinen silloin, kun geometrinen morffaus ei tuota luonnollista lopputulosta — esimerkiksi kun elementtien koot eroavat toisistaan merkittävästi.
GlassEffectUnion: Ryhmittely ilman kontaineria
Joskus elementtejä ei voi (tai halua) kääriä GlassEffectContainer-kontaineriin. Tällöin glassEffectUnion-modifikaattori tulee avuksi:
.glassEffectUnion(id: "toolbar", namespace: toolbarNamespace)
Tämä mahdollistaa morffausanimaatiot ilman, että elementtien täytyy olla fyysisesti samassa kontainerissa.
Käytännön esimerkki: Mukautettu kelluva työkalupalkki
Rakennetaan kokonainen kelluva työkalupalkki, joka hyödyntää kaikkia tähän mennessä opittuja tekniikoita:
struct FloatingToolbar: View {
@State private var selectedTool: ToolType = .brush
@State private var showColorPicker = false
@Namespace private var toolbarNamespace
enum ToolType: String, CaseIterable {
case brush, eraser, fill, text
var icon: String {
switch self {
case .brush: return "paintbrush"
case .eraser: return "eraser"
case .fill: return "drop"
case .text: return "textformat"
}
}
var label: String {
switch self {
case .brush: return "Sivellin"
case .eraser: return "Pyyhekumi"
case .fill: return "Täyttö"
case .text: return "Teksti"
}
}
}
var body: some View {
VStack(spacing: 16) {
GlassEffectContainer {
VStack(spacing: 12) {
// Työkaluvalinta
HStack(spacing: 8) {
ForEach(
ToolType.allCases,
id: \.self
) { tool in
toolButton(for: tool)
}
}
// Värinvalitsija-painike
if showColorPicker {
colorPickerPanel
.glassEffect(
.regular.tint(
.blue.opacity(0.3)
),
in: RoundedRectangle(
cornerRadius: 12
)
)
.glassEffectID(
"colorPanel",
in: toolbarNamespace
)
}
}
}
}
.padding()
}
private func toolButton(
for tool: ToolType
) -> some View {
Button {
withAnimation(.bouncy) {
selectedTool = tool
}
} label: {
VStack(spacing: 4) {
Image(systemName: tool.icon)
.font(.title3)
if selectedTool == tool {
Text(tool.label)
.font(.caption2)
}
}
.foregroundStyle(
selectedTool == tool
? .white
: .secondary
)
.frame(
width: selectedTool == tool ? 60 : 44,
height: 50
)
}
.glassEffect(
selectedTool == tool
? .regular.tint(
.blue.opacity(0.5)
).interactive()
: .regular.interactive()
)
.glassEffectID(
tool.rawValue,
in: toolbarNamespace
)
}
private var colorPickerPanel: some View {
HStack(spacing: 12) {
ForEach(
[Color.red, .orange, .yellow,
.green, .blue, .purple],
id: \.self
) { color in
Circle()
.fill(color)
.frame(width: 32, height: 32)
}
}
.padding()
}
}
Tässä esimerkissä näet useita tekniikoita yhdessä: ehdollinen värjäys valitulle työkalulle, interaktiiviset painikkeet, morffausanimaatiot ja kontainerin käyttö yhtenäiseen renderöintiin. Aika siistiä, vai mitä?
Tunnettuja sudenkuoppia ja ratkaisuja
Liquid Glassin käyttöönotto ei aina ole suoraviivaista. Tässä ovat yleisimmät ongelmat ja niiden ratkaisut — käytännössä ne asiat, joihin tulet todennäköisesti törmäämään.
1. Rotaatioanimaation vääristymä
Kun käytät rotationEffect(_:anchor:)-modifikaattoria lasi-elementtien kanssa, muoto vääristyy animaation aikana. Kapseli muuttuu ensin ympyräksi ja sitten pyöritettyksi kapseliksi — eikä se näytä hyvältä.
Ratkaisu: Käytä UIKit-siltausta:
struct RotatableGlassView: UIViewRepresentable {
let rotation: Angle
func makeUIView(
context: Context
) -> UIVisualEffectView {
let effect = UIGlassEffect()
let view = UIVisualEffectView(effect: effect)
view.layer.cornerCurve = .continuous
return view
}
func updateUIView(
_ uiView: UIVisualEffectView,
context: Context
) {
uiView.transform = CGAffineTransform(
rotationAngle: rotation.radians
)
}
}
UIKit:n alla oleva Core Animation -komponentti käsittelee rotaation oikein. Hieman lisätyötä, mutta toimii.
2. Kosketusalueen ongelma
Tämä on klassikko. Lasi-elementin sisällä vain itse sisältö (esim. SF-symboli) reagoi kosketukseen — koko näkyvä lasipinta ei vastaa klikkauksiin.
Ratkaisu: Määritä sisällön muoto contentShape()-modifikaattorilla:
Button(action: { performAction() }) {
Image(systemName: "ellipsis")
.font(.title2)
.frame(width: 50, height: 50)
}
.contentShape(Circle())
.glassEffect(.regular, in: .circle)
Näin koko lasipinta on klikattava. Pieni muutos, iso vaikutus käyttökokemukseen.
3. Menu-elementtien morffausongelmat
iOS 26.0:ssa ja 26.0.1:ssä Menu-elementtien lasiefektit käyttäytyivät odottamattomasti. Tässä kiertotapa:
// iOS 26.0 kiertotapa:
// sovella lasiefektiä Menuun, ei sen sisältöön
Menu {
Button("Vaihtoehto 1") { }
Button("Vaihtoehto 2") { }
} label: {
Image(systemName: "ellipsis.circle")
.frame(width: 44, height: 44)
}
.glassEffect(.regular.interactive())
iOS 26.1:stä alkaen voit käyttää mukautettua ButtonStyle-toteutusta:
struct GlassMenuStyle: ButtonStyle {
func makeBody(
configuration: Configuration
) -> some View {
configuration.label
.glassEffect(
.regular.interactive()
)
}
}
Menu {
Button("Vaihtoehto 1") { }
Button("Vaihtoehto 2") { }
} label: {
Image(systemName: "ellipsis.circle")
.frame(width: 44, height: 44)
}
.buttonStyle(GlassMenuStyle())
4. SF-symbolien varianttien valinta
Pieni mutta tärkeä yksityiskohta: Liquid Glass -painikkeiden kanssa suositellaan "none"-varianttia SF-symboleista ympyrävarianttien sijaan:
// Suositeltu tapa
Image(systemName: "checkmark")
.symbolVariant(.none)
// Vältä tätä
Image(systemName: "checkmark.circle")
Puhtaampaa koodia ja parempi visuaalinen lopputulos lasi-elementtien sisällä.
Saavutettavuus ja Liquid Glass
Saavutettavuus on kriittinen osa jokaista käyttöliittymätoteutusta, eikä Liquid Glass ole poikkeus. Onneksi Apple on hoitanut tämän pitkälti automaattisesti.
Automaattiset mukautukset
Järjestelmä käsittelee automaattisesti seuraavat asetukset:
- Reduce Transparency — lisää huurteen määrää, jolloin lasi on vähemmän läpinäkyvä
- Increase Contrast — käyttää voimakkaampia värejä ja reunoja
- Reduce Motion — vähentää animaatioiden intensiteettiä
- Tinted Mode (iOS 26.1+) — mahdollistaa käyttäjän hallitseman opasiteetin
Milloin puuttua manuaalisesti?
Useimmiten järjestelmä hoitaa mukautukset. Puutu vain silloin, kun sinulla on hyvin erikoistunut elementti, joka ei noudata vakiomalleja:
@Environment(\.accessibilityReduceTransparency)
private var reduceTransparency
var body: some View {
customOverlay
.glassEffect(
reduceTransparency
? .identity
: .regular
)
}
Muista: anna järjestelmän hoitaa mahdollisimman paljon. Liialliset manuaaliset mukautukset voivat rikkoa yhtenäisen kokemuksen.
Suorituskyvyn optimointi
Liquid Glass on visuaalisesti vaikuttava, mutta renderöintikustannukset ovat reaaliset. Tässä tärkeimmät vinkit.
Minimoi erillisten lasi-elementtien määrä
Jokainen erillinen lasiefekti vaatii kolme offscreen-tekstuuria. 10 erillistä elementtiä tarkoittaa 30 tekstuuria — merkittävä GPU-kuorma etenkin vanhemmissa laitteissa.
Käytä aina GlassEffectContainer-kontaineria
Kontaineri yhdistää useat lasiefektit yhdeksi CABackdropLayer-kerrokseksi:
// Huono: 5 erillistä renderöintikerrosta (15 tekstuuria)
VStack {
button1.glassEffect()
button2.glassEffect()
button3.glassEffect()
button4.glassEffect()
button5.glassEffect()
}
// Hyvä: 1 jaettu renderöintikerros (3 tekstuuria)
GlassEffectContainer {
VStack {
button1.glassEffect()
button2.glassEffect()
button3.glassEffect()
button4.glassEffect()
button5.glassEffect()
}
}
Vältä lasiefektiä vieritettävässä sisällössä
Lasiefektit vieritettävien listojen sisällä ovat raskaita — jokainen rivi vaatii jatkuvaa uudelleenrenderöintiä. Pidä lasiefektit staattisissa, kelluvissa elementeissä.
Käytä isEnabled-parametria ehdolliseen renderöintiin
// Poista lasiefekti käytöstä suorituskykykriittisissä tilanteissa
.glassEffect(.regular, isEnabled: !isScrolling)
Yhteensopivuus ja vähittäinen käyttöönotto
Liquid Glass on saatavilla iOS 26:sta alkaen, joten yhteensopivuus vanhempien versioiden kanssa vaatii huomiota.
Ehdollinen käyttö
Saatavuustarkistus varmistaa, että sovellus toimii myös vanhemmissa versioissa:
struct AdaptiveGlassButton: View {
let title: String
let action: () -> Void
var body: some View {
Button(title, action: action)
.padding()
.modifier(GlassModifier())
}
}
struct GlassModifier: ViewModifier {
func body(content: Content) -> some View {
if #available(iOS 26, *) {
content
.glassEffect(.regular.interactive())
} else {
content
.background(.ultraThinMaterial)
.clipShape(Capsule())
}
}
}
Tämä lähestymistapa tarjoaa Liquid Glass -kokemuksen iOS 26:ssa ja putoaa takaisin .ultraThinMaterial-materiaaliin vanhemmissa versioissa. Näppärää.
Siirtymäkausi
Apple tarjoaa kehittäjille yhden vuoden siirtymäkauden, jonka aikana voit poistaa Liquid Glass -ulkoasun käytöstä iOS 26:ssa Info.plist-asetuksella. Siirtymäkausi on kuitenkin väliaikainen — pitkällä aikavälillä kaikkien sovellusten tulisi omaksua uusi suunnittelukieli.
Kokonainen esimerkkisovellus: Kuvagallerian kelluva käyttöliittymä
Yhdistetään kaikki opit yhdeksi kokonaiseksi esimerkiksi — kuvagalleria, jossa on kelluva Liquid Glass -käyttöliittymä:
struct GalleryView: View {
@State private var selectedFilter: FilterType = .all
@State private var showDetails = false
@State private var selectedImage: GalleryImage?
@Namespace private var galleryNamespace
enum FilterType: String, CaseIterable {
case all = "Kaikki"
case favorites = "Suosikit"
case recent = "Viimeisimmät"
var icon: String {
switch self {
case .all: return "square.grid.2x2"
case .favorites: return "heart"
case .recent: return "clock"
}
}
}
var body: some View {
ZStack(alignment: .bottom) {
// Päasisältö: kuvaruudukko
ScrollView {
LazyVGrid(
columns: [
GridItem(.adaptive(minimum: 120))
],
spacing: 4
) {
ForEach(galleryImages) { image in
galleryThumbnail(image)
}
}
.padding(.bottom, 100)
}
// Kelluva navigointipalkki
GlassEffectContainer {
HStack(spacing: 8) {
ForEach(
FilterType.allCases,
id: \.self
) { filter in
filterButton(for: filter)
}
}
.padding(.horizontal, 8)
.padding(.vertical, 6)
.glassEffect(
.regular,
in: RoundedRectangle(
cornerRadius: 20
)
)
.glassEffectID(
"filterBar",
in: galleryNamespace
)
}
.padding(.bottom, 20)
}
}
private func filterButton(
for filter: FilterType
) -> some View {
Button {
withAnimation(.bouncy) {
selectedFilter = filter
}
} label: {
HStack(spacing: 6) {
Image(systemName: filter.icon)
.symbolVariant(.none)
if selectedFilter == filter {
Text(filter.rawValue)
.font(.subheadline)
.fontWeight(.medium)
}
}
.foregroundStyle(
selectedFilter == filter
? .white
: .secondary
)
.padding(.horizontal, 14)
.padding(.vertical, 10)
}
.glassEffect(
selectedFilter == filter
? .regular.tint(
.blue.opacity(0.5)
).interactive()
: .clear.interactive()
)
}
private func galleryThumbnail(
_ image: GalleryImage
) -> some View {
Image(image.name)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(
minWidth: 120,
minHeight: 120
)
.clipped()
.onTapGesture {
selectedImage = image
showDetails = true
}
}
}
Tässä esimerkissä kelluva suodatinpalkki käyttää GlassEffectContainer-kontaineria, ehdollista värjäystä, morffausanimaatioita suodattimien välillä ja .clear-varianttia ei-aktiivisille painikkeille. Hyvä lähtökohta omille sovelluksillesi.
Liquid Glass watchOS:ssä ja muilla alustoilla
Liquid Glass ei rajoitu pelkästään iOS:ään — Apple on tuonut saman suunnittelukielen kaikille alustoilleen.
watchOS 26
Apple Watchissa Liquid Glass näkyy navigaatioelementeissä ja komplikaatioissa. Pienen näytön vuoksi efektit ovat hillitympiä, mutta API toimii samalla tavalla:
// watchOS 26 -komplikaatio lasilla
struct WatchComplication: View {
var body: some View {
VStack(spacing: 4) {
Image(systemName: "heart.fill")
.font(.title3)
Text("72")
.font(.headline)
Text("bpm")
.font(.caption2)
}
.foregroundStyle(.white)
.padding(8)
.glassEffect(.regular, in: .circle)
}
}
watchOS:ssä GPU-resurssit ovat rajallisemmat kuin iPhonessa. Käytä GlassEffectContainer-kontaineria huolellisesti ja minimoi samanaikaisten lasi-elementtien määrä.
macOS Tahoe 26
macOS:ssä Liquid Glass korvaa aiemman vibrancy-pohjaisen materiaalijärjestelmän. Ikkunoiden otsikkopalkit, sivupalkit ja työkalupalkit käyttävät automaattisesti uutta materiaalia. SwiftUI:n .glassEffect() toimii identtisesti kaikilla alustoilla, joten sama koodi tuottaa oikean lopputuloksen.
tvOS 26
Apple TV:ssä Liquid Glass hyödyntää fokuspohjaista navigaatiota. Kun fokus siirtyy Siri Remote -ohjaimella, lasi-elementit reagoivat parallaksi-efektillä, joka korostaa kolmiulotteisuutta entisestään. Se on visuaalisesti todella näyttävää isolla ruudulla.
Testaus ja iteraatio Xcode 26:ssa
Liquid Glass -käyttöliittymien kehittäminen vaatii visuaalista tarkistamista. Xcode 26 tarjoaa siihen hyviä työkaluja.
Preview-kanvaasi
SwiftUI Preview tukee täysin Liquid Glass -efektejä. Voit esikatsella eri taustavärejä ja -kuvia:
#Preview {
ZStack {
LinearGradient(
colors: [.blue, .purple],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
FloatingToolbar()
}
}
#Preview("Tumma tausta") {
ZStack {
Color.black
.ignoresSafeArea()
FloatingToolbar()
}
.preferredColorScheme(.dark)
}
#Preview("Kuvan päällä") {
ZStack {
Image("sampleBackground")
.resizable()
.aspectRatio(contentMode: .fill)
.ignoresSafeArea()
FloatingToolbar()
}
}
Useita Preview-variantteja eri taustoilla — se on paras tapa varmistaa, että lasiefektit näyttävät hyviltä kaikkialla.
Saavutettavuustarkastaja
Xcode 26:n Accessibility Inspector tukee Liquid Glass -elementtien tarkistamista. Se näyttää, miten elementit renderöidään eri saavutettavuusasetuksilla. Testaa aina sovellustasi Reduce Transparency, Increase Contrast ja Reduce Motion -asetuksilla päällä.
Instruments ja suorituskyvyn profilointi
Core Animation -instrumentti näyttää offscreen-renderöinnin määrän, joka on suoraan yhteydessä lasi-elementtien lukumäärään. Seuraa erityisesti:
- Offscreen render passes — jokainen erillinen lasiefekti lisää näitä
- GPU-muistin käyttö — tekstuurit kuluttavat muistia
- Frame drop -raportit — pudotukset 60fps:stä kertovat liian monesta lasi-elementistä
Liquid Glassin integrointi olemassa oleviin komponentteihin
Liquid Glass muuttaa automaattisesti monia SwiftUI:n vakiokomponentteja iOS 26:ssa. Tässä tärkeimmät.
TabView
TabView saa automaattisesti Liquid Glass -ulkoasun. Välilehtipalkki kutistuu käyttäjän vierittäessä ja laajenee takaisin vierityksen pysähtyessä:
TabView {
Tab("Koti", systemImage: "house") {
HomeView()
}
Tab("Haku", systemImage: "magnifyingglass") {
SearchView()
}
Tab("Profiili", systemImage: "person") {
ProfileView()
}
}
// Liquid Glass -ulkoasu on automaattinen iOS 26:ssa
Voit myös lisätä lisätoimintoja .tabViewBottomAccessory-modifikaattorilla — vaikkapa musiikkisoittimen minisoittimen kaltaisen elementin.
NavigationStack ja NavigationSplitView
Navigaatiopalkit saavat automaattisesti Liquid Glass -materiaalin. Reunaefektiin vaikutetaan .scrollEdgeEffectStyle-modifikaattorilla:
NavigationStack {
ScrollView {
contentView
}
.navigationTitle("Asetukset")
.scrollEdgeEffectStyle(.soft, for: .top)
}
Toolbar
Työkalupalkkien painikkeet hyödyntävät Liquid Glassia automaattisesti. Uusi ToolbarSpacer mahdollistaa painikkeiden ryhmittelyn:
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button("Tallenna") { save() }
}
ToolbarSpacer(.fixed)
ToolbarItem(placement: .primaryAction) {
Button("Jaa") { share() }
}
}
Liquid Glass UIKit-hybridisovelluksissa
Jos sovelluksesi käyttää sekä UIKit:iä että SwiftUI:ta, UIGlassEffect-luokka on käytettävissäsi UIKit-puolella:
// UIKit-puolella
let glassEffect = UIGlassEffect()
let visualEffectView = UIVisualEffectView(
effect: glassEffect
)
visualEffectView.layer.cornerRadius = 20
visualEffectView.layer.cornerCurve = .continuous
visualEffectView.clipsToBounds = true
view.addSubview(visualEffectView)
iOS 26:n Scene Bridging -ominaisuus (UIHostingSceneDelegate) mahdollistaa SwiftUI-kohtausten upottamisen UIKit-sovelluksiin, joten voit asteittain siirtyä käyttämään deklaratiivista .glassEffect()-API:a myös olemassa olevissa UIKit-projekteissa.
Edistyneet tekniikat: Dynaaminen lasiefekti tilakoneen avulla
Monimutkaisemmissa sovelluksissa saatat tarvita lasi-elementtejä, jotka vaihtavat dynaamisesti ulkoasuaan sovelluksen tilan mukaan. Rakennetaan tilakonepohjainen komponentti:
struct AdaptiveGlassHeader: View {
@State private var headerState: HeaderState = .expanded
@Namespace private var headerNamespace
enum HeaderState {
case expanded
case compact
case hidden
var glassVariant: Glass {
switch self {
case .expanded:
return .regular.tint(.blue.opacity(0.2))
case .compact:
return .regular.interactive()
case .hidden:
return .identity
}
}
var cornerRadius: CGFloat {
switch self {
case .expanded: return 24
case .compact: return 16
case .hidden: return 0
}
}
}
var body: some View {
GlassEffectContainer {
Group {
switch headerState {
case .expanded:
expandedHeader
case .compact:
compactHeader
case .hidden:
EmptyView()
}
}
.glassEffect(
headerState.glassVariant,
in: RoundedRectangle(
cornerRadius: headerState.cornerRadius
)
)
.glassEffectID(
"header",
in: headerNamespace
)
}
}
private var expandedHeader: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
Image(systemName: "waveform.circle")
.font(.largeTitle)
VStack(alignment: .leading) {
Text("Nyt soi")
.font(.headline)
Text("Kappaleen nimi")
.font(.subheadline)
.foregroundStyle(.secondary)
}
Spacer()
Button {
withAnimation(.spring) {
headerState = .compact
}
} label: {
Image(systemName: "chevron.up")
}
}
ProgressView(value: 0.4)
.tint(.white)
HStack {
Button(action: {}) {
Image(systemName: "backward.fill")
}
Spacer()
Button(action: {}) {
Image(systemName: "pause.fill")
.font(.title)
}
Spacer()
Button(action: {}) {
Image(systemName: "forward.fill")
}
}
}
.foregroundStyle(.white)
.padding(20)
.frame(maxWidth: .infinity)
}
private var compactHeader: some View {
HStack(spacing: 12) {
Image(systemName: "waveform.circle")
.font(.title2)
Text("Kappaleen nimi")
.font(.subheadline)
.lineLimit(1)
Spacer()
Button(action: {}) {
Image(systemName: "pause.fill")
}
Button {
withAnimation(.spring) {
headerState = .expanded
}
} label: {
Image(systemName: "chevron.down")
}
}
.foregroundStyle(.white)
.padding(.horizontal, 16)
.padding(.vertical, 12)
}
}
Tämä esimerkki näyttää, miten musiikkisoittimen minisoitin voi morffautua sulavasti laajennetusta kompaktiin muotoon ja takaisin — kaikki Liquid Glass -efektien kanssa.
Animaatiokurssien valinta
Morffausten visuaalinen laatu riippuu paljon animaatiokurssista. Tässä omat suositukseni:
- .bouncy — paras yleisvalinta painikkeille ja kelluviin elementteihin. Luonteva ja leikkisä
- .spring(duration: 0.4) — hallitumpi jousto pitkäkestoisempiin siirtymiin kuten paneelien laajentumiseen
- .smooth — rauhallinen siirtymä hienostuneisiin animaatioihin, kuten värinvaihtoihin
- .easeInOut(duration: 0.3) — perinteinen kurvi yksinkertaisiin tilanvaihtoihin
// Vertaile eri animaatiokursseja
withAnimation(.bouncy) {
// Energinen morffaus kelluvassa painikkeessa
isExpanded.toggle()
}
withAnimation(.spring(duration: 0.5, bounce: 0.2)) {
// Kontrolloitu jousto sivupaneelille
showPanel.toggle()
}
withAnimation(.smooth(duration: 0.3)) {
// Hienovarainen värin vaihto
selectedTab = .favorites
}
Oikean animaatiokurssin valinta tekee eron mekaanisen ja luonnollisen tuntuisen käyttöliittymän välillä. Ja tärkeä vinkki: testaa aina oikealla laitteella. Simulaattori ei toista ajoitusta täydellisesti.
Yhteenveto ja parhaat käytännöt
Liquid Glass on iOS 26:n merkittävin visuaalinen muutos ja tarjoaa kehittäjille tehokkaan työkalun modernien käyttöliittymien rakentamiseen. Käydään vielä tärkeimmät asiat läpi.
Suunnitteluperiaatteet
- Käytä Liquid Glassia vain navigaatioelementeissä — välilehtivalikot, työkalupalkit, kelluvat painikkeet
- Älä sovella kaikkialle — liikakäyttö heikentää visuaalista hierarkiaa
- Hyödynnä värjäystä hierarkian luomiseen — korostettavat elementit saavat väriä, toissijaiset pysyvät neutraaleina
- Käytä .clear-varianttia harkiten — vain mediarikasten taustojen päällä
Tekninen toteutus
- Ryhmitä aina vierekkäiset lasi-elementit
GlassEffectContainer-kontaineriin - Hyödynnä glassEffectID:tä morffauksiin sulavien tilansiirtymien luomiseksi
- Käytä contentShape()-modifikaattoria kosketusalueen laajentamiseen
- Vältä lasiefektejä vieritettävässä sisällössä suorituskykysyistä
Yhteensopivuus
- Käytä #available(iOS 26, *) -tarkistusta taaksepäin yhteensopivuuteen
- Tarjoa .ultraThinMaterial varatoteutuksena vanhemmille versioille
- Hyödynnä siirtymäkautta vähittäiseen käyttöönottoon
Liquid Glass edustaa Applen visiota käyttöliittymien tulevaisuudesta — materiaalit, jotka tuntuvat fyysisiltä, reagoivat ympäristöönsä ja luovat aidon kolmiulotteisen syvyyden. Nyt on oikea aika aloittaa sen toteuttaminen omissa sovelluksissasi.