Om een SwiftUI-scrollweergave aan korte inhoud aan te passen, meet je de binnenste inhoud en stel je de hoogte van de viewport in op de kleinste waarde van die meting en een gekozen limiet. Langere inhoud kan vervolgens binnen die limiet scrollen. In deze uitleg bouw je dat gedrag voor een klein labelpaneel in je eigen Mac-app.

Waarom laat een korte scrollweergave lege ruimte over?

Begin met twee dimensies uit elkaar te houden: de hoogte van de inhoud en de hoogte van de viewport waarin die wordt weergegeven. Apple beschrijft ScrollView als een weergave die een deel van de onderliggende inhoud toont en tijdens het scrollen het zichtbare deel aanpast. Een korte groep labels bepaalt op zichzelf niet welke viewporthoogte je wilt. Apple’s documentatie over ScrollView licht dit onderscheid toe.

Stel voor een compact bureaubladpaneel een expliciete regel voor de grootte vast: viewportHeight = min(contentHeight, maximumHeight). Meet de inhoud op de breedte die deze daadwerkelijk krijgt, inclusief eventuele opvulling die met de inhoud moet scrollen. Pas het resultaat toe op de buitenste scrollweergave. Swift’s min(_:_:) geeft de kleinste waarde terug, terwijl SwiftUI’s frame(width:height:alignment:) een vaste hoogte instelt wanneer het hoogteargument is opgegeven. Swift min(_:_:), Vast frame in SwiftUI.

Stel ter illustratie dat het maximum 240 punten is. Inhoud met een hoogte van 86 punten levert een viewport van 86 punten op; inhoud met een hoogte van 410 punten levert een viewport van 240 punten op, waarin de resterende inhoud bereikbaar is door te scrollen. Dit zijn illustratieve invoerwaarden, geen metingen uit het onderstaande voorbeeld.

Wijziging Wat wordt ermee geregeld? Stelt dit de gewenste compacte hoogte in?
Gemeten inhoudshoogte, begrensd en toegepast op de scrollweergave Viewporthoogte Ja, in de begrensde indeling hieronder
.scrollBounceBehavior(.basedOnSize) Stuitergedrag ten opzichte van de inhoudsgrootte Nee
.fixedSize(horizontal: false, vertical: true) op de labels De ideale verticale grootte van de inhoud Dit ondersteunt de meting; voor de buitenste viewport is nog steeds een hoogteregel nodig

Apple documenteert .basedOnSize als een configuratie voor stuitergedrag en fixedSize(horizontal:vertical:) als een manier om een weergave op de geselecteerde dimensies vast te zetten op de ideale grootte. Geen van beide beschrijvingen stelt een op de inhoud gebaseerde viewporthoogte in. Stuitergedrag van scrollweergaven, Vaste grootte per as.

Een compact paneel bouwen met een maximale scrollhoogte

Gebruik een actuele Xcode-installatie waarvan de macOS SDK de onGeometryChange(for:of:action:)-overload met één waarde beschikbaar stelt. Apple documenteert momenteel dat deze overload beschikbaar is vanaf macOS 13.0. Het volledige voorbeeld hieronder is bedoeld voor macOS 13.3 of hoger, omdat het ook scrollBounceBehavior(_:axes:) gebruikt, dat beschikbaar is vanaf macOS 13.3. Callback voor geometriewijzigingen, Beschikbaarheid van de stuitermodifier.

Maak een macOS-appproject met SwiftUI. Vervang de gegenereerde appdeclaratie en contentweergave door dit programma in één bestand en zorg dat er in het target maar één @main-declaratie staat. Alle labels zijn lokaal gegenereerde strings. Met de schakelaar Meer labels tonen wissel je tussen korte en lange inhoud.

import SwiftUI

@main
struct CompactLabelsApp: App {
    var body: some Scene {
        WindowGroup {
            CompactLabelsView()
        }
    }
}

struct CompactLabelsView: View {
    @State private var showMore = false
    @State private var viewportHeight: CGFloat = 240

    private var labels: [String] {
        if showMore {
            return (1...18).map { number in
                "Sample label \(number): a longer description that can wrap when the panel becomes narrower."
            }
        }
        return ["Sample label A", "Sample label B"]
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            Toggle("Show more labels", isOn: $showMore)

            ScrollView(.vertical) {
                VStack(alignment: .leading, spacing: 8) {
                    if labels.isEmpty {
                        Text("No labels to display")
                    } else {
                        ForEach(labels.indices, id: \.self) { index in
                            Text(labels[index])
                        }
                    }
                }
                .font(.body)
                .fixedSize(horizontal: false, vertical: true)
                .frame(maxWidth: .infinity, alignment: .leading)
                .padding(12)
                .onGeometryChange(for: CGFloat.self) { proxy in
                    min(proxy.size.height.rounded(.up), 240)
                } action: { newHeight in
                    viewportHeight = newHeight
                }
            }
            .frame(height: viewportHeight)
            .scrollBounceBehavior(.basedOnSize, axes: .vertical)
            .border(Color.secondary.opacity(0.4))

            Text("The label panel grows up to 240 points.")
                .font(.caption)
        }
        .padding(20)
        .frame(
            maxWidth: .infinity,
            maxHeight: .infinity,
            alignment: .topLeading
        )
        .frame(minWidth: 300, minHeight: 360)
    }
}

Het programma is bekeken, maar is hier niet gecompileerd of uitgevoerd, omdat deze omgeving geen Swift-compiler of macOS-runtime heeft. Bouw het in Xcode en voer de onderstaande controles uit voordat je het in een bestaande interface opneemt.

Waarom de meting binnen de scrollweergave hoort

De callback voor geometriewijzigingen observeert de binnenste stack met opvulling. Als je de buitenste scrollweergave meet, rapporteert die de viewport die je al hebt ingesteld. Dat levert een circulaire regel voor de grootte op, in plaats van de benodigde hoogte van de inhoud te achterhalen.

De transformatie geeft alleen de afgeronde, begrensde hoogte terug. Apple raadt aan geometrie om te zetten naar de waarde die je nodig hebt en brede updates bij frequente geometriewijzigingen te vermijden. In dit geval zijn wijzigingen van de scrollpositie niet relevant voor de berekening, en inhoud die hoger is dan de limiet levert nog steeds hetzelfde resultaat van 240 punten op. Apple’s richtlijnen voor geometriewijzigingen.

Omhoog afronden voorkomt dat je bewust een viewport toewijst die een fractie lager is dan de gemeten inhoud. De beginwaarde van 240 punten biedt een bruikbare viewport voordat de meting binnenkomt; het is een startwaarde, geen gemeten inhoudshoogte. De callback vervangt deze waarde vervolgens.

Waarom breedte en de volgorde van modifiers ertoe doen

Houd de horizontale grootte flexibel. Het voorbeeld gebruikt .fixedSize(horizontal: false, vertical: true), zodat de labels de beschikbare breedte kunnen benutten en hun ideale verticale grootte behouden. Apple waarschuwt dat het vastzetten van een ideale dimensie ertoe kan leiden dat een weergave buiten de grenzen van de bovenliggende weergave valt. Daarom is het vastzetten van beide dimensies geen geschikte standaard voor labels die moeten afbreken in een smal paneel. Apple’s documentatie over fixedSize(horizontal:vertical:).

De opvulling van 12 punten staat vóór de meetmodifier en telt daardoor mee in de gemeten inhoudshoogte. De rand staat na de hoogte-instelling van de buitenste weergave en markeert de grens van de viewport. Zo zie je makkelijker of ongebruikte ruimte bij het scrollpaneel hoort of bij het omliggende venster.

Korte inhoud, tekstafbreking en verkleinen controleren

Gebruik deze stappen om het gedrag in het voorbeeld te controleren en herhaal ze nadat je het paneel in de daadwerkelijke container van je app hebt geplaatst:

  1. Start met Meer labels tonen uitgeschakeld. De rand hoort de twee labels en hun opvulling te omsluiten, met het bijschrift er direct onder.
  2. Schakel Meer labels tonen in. Het paneel hoort tot de limiet van 240 punten te groeien. Scroll om te controleren of het laatste label bereikbaar blijft.
  3. Maak het venster smaller en breder. Labels die afbreken, horen van hoogte te veranderen zonder dat hun laatste regels verloren gaan.
  4. Schakel de optie weer uit. Het paneel hoort terug te krimpen tot de hoogte van de korte inhoud.
  5. Wijzig tijdelijk de korte array in []. De expliciete melding Geen labels om weer te geven hoort dan de inhoudshoogte te bepalen.
  6. Herhaal dit met de tekststijlen en omliggende bedieningselementen van je echte paneel.

De lege toestand is een bewuste keuze voor de interface: dit voorbeeld behoudt een melding en opvulling wanneer de array leeg is. Als het paneel helemaal moet verdwijnen, laat je de volledige scrollweergave voorwaardelijk weg wanneer er geen labels zijn. Dat is een afzonderlijke productkeuze, los van het meten van een paneel met inhoud.

