TextView.swift

Text en Label opmaak uitgelegd — gegenereerd uit broncode met commentaar

Concepten in dit bestand:

import SwiftUI

Hoofdview

struct TextView: View {
    var body: some View {

NavigationStack geeft ons een navigatiebalk met een titel.

        NavigationStack {

ScrollView zorgt dat alle voorbeelden bereikbaar zijn, ook als ze niet op een enkel scherm passen.

            ScrollView {

VStack stapelt alle secties verticaal onder elkaar. alignment: .leading lijnt alles links uit.

                VStack(alignment: .leading, spacing: 0) {
                    sectieGrootte
                    sectieGewicht
                    sectieKleur
                    sectieStijl
                    sectieDecoratie
                    sectieSpateing
                    sectieUitlijning
                    sectieTruncatie
                    sectieOpmaakCombinaties
                    sectieAttributed
                    sectieLabel
                    sectieSpeciaal
                }
                .padding(.bottom, 32)
            }
            .navigationTitle("Tekst en labels")
            .navigationSubtitle("Opmaak en manipulatie")
            #if os(iOS)
            .navigationBarTitleDisplayMode(.large)
            #endif
        }
    }

Lettergrootte

Een computed property die een sectie teruggeeft. Door elke sectie als eigen property te schrijven blijft 'body' leesbaar.

    private var sectieGrootte: some View {
        Sectie(titel: "Lettergrootte — .font") {

SwiftUI heeft vaste tekststijlen die meeschalen met de tekstgrootte-instelling van het apparaat (Dynamic Type). Grotere tekst is toegankelijker voor slechtzienden.

            Voorbeeld(label: ".largeTitle") {
                Text("Grote titel").font(.largeTitle)
            }
            Voorbeeld(label: ".title") {
                Text("Titel").font(.title)
            }
            Voorbeeld(label: ".title2") {
                Text("Titel 2").font(.title2)
            }
            Voorbeeld(label: ".title3") {
                Text("Titel 3").font(.title3)
            }
            Voorbeeld(label: ".headline") {
                Text("Headline").font(.headline)
            }
            Voorbeeld(label: ".subheadline") {
                Text("Subheadline").font(.subheadline)
            }
            Voorbeeld(label: ".body") {
                Text("Body (standaard)").font(.body)
            }
            Voorbeeld(label: ".callout") {
                Text("Callout").font(.callout)
            }
            Voorbeeld(label: ".footnote") {
                Text("Footnote").font(.footnote)
            }
            Voorbeeld(label: ".caption") {
                Text("Caption").font(.caption)
            }
            Voorbeeld(label: ".caption2") {
                Text("Caption 2").font(.caption2)
            }

Vaste lettergrootte met .system(size:). Dit schaalt NIET mee met Dynamic Type.

            Voorbeeld(label: ".system(size: 28)") {
                Text("Vaste grootte 28").font(.system(size: 28))
            }
        }
    }

Letterdikte

    private var sectieGewicht: some View {
        Sectie(titel: "Letterdikte — .fontWeight") {

fontWeight bepaalt hoe dik de letters zijn. Van dun naar zwaar: ultraLight → thin → light → regular

            Voorbeeld(label: ".ultraLight") {
                Text("UltraLight").fontWeight(.ultraLight).font(.title3)
            }
            Voorbeeld(label: ".thin") {
                Text("Thin").fontWeight(.thin).font(.title3)
            }
            Voorbeeld(label: ".light") {
                Text("Light").fontWeight(.light).font(.title3)
            }
            Voorbeeld(label: ".regular") {
                Text("Regular").fontWeight(.regular).font(.title3)
            }
            Voorbeeld(label: ".medium") {
                Text("Medium").fontWeight(.medium).font(.title3)
            }
            Voorbeeld(label: ".semibold") {
                Text("Semibold").fontWeight(.semibold).font(.title3)
            }
            Voorbeeld(label: ".bold") {
                Text("Bold").fontWeight(.bold).font(.title3)
            }
            Voorbeeld(label: ".heavy") {
                Text("Heavy").fontWeight(.heavy).font(.title3)
            }
            Voorbeeld(label: ".black") {
                Text("Black").fontWeight(.black).font(.title3)
            }

.bold() is een snelkoppeling voor .fontWeight(.bold)

            Voorbeeld(label: ".bold() snelkoppeling") {
                Text("Vet via .bold()").bold()
            }
        }
    }

Kleur

