Liquid Glass SwiftUI:ssa: Käytännön opas iOS 26:n uuteen suunnittelukieleen

Käytännön opas Liquid Glassin toteuttamiseen SwiftUI:ssa iOS 26:lle. Opi glassEffect-modifikaattori, GlassEffectContainer, morffausanimaatiot ja suorituskyvyn optimointi koodiesimerkkien avulla.

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:

  1. Elementtien on oltava samassa GlassEffectContainer-kontainerissa
  2. Jokaisella näkymällä on oltava yksilöllinen glassEffectID jaetussa Namespace-avaruudessa
  3. 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.

Tietoa Kirjoittajasta Editorial Team

Our team of expert writers and editors.