Het omliggende voorbeeldvenster behoudt zijn minimale hoogte. De code bepaalt de grootte van de labelviewport; er wordt niet gevraagd om het Mac-venster te verkleinen zodra labels worden verwijderd. Eventuele ruimte onder het bijschrift hoort bij de buitenste indeling, buiten de scrollweergave met rand.

Resterende gaten of afgeknipte labels opsporen

Als het paneel hoog blijft, controleer dan eerst waar de meetmodifier is toegevoegd. Deze hoort de contentstack te observeren nadat de interne opvulling is toegepast. Controleer vervolgens of bovenliggende weergaven een expliciete hoogte of minimale hoogte hebben die ruimte rond het paneel reserveert. Apple documenteert een opgegeven framehoogte als een vaste dimensie. Een afzonderlijk buitenste frame kan dus ruimte behouden, ook wanneer de binnenste viewport krimpt. Framegrootte in SwiftUI.

Als de inhoud zelf onverwacht hoog blijft, verwijder dan verticale uitrekking uit de gemeten subtree. Vermijd voor dit patroon met kleine labels een verticale spacer of een contentframe waarvan de minimale hoogte is afgeleid van de viewport. Door zulke keuzes hangt de hoogte van de inhoud af van de viewport, waardoor de onafhankelijke meting die de groottesregel nodig heeft wordt ondermijnd.

Als de opvulling onderaan wordt afgeknipt, controleer dan of deze vóór de meting is inbegrepen. Als tekst na integratie anders afbreekt, controleer dan de daadwerkelijke breedte die de nieuwe bovenliggende weergave biedt. Een hoogte die op één breedte is gemeten, mag niet worden beschouwd als een permanente hoogte bij een andere breedte.

Zorg dat de limiet van 240 punten past bij de omringende interface. Het voorbeeld stelt een minimale vensterhoogte in die groot genoeg is voor de bedieningselementen, opvulling, het bijschrift en het begrensde paneel. Een lagere container vereist een kleiner ruimtebudget of een andere omringende indeling; deze vaste limiet berekent niet automatisch hoeveel ruimte er overblijft onder andere bedieningselementen.

Gebruik deze aanpak voor een bescheiden aantal labels die direct worden ingedeeld. Apple beschrijft LazyVStack als een weergave die items pas aanmaakt wanneer ze nodig zijn voor weergave op het scherm. Ga er daarom niet van uit dat je de binnenste stack door een lazy stack kunt vervangen en daarbij dezelfde meetstrategie voor de volledige inhoud behoudt. Voor een grote verzameling is een stabiele viewport met lazy inhoud meestal een geschikter ontwerp. Apple’s documentatie over LazyVStack.

Wanneer zijn metingen met AppKit en TextKit van toepassing?

Meet voor dit SwiftUI-labelpaneel de gerenderde SwiftUI-subtree. Een tekstmeting met AppKit zou een extra lay-outconfiguratie introduceren die overeen moet komen met de weergegeven labels.

Als je app al een NSScrollView gebruikt, behoud dan hetzelfde onderscheid tussen document en viewport. Apple definieert documentView als de weergave die wordt gescrold, terwijl contentSize de grootte is van de contentweergave van de scrollweergave. Ondanks de naam is contentSize geen meting van het volledige document. AppKit documentView, AppKit contentSize.

Voor een bestaande TextKit-indeling die is gebouwd rond NSLayoutManager rapporteert usedRect(for:) de omhullende rechthoek van glyphs die al in een tekstcontainer zijn ingedeeld; Apple zegt expliciet dat het opvragen ervan geen glyphs genereert en geen indeling uitvoert. Als een volledige containerindeling nodig is, is ensureLayout(for:) de gedocumenteerde methode om die indeling eerst af te dwingen. TextKit usedRect(for:), TextKit ensureLayout(for:).

Ook dan is de glyphrechthoek niet het volledige frame van de tekstweergave. Apple documenteert extra ruimte rondom de tekstcontainer via textContainerInset, evenals opvulling van regelafbrekingen binnen de container. Houd rekening met de daadwerkelijke configuratie van de tekstweergave in plaats van een glyphhoogte rechtstreeks over te nemen voor een scrollviewport. Deze methoden beschrijven de aanpak met NSLayoutManager; het is geen universele methode om elk tekstonderdeel te schalen. AppKit textContainerInset.

Voor een klein SwiftUI-labelpaneel is de praktische oplossing om de breedte flexibel te houden, de binnenste inhoud inclusief opvulling te meten en de hoogte van de buitenste viewport te begrenzen. Controleer het resultaat bij zowel korte als lange inhoud en test het vervolgens binnen de daadwerkelijke indeling van de bovenliggende weergave, zodat de omliggende beperkingen hetzelfde compacte gedrag mogelijk maken.

Bronnen