    private var sectieKleur: some View {
        Sectie(titel: "Kleur — .foregroundColor / .foregroundStyle") {

.foregroundColor past de tekstkleur aan. De systeemkleuren passen automatisch aan bij licht/donker modus.

            Voorbeeld(label: ".primary") {
                Text("Primaire tekst").foregroundColor(.primary)
            }
            Voorbeeld(label: ".secondary") {
                Text("Secundaire tekst").foregroundColor(.secondary)
            }
            Voorbeeld(label: ".accentColor") {
                Text("Accentkleur").foregroundColor(.accentColor)
            }
            Voorbeeld(label: ".red") {
                Text("Rood").foregroundColor(.red)
            }
            Voorbeeld(label: ".orange") {
                Text("Oranje").foregroundColor(.orange)
            }
            Voorbeeld(label: ".green") {
                Text("Groen").foregroundColor(.green)
            }
            Voorbeeld(label: ".blue") {
                Text("Blauw").foregroundColor(.blue)
            }
            Voorbeeld(label: ".purple") {
                Text("Paars").foregroundColor(.purple)
            }

.foregroundStyle accepteert ook een LinearGradient. Zo krijgt de tekst een verloopkleur.

            Voorbeeld(label: "LinearGradient") {
                Text("Verloopkleur")
                    .font(.title2.bold())
                    .foregroundStyle(
                        LinearGradient(
                            colors: [.orange, .pink, .purple],
                            startPoint: .leading,
                            endPoint: .trailing
                        )
                    )
            }

Aangepaste kleur via RGB-waarden (0.0 tot 1.0)

            Voorbeeld(label: "Color(red:green:blue:)") {
                Text("Eigen kleur")
                    .foregroundColor(Color(red: 0.2, green: 0.7, blue: 0.5))
            }

Kleur met transparantie via .opacity()

            Voorbeeld(label: ".opacity(0.4)") {
                Text("Half transparant")
                    .foregroundColor(.blue.opacity(0.4))
                    .font(.title3)
            }
        }
    }

Stijl en ontwerp

    private var sectieStijl: some View {
        Sectie(titel: "Lettertypestijl — .fontDesign / .italic()") {

.fontDesign bepaalt de algemene stijl van het systeemlettertype.

            Voorbeeld(label: ".default") {
                Text("Standaard ontwerp").font(.title3.weight(.medium))
            }
            Voorbeeld(label: ".rounded") {
                Text("Afgerond ontwerp")
                    .font(.title3.weight(.medium))
                    .fontDesign(.rounded)
            }
            Voorbeeld(label: ".serif") {
                Text("Serif ontwerp")
                    .font(.title3.weight(.medium))
                    .fontDesign(.serif)
            }
            Voorbeeld(label: ".monospaced") {
                Text("Monospaced ontwerp")
                    .font(.title3.weight(.medium))
                    .fontDesign(.monospaced)
            }

.italic() maakt de tekst cursief.

            Voorbeeld(label: ".italic()") {
                Text("Cursieve tekst").italic()
            }

Combinatie van meerdere stijlen tegelijk

            Voorbeeld(label: "Bold + italic + rounded") {
                Text("Combinatie")
                    .font(.title2.bold())
                    .italic()
                    .fontDesign(.rounded)
                    .foregroundColor(.indigo)
            }
        }
    }

Decoratie

    private var sectieDecoratie: some View {
        Sectie(titel: "Decoratie — underline / strikethrough") {

.underline() tekent een lijn onder de tekst. De kleur is standaard gelijk aan de tekstkleur.

            Voorbeeld(label: ".underline()") {
                Text("Onderstreepte tekst").underline()
            }
            Voorbeeld(label: ".underline(color: .red)") {
                Text("Rode onderstreping").underline(color: .red)
            }

.strikethrough() tekent een lijn door de tekst heen. Handig voor afgeronde of verwijderde items.

            Voorbeeld(label: ".strikethrough()") {
                Text("Doorgestreepte tekst").strikethrough()
            }
            Voorbeeld(label: ".strikethrough(color: .orange)") {
                Text("Oranje doorstreping")
                    .strikethrough(color: .orange)
                    .foregroundColor(.secondary)
            }

Combinatie: doorstreping + andere kleur

            Voorbeeld(label: "Oud en nieuw naast elkaar") {
                HStack(spacing: 8) {
                    Text("49,99")
                        .strikethrough()
                        .foregroundColor(.secondary)
                    Text("29,99")
                        .bold()
                        .foregroundColor(.green)
                }
            }
        }
    }

Spatiering

    private var sectieSpateing: some View {
        Sectie(titel: "Spatiering — tracking / kerning / lineSpacing") {

.tracking voegt gelijkmatige ruimte toe tussen alle letters. Positief = meer ruimte, negatief = minder ruimte.

            Voorbeeld(label: ".tracking(-2)") {
                Text("Nauw").tracking(-2).font(.title3)
            }
            Voorbeeld(label: ".tracking(0) standaard") {
                Text("Standaard").tracking(0).font(.title3)
            }
            Voorbeeld(label: ".tracking(6)") {
                Text("Wijd").tracking(6).font(.title3)
            }
            Voorbeeld(label: ".tracking(12)") {
                Text("Heel wijd").tracking(12).font(.title3)
            }

.kerning is subtieler dan tracking: past de ruimte aan tussen specifieke tekenparen (A-V, T-o, etc.).

            Voorbeeld(label: ".kerning(4)") {
                Text("Kerning voorbeeld").kerning(4)
            }

.baselineOffset verschuift de tekst omhoog (positief) of omlaag (negatief) ten opzichte van de basislijn. Handig voor voet- of superscript-effecten.

            Voorbeeld(label: ".baselineOffset") {
                HStack(alignment: .bottom, spacing: 0) {
                    Text("H")
                        .font(.title)
                    Text("2")
                        .font(.caption)
                        .baselineOffset(-6)
                    Text("O")
                        .font(.title)
                }
            }

.lineSpacing regelt de ruimte tussen regels bij meerregelige tekst.

            Voorbeeld(label: ".lineSpacing(2) standaard") {
                Text("Eerste regel\nTweede regel\nDerde regel")
                    .lineSpacing(2)
            }
            Voorbeeld(label: ".lineSpacing(12)") {
                Text("Eerste regel\nTweede regel\nDerde regel")
                    .lineSpacing(12)
            }
        }
    }

Uitlijning en afkapping

    private var sectieUitlijning: some View {
        Sectie(titel: "Uitlijning — .multilineTextAlignment") {

.multilineTextAlignment bepaalt hoe meerregelige tekst uitgelijnd is. Let op: dit heeft alleen effect als de Text breder is dan een regel.

            Voorbeeld(label: ".leading (links, standaard)") {
                Text("Dit is een langere zin die over meerdere regels loopt.")
                    .multilineTextAlignment(.leading)
                    .frame(maxWidth: .infinity, alignment: .leading)
            }
            Voorbeeld(label: ".center (gecentreerd)") {
                Text("Dit is een langere zin die over meerdere regels loopt.")
                    .multilineTextAlignment(.center)
                    .frame(maxWidth: .infinity, alignment: .center)
            }
            Voorbeeld(label: ".trailing (rechts)") {
                Text("Dit is een langere zin die over meerdere regels loopt.")
                    .multilineTextAlignment(.trailing)
                    .frame(maxWidth: .infinity, alignment: .trailing)
            }
        }
    }

Afkapping en regellimiet

    private var sectieTruncatie: some View {
        Sectie(titel: "Afkapping — .lineLimit / .truncationMode") {

.lineLimit beperkt de tekst tot een aantal regels. De rest wordt afgekapt met een '...'.

            Voorbeeld(label: ".lineLimit(1)") {
                Text("Dit is een heel lange zin die zeker niet op een regel past maar toch maar een regel mag innemen.")
                    .lineLimit(1)
            }
            Voorbeeld(label: ".lineLimit(2)") {
                Text("Dit is een heel lange zin die zeker niet op een regel past maar toch maar twee regels mag innemen.")
                    .lineLimit(2)
            }
            Voorbeeld(label: ".lineLimit(nil) geen limiet") {
                Text("Dit is een heel lange zin die zeker niet op een regel past en alle ruimte mag innemen die hij nodig heeft.")
                    .lineLimit(nil)
            }

.truncationMode bepaalt waar de '...' verschijnt.

            Voorbeeld(label: ".truncationMode(.tail) standaard") {
                Text("Afkappen aan het einde van de tekst hier")
                    .lineLimit(1)
                    .truncationMode(.tail)
            }
            Voorbeeld(label: ".truncationMode(.middle)") {
                Text("Afkappen in het midden van de tekst hier")
                    .lineLimit(1)
                    .truncationMode(.middle)
            }
            Voorbeeld(label: ".truncationMode(.head)") {
                Text("Afkappen aan het begin van de tekst hier")
                    .lineLimit(1)
                    .truncationMode(.head)
            }

.allowsTightening laat SwiftUI de letterafstand iets verkleinen om te voorkomen dat tekst afgekapt wordt.

            Voorbeeld(label: ".allowsTightening(true)") {
                Text("Iets compacter als het krap is")
                    .lineLimit(1)
                    .allowsTightening(true)
            }

.minimumScaleFactor schaalt de tekst kleiner als hij niet past. 0.5 betekent: tot de helft van de originele grootte verkleinen.

            Voorbeeld(label: ".minimumScaleFactor(0.5)") {
                Text("Kleinere tekst als het niet past")
                    .font(.title)
                    .lineLimit(1)
                    .minimumScaleFactor(0.5)
            }
        }
    }

Opmaak combinaties

