Bereken de passende grootte van de voorbeeldweergave op basis van de beschikbare vensterruimte en geef de bedieningselementen vervolgens dezelfde hoogte om zijbedieningselementen af te stemmen op een SwiftUI-voorbeeldweergave met beeldverhouding. Dit macOS-voorbeeld houdt een synthetische voorbeeldweergave proportioneel en laat overlopende bedieningselementen scrollen.
De taak is een aanpasbare desktopeditor met links een voorbeeldweergave en rechts een bedieningspaneel met vaste breedte. Hier betekent ‘voorbeeldweergave’ de inhoud binnen je actieve app. Het doel is om de boven- en onderranden van de lay-out van beide panelen op één lijn te houden wanneer het venster van grootte verandert.
Welke hoogte moeten de bedieningselementen volgen?
Begin met het definiëren van de rechthoek die je wilt afstemmen. Een voorbeeldweergave die met behoud van de beeldverhouding wordt passend gemaakt, kan minder ruimte innemen dan de omringende container. Als de bedieningselementen de hoogte van de container volgen, kunnen ze onder de zichtbare voorbeeldweergave uitsteken, zelfs wanneer beide weergaven correct zijn ingedeeld.
Apple definieert de beeldverhouding als breedte gedeeld door hoogte. Voor aanpasbare inhoud behoudt aspectRatio(_:contentMode:) met .fit die verhouding terwijl de inhoud binnen de voorgestelde ruimte past. Het ellipsevoorbeeld in de documentatie laat zien waarom de passende vorm en een omsluitend vierkant frame verschillende afmetingen kunnen hebben. Apple-documentatie over beeldverhoudingen.
Deze implementatie maakt de passende rechthoek expliciet. Er wordt een vaste breedte gereserveerd voor de bedieningselementen, met een tussenruimte tussen de panelen. Vervolgens wordt de voorbeeldweergave passend gemaakt binnen de resterende breedte en de beschikbare hoogte. Beide panelen krijgen de berekende hoogte van de voorbeeldweergave.
Dat is het indelingsbeleid voor dit voorbeeld: de voorbeeldweergave bepaalt de hoogte van het zichtbare gebied van de bedieningselementen. Een lange lijst met bedieningselementen scrolt binnen dat gebied, zodat de voorbeeldweergave niet hoger wordt. Apple beschrijft ScrollView als een weergave die een verplaatsbaar deel van de onderliggende inhoud toont binnen een scrollbaar gebied. Apple-documentatie over scrollweergaven.
Bereken de passende rechthoek één keer
Laat W en H de beschikbare afmetingen van de inhoud na de buitenste opvulling zijn, C de breedte van het bedieningspaneel, G de tussenruimte en R de breedte-hoogteverhouding van de voorbeeldweergave. De berekening is:
remainingWidth = max(0, W - C - G)
previewWidth = min(remainingWidth, H * R)
previewHeight = previewWidth / R
controlsHeight = previewHeight
De eerste begrenzing voorkomt dat de voorbeeldweergave de horizontale ruimte van de bedieningselementen inneemt. De tweede voorkomt dat een breed venster een voorbeeldweergave oplevert die hoger is dan het beschikbare gebied. Door de hoogte te berekenen op basis van de gekozen breedte blijft de verhouding in beide gevallen behouden.
Stel bijvoorbeeld dat het inhoudsgebied vóór de opvulling 960 bij 600 punten is. Met 16 punten opvulling aan elke rand, een bedieningspaneel van 220 punten breed, een tussenruimte van 16 punten en een voorbeeldweergave met verhouding 16:9 is het beschikbare gebied 928 bij 568 punten. De resterende breedte voor de voorbeeldweergave is 692 punten, wat een hoogte van 389,25 punten oplevert. Het zichtbare gebied van de bedieningselementen wordt dus 220 bij 389,25 punten.
Dit zijn illustratieve invoerwaarden, geen gemeten resultaten van een applicatie. Ze laten ook zien waarom de bedieningselementen afstemmen op de volledige beschikbare hoogte van 568 punten tot een verkeerde uitlijning zou leiden.
Gebruik CGFloat voor de afmetingen van de lay-out en een positieve, eindige verhouding. In Swift maakt let ratio: CGFloat = 16.0 / 9.0 de bedoelde berekening met drijvende komma expliciet. Als je app later instelbare verhoudingen accepteert, valideer die dan voordat je deelt.
Bouw het volledige macOS-voorbeeld
Maak in Xcode een macOS SwiftUI-app en stel het implementatiedoel in op macOS 13 of nieuwer. Vervang de gegenereerde app- en content-viewdeclaraties door dit ene bestand, zodat er precies één @main-declaratie in het doel staat. Voor dit voorbeeld zijn geen assets of externe pakketten nodig.
De vereiste versie volgt uit de API voor venstergrootte die hier wordt gebruikt. De scene gebruikt .contentMinSize, waarmee volgens Apple de minimale venstergrootte wordt afgeleid van de inhoud, zonder maximale grootte op te leggen. Apple-documentatie over het aanpassen van vensters, Apple-documentatie over minimale inhoudsgrootte.
import SwiftUI
@main
struct MatchedPreviewApp: App {
var body: some Scene {
WindowGroup {
PreviewEditor()
.frame(minWidth: 640, minHeight: 360)
}
.windowResizability(.contentMinSize)
}
}
struct PreviewEditor: View {
@State private var intensity: Double = 0.5
@State private var showsLabel = true
private let ratio: CGFloat = 16.0 / 9.0
private let controlsWidth: CGFloat = 220
private let gap: CGFloat = 16
var body: some View {
GeometryReader { geometry in
let availableHeight = max(0, geometry.size.height)
let remainingWidth = max(
0,
geometry.size.width - controlsWidth - gap
)
let previewWidth = min(
remainingWidth,
availableHeight * ratio
)
let previewHeight = previewWidth / ratio
HStack(alignment: .top, spacing: gap) {
RoundedRectangle(cornerRadius: 12)
.fill(Color.blue.opacity(0.2 + intensity * 0.7))
.aspectRatio(ratio, contentMode: .fit)
.overlay {
Text("Sample composition")
.font(.headline)
.opacity(showsLabel ? 1 : 0)
}
.frame(
width: previewWidth,
height: previewHeight
)
ScrollView(.vertical) {
VStack(alignment: .leading, spacing: 16) {
Text("Preview controls")
.font(.headline)
Toggle("Show label", isOn: $showsLabel)
VStack(alignment: .leading, spacing: 8) {
Text("Color intensity")
Slider(value: $intensity, in: 0...1)
}
ForEach(1...8, id: \.self) { index in
Text("Sample setting \(index)")
.font(.caption)
}
}
.padding(16)
.frame(maxWidth: .infinity, alignment: .leading)
}
.frame(
width: controlsWidth,
height: previewHeight
)
.background(Color.gray.opacity(0.08))
}
.frame(
width: geometry.size.width,
height: geometry.size.height,
alignment: .center
)
}
.padding(16)
}
}
GeometryReader levert de grootte van het gebied dat aan de editor is toegewezen. Apple beschrijft deze als een weergave die inhoud definieert op basis van haar eigen grootte en coördinatenruimte, met een flexibele voorkeursgrootte. Door de opvulling buiten de reader te plaatsen, gebruikt de berekening het ingesprongen inhoudsgebied. Apple-documentatie over GeometryReader.
De twee vaste frames bepalen de gedeelde hoogte. Apple beschrijft frame(width:height:alignment:) als het maken van een onzichtbaar frame met de opgegeven afmetingen. De achtergrond van de bedieningselementen komt na dat frame, zodat deze het zichtbare bedieningsgebied bedekt, ook de ruimte onder een korte lijst. Apple-documentatie over vaste frames.
Het laatste frame centreert de volledige rij binnen de reader. Het vergroot geen van beide panelen. Daardoor kan een hoog venster ongebruikte ruimte boven en onder de rij bevatten, terwijl de bedieningselementen nog steeds op de voorbeeldweergave aansluiten.
Behoud status en levenscyclusgedrag tijdens het aanpassen van de grootte
Houd door de gebruiker aanpasbare waarden gescheiden van berekende geometrie. In het voorbeeld zijn intensity en showsLabel statuswaarden; de breedte en hoogte van de voorbeeldweergave zijn lokale waarden die worden afgeleid van de huidige beschikbare grootte.
Apple beschrijft status als door SwiftUI beheerde opslag waarvan wijzigingen afhankelijke delen van de weergavehiërarchie bijwerken. Zo kunnen de waarden van de schuifregelaar en schakelaar in de editor blijven die deze beheert. Apple-documentatie over status.
De indelingsberekening bevat geen statuswijzigingen, verschijningscallbacks, asynchroon werk of meetterugkoppeling. Wanneer het formaat verandert, veranderen de afmetingen die door dezelfde weergavestructuur worden gebruikt. Het label blijft aanwezig en verandert van dekking wanneer de schakelaar verandert.
Behoud bij het aanpassen hiervan aan een bestaande editor het eigenaarschap van het model en de typen bindings. Pas het formaatbeleid toe rond de voorbeeldweergave en de bedieningselementen; verplaats het aanmaken van het model niet naar de geometrieberekening. Voor de berekening in dit voorbeeld zijn alleen de beschikbare afmetingen en de gekozen verhouding nodig.
Controleer het aanpassen van de grootte, overlopen en de volgorde van modifiers
Gebruik de volgende afmetingen als controle van de berekening. Ze beschrijven het inhoudsgebied vóór de buitenste opvulling van het voorbeeld, niet het buitenste frame van het venster. De verwachte waarden zijn afgerond op twee decimalen.
| Inhoudsgebied | Grootte voorbeeldweergave | Zichtbaar gebied bedieningselementen |
|---|---|---|
| 960 × 600 punten | 692 × 389.25 | 220 × 389.25 |
| 640 × 360 punten | 372 × 209.25 | 220 × 209.25 |
| 1200 × 400 punten | 654.22 × 368 | 220 × 368 |
| 700 × 900 punten | 432 × 243 | 220 × 243 |
De berekening is gecontroleerd met een kleine Python-berekening, inclusief assertions dat de passende voorbeeldweergave binnen de beschikbare hoogte blijft en dat beide panelen plus de tussenruimte horizontaal passen. Swift en Xcode waren niet beschikbaar in de onderzoeksomgeving, dus het volledige SwiftUI-programma is geïnspecteerd, maar niet gecompileerd of uitgevoerd.
Voer de app in Xcode uit en wijzig het formaat door brede, lage en hoge venstervormen te proberen. Controleer of de paneelranden uitgelijnd blijven, of de bedieningselementen scrollen wanneer hun inhoud groter is dan het zichtbare gebied en of de schuifregelaar en schakelaar hun waarden behouden tijdens het aanpassen van het formaat.
Als de uitlijning na het aanpassen van het voorbeeld nog steeds verschilt, controleer dan deze specifieke omstandigheden:
- Extra opvulling van een paneel: Opvulling die buiten het vaste frame van slechts één paneel wordt toegepast, vergroot het totale oppervlak van dat paneel. Houd de opvulling van de bedieningselementen binnen de scrollweergave, zoals getoond.
- Een grotere wrapper rond de voorbeeldweergave: Als je een groter frame rond de voorbeeldweergave toevoegt, bepaal dan of de bedieningselementen moeten aansluiten op die wrapper of op de passend gemaakte inhoud. De berekening hier stemt af op de passende rechthoek.
- Aangepaste inhoud die overloopt: Een frame bepaalt de afmetingen van de lay-out, maar verbergt niet automatisch tekenwerk dat daarbuiten valt. Apple documenteert
.clipped()voor rechthoekig bijsnijden; gebruik dit wanneer je wilt dat onbedoeld overlopen van de voorbeeldweergave verborgen blijft. Houd de bedieningselementen scrollbaar, zodat alle instellingen bereikbaar blijven. Apple-documentatie over bijsnijden. - Onvoldoende ruimte in de host: Het voorbeeld is afhankelijk van de minimale inhoudsgrootte. Als het in een kleinere host wordt ingebed, laat de vaste breedte van het bedieningspaneel mogelijk geen bruikbare breedte over voor de voorbeeldweergave. Kies voor die host een kleiner paneel, een verticale indeling of een expliciete minimumgrootte.
Wanneer gebruik je beter een aangepaste lay-out?
Deze aanpak is geschikt voor een editor die een begrensd venstergebied krijgt en een bekende beeldverhouding gebruikt. Het formaatbeleid is expliciet en hangt niet af van metingen van de interne inhoud van de voorbeeldweergave.
Als je herbruikbare component de voorkeursgrootte moet bepalen op basis van zijn onderliggende weergaven, overweeg dan een aangepaste Layout. Het gedocumenteerde contract van Apple maakt onderscheid tussen de samengestelde grootte doorgeven via sizeThatFits(proposal:subviews:cache:) en de onderliggende weergaven positioneren via placeSubviews(in:proposal:subviews:cache:). Proxy's voor onderliggende weergaven bieden bewerkingen voor meten en plaatsen. Apple-documentatie over lay-outs.
Voor de begrensde desktopeditor in dit voorbeeld volstaat één gedeelde passende rechthoek: reserveer horizontale ruimte voor de bedieningselementen, pas de voorbeeldweergave binnen beide beschikbare afmetingen en geef de hoogte ervan aan het zichtbare gebied van de bedieningselementen. Zo heb je een duidelijke regel voor het aanpassen van de grootte, terwijl de inhoud van de bedieningselementen vrij kan scrollen.
Bronnen
- aspectRatio(_:contentMode:) — Apple Developer-documentatie.
- ScrollView — Apple Developer-documentatie.
- windowResizability(_:) — Apple Developer-documentatie.
- contentMinSize — Apple Developer-documentatie.
- GeometryReader — Apple Developer-documentatie.
- frame(width:height:alignment:) — Apple Developer-documentatie.
- State — Apple Developer-documentatie.
- clipped(antialiased:) — Apple Developer-documentatie.
- Layout — Apple Developer-documentatie.