    private var sectieOpmaakCombinaties: some View {
        Sectie(titel: "Combinaties") {

.textCase past automatisch hoofd- of kleine letters toe zonder de onderliggende string te wijzigen.

            Voorbeeld(label: ".textCase(.uppercase)") {
                Text("automatisch hoofdletters").textCase(.uppercase)
            }
            Voorbeeld(label: ".textCase(.lowercase)") {
                Text("AUTOMATISCH KLEINE LETTERS").textCase(.lowercase)
            }

.monospacedDigit zorgt dat alle cijfers even breed zijn. Handig bij tellers of tabellen zodat de tekst niet verspringt.

            Voorbeeld(label: ".monospacedDigit()") {
                VStack(alignment: .trailing, spacing: 4) {
                    Text("1.234,56").monospacedDigit()
                    Text("99,00").monospacedDigit()
                    Text("10.000,10").monospacedDigit()
                }
                .font(.body)
            }

Text + Text combineert twee tekststukken tot een geheel. Elk deel kan zijn eigen opmaak hebben.

            Voorbeeld(label: "Text + Text samenvoegen") {
                Text("Normaal ")
                + Text("vet ").bold()
                + Text("en ")
                + Text("gekleurd").foregroundColor(.orange)
                + Text(" in een zin.")
            }

Opmaak via string-interpolatie met format:

            Voorbeeld(label: "Getal met format:") {
                VStack(alignment: .leading, spacing: 4) {
                    Text(1234567.89, format: .number)
                    Text(1234567.89, format: .currency(code: "EUR"))
                    Text(0.856, format: .percent)
                    Text(Date.now, format: .dateTime.day().month().year())
                }
                .font(.body.monospacedDigit())
            }
        }
    }

AttributedString

    private var sectieAttributed: some View {
        Sectie(titel: "AttributedString — per-karakter opmaak") {

AttributedString laat je per teken of woord andere opmaak instellen. Dit is de moderne Swift-versie van NSAttributedString.

            Voorbeeld(label: "Gemengde opmaak") {
                Text(gemaakteAttributedString)
            }

Markdown in een String literal wordt automatisch herkend. Ondersteunde opmaak: **vet**, *cursief*, `code`, [link](url)

            Voorbeeld(label: "Markdown in Text") {
                Text("Dit is **vet**, dit is *cursief* en dit is `code`.")
            }
            Voorbeeld(label: "Markdown link") {
                Text("Bezoek [Apple](https://apple.com) voor meer info.")
            }
        }
    }

Bouw een AttributedString op met verschillende stijlen per woord.

    private var gemaakteAttributedString: AttributedString {
        var tekst = AttributedString("Groot ")
        tekst.font = .title2.bold()
        tekst.foregroundColor = .indigo
        var normaal = AttributedString("normaal ")
        normaal.font = .body
        var rood = AttributedString("rood ")
        rood.foregroundColor = .red
        rood.font = .body.bold()
        var onderstreept = AttributedString("onderstreept")
        onderstreept.underlineStyle = .single
        onderstreept.font = .body

AttributedString-stukken samenvoegen met +=

        tekst += normaal
        tekst += rood
        tekst += onderstreept
        return tekst
    }

Label

    private var sectieLabel: some View {
        Sectie(titel: "Label — tekst met icoontje") {

Label combineert een SF Symbol-icoontje met een tekst. Het past zich aan het platform aan: in toolbars toont het soms alleen het icoontje, elders beide.

            Voorbeeld(label: "Standaard Label") {
                Label("Instellingen", systemImage: "gear")
            }
            Voorbeeld(label: "Label met kleur") {
                Label("Favorieten", systemImage: "star.fill")
                    .foregroundColor(.orange)
            }
            Voorbeeld(label: ".labelStyle(.iconOnly)") {
                Label("Alleen icoontje", systemImage: "bell.fill")
                    .labelStyle(.iconOnly)
                    .foregroundColor(.blue)
            }
            Voorbeeld(label: ".labelStyle(.titleOnly)") {
                Label("Alleen tekst", systemImage: "bell.fill")
                    .labelStyle(.titleOnly)
            }
            Voorbeeld(label: ".labelStyle(.titleAndIcon)") {
                Label("Tekst en icoontje", systemImage: "heart.fill")
                    .labelStyle(.titleAndIcon)
                    .foregroundColor(.red)
            }

Label met een eigen view als icoontje

            Voorbeeld(label: "Label met eigen icoontje") {
                Label {
                    Text("Eigen icoontje")
                        .foregroundColor(.primary)
                } icon: {
                    RoundedRectangle(cornerRadius: 4)
                        .fill(Color.purple)
                        .frame(width: 20, height: 20)
                        .overlay(
                            Text("A")
                                .font(.caption.bold())
                                .foregroundColor(.white)
                        )
                }
            }
        }
    }

Speciale modifiers

    private var sectieSpeciaal: some View {
        Sectie(titel: "Speciale modifiers") {

.redacted(reason: .placeholder) vervangt de tekst met een grijze balk. Handig als laadscherm terwijl data nog opgehaald wordt (skeleton UI).

            Voorbeeld(label: ".redacted(.placeholder)") {
                VStack(alignment: .leading, spacing: 6) {
                    Text("Dit is een titel die verborgen is")
                        .font(.headline)
                    Text("En dit is een langere beschrijvingstekst")
                        .font(.body)
                        .foregroundColor(.secondary)
                }
                .redacted(reason: .placeholder)
            }

.textSelection(.enabled) laat de gebruiker de tekst selecteren door er lang op te drukken (zoals in een browser).

            Voorbeeld(label: ".textSelection(.enabled)") {
                Text("Druk lang om te selecteren")
                    .textSelection(.enabled)
            }

.privacySensitive() verbergt de tekst in screenshots, app-switcher en andere plaatsen waar privacy belangrijk is.

            Voorbeeld(label: ".privacySensitive()") {
                Text("Geheime waarde: 1234")
                    .privacySensitive()
            }

Tekst in een Badge toont een getal of label bovenop een icoontje. .badge is een modifier op een List-rij of TabItem.

            Voorbeeld(label: "Text als teller badge") {
                HStack {
                    Image(systemName: "envelope.fill")
                        .font(.title2)
                        .foregroundColor(.blue)
                    Text("12")
                        .font(.caption2.bold())
                        .foregroundColor(.white)
                        .padding(.horizontal, 6)
                        .padding(.vertical, 2)
                        .background(Color.red)
                        .clipShape(Capsule())
                }
            }
        }
    }
}

Herbruikbare hulpviews

Sectie groepeert een reeks voorbeelden onder een gedeeld koptekst. Generieke parameter 'Content' laat toe dat elk soort View als inhoud meegegeven wordt.

struct Sectie<Content: View>: View {
    let titel: String

@ViewBuilder zorgt dat de 'inhoud' closure meerdere views kan teruggeven.

    @ViewBuilder let inhoud: () -> Content
    var body: some View {
        VStack(alignment: .leading, spacing: 0) {

Sectie-koptekst

            Text(titel)
                .font(.headline)
                .foregroundColor(.primary)
                .padding(.horizontal, 16)
                .padding(.top, 28)
                .padding(.bottom, 12)

Scheidingslijn onder de koptekst

            Rectangle()
                .fill(Color.accentColor.opacity(0.4))
                .frame(height: 2)
                .padding(.horizontal, 16)

Alle voorbeelden

            VStack(alignment: .leading, spacing: 0) {
                inhoud()
            }
            .padding(.top, 4)
        }
    }
}

Voorbeeld toont een label (de modifier-naam) en het visuele resultaat ernaast.

struct Voorbeeld<Content: View>: View {
    let label: String
    @ViewBuilder let inhoud: () -> Content
    var body: some View {
        HStack(alignment: .center, spacing: 12) {

Label: de naam van de modifier, vaste breedte zodat alles uitlijnt.

            Text(label)
                .font(.system(size: 11, design: .monospaced))
                .foregroundColor(.secondary)
                .frame(width: 200, alignment: .leading)
                .lineLimit(2)
                .minimumScaleFactor(0.8)
            Divider().frame(height: 32)

Het visuele resultaat van de modifier

            inhoud()
                .frame(maxWidth: .infinity, alignment: .leading)
        }
        .padding(.horizontal, 16)
        .padding(.vertical, 10)

Elke tweede rij een lichte achtergrond voor leesbaarheid. Dit is een vereenvoudiging; in een echte app gebruik je List.

        .background(Color.primary.opacity(0.03))
        .overlay(
            Rectangle()
                .fill(Color.primary.opacity(0.06))
                .frame(height: 1),
            alignment: .bottom
        )
    }
}

Preview

#Preview {
    TextView()
}