Om een label in je Mac-app te meten zonder het uit te rekken, voeg je onGeometryChange toe aan de weergave die je wilt inspecteren. Plaats bij een oudere deploymenttarget een GeometryReader op de achtergrond. De positie van de modifier bepaalt welke breedte je uitleest.
In deze walkthrough meten we een synthetisch label, inclusief de opvulling, terwijl de omringende lay-out onafhankelijk blijft van het resultaat. Ook leggen we uit hoe je de huidige breedte van het label onderscheidt van de ideale breedte en de breedte van de container.
Waarom zorgt een geometry reader ervoor dat de lay-out uitrekt?
Bij de lay-out in SwiftUI gaat het om voorstellen en antwoorden: een bovenliggende weergave stelt ruimte voor, en de onderliggende weergaven geven hun berekende afmetingen door. Door een frame toe te voegen, komt er een weergave bij in die hiërarchie, waardoor het label en het frame verschillende breedtes kunnen hebben. Apple legt dit proces uit in Een eenvoudige weergave indelen.
Een GeometryReader is een container met een flexibele voorkeursgrootte. Als je er een compact label in verpakt, verandert dus de lay-outdeelnemer die aan de bovenliggende weergave wordt aangeboden. Je meet de container van de reader, die aanzienlijk meer ruimte kan innemen dan de tekst nodig heeft. Zie Apple’s documentatie over GeometryReader.
Voeg de meting voor observatie toe aan de bestaande weergave. Als een reader op de achtergrond nodig is, is de plaatsing van belang: Apple documenteert dat een achtergrond de afmetingen ontvangt van de weergave waaraan deze is gekoppeld. Zo bepaalt het label het meetgebied. Zie Een achtergrond aan je weergave toevoegen.
Bepaal voordat je een implementatie kiest welke vraag je interface moet beantwoorden:
| Je vraag | Geschikte aanpak |
|---|---|
| Hoe breed is dit label in de huidige lay-out? | Observeer de geometrie van het label. |
| Hoe breed is de badge met opvulling rond het label? | Observeer na de opvulling. |
| Hoe breed is de rij waarin het staat? | Observeer de rij of het frame ervan. |
| Hoe breed zou de volledige tekst idealiter zijn? | Onderzoek de ideale grootte afzonderlijk. |
| Welke van meerdere weergaven past? | Overweeg ViewThatFits. |
| Hoe meet en positioneer je meerdere onderliggende weergaven samen? | Overweeg een aangepaste Layout. |
Dit onderscheid voorkomt een veelgemaakte fout: de breedte van een begrensd, mogelijk afgekapt label aanzien voor de ruimte die nodig is om de volledige tekst weer te geven.
Meet de huidige breedte op macOS 13 en hoger
De huidige documentatie van Apple vermeldt dat de overload met één actieparameter van onGeometryChange(for:of:action:) beschikbaar is op macOS 13 en hoger. Deze overload zet een geometry-proxy om in een equatable waarde en roept een actie aan wanneer die waarde verandert. Gebruik een SDK waarin deze API beschikbaar is en houd je aan de actie met één parameter die hier wordt getoond. Zie onGeometryChange(for:of:action:).
Maak in Xcode een macOS-appproject met SwiftUI en een deploymenttarget van macOS 13 of hoger. Vervang de gegenereerde app- en content-view-declaraties door dit volledige programma en zorg dat er in het target slechts één @main-appdeclaratie staat. Het voorbeeld gebruikt alleen lokale synthetische labels.
import SwiftUI
import Foundation
@main
struct WidthDemoApp: App {
var body: some Scene {
WindowGroup {
WidthDemoView()
}
}
}
struct WidthDemoView: View {
@State private var measuredWidth: CGFloat = 0
@State private var offeredWidth: Double = 260
@State private var showLongLabel = false
private var label: String {
showLongLabel
? "Sample collection of blue circles"
: "Blue circles"
}
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text(label)
.font(.system(size: 16))
.lineLimit(1)
.padding(.horizontal, 12)
.padding(.vertical, 8)
.background(Color.blue.opacity(0.12))
.onGeometryChange(for: CGFloat.self) { proxy in
proxy.size.width
} action: { newWidth in
measuredWidth = newWidth
}
.frame(
width: CGFloat(offeredWidth),
alignment: .leading
)
Text(String(
format: "Measured badge: %.1f pt",
Double(measuredWidth)
))
.monospacedDigit()
.frame(width: 340, alignment: .leading)
Slider(value: $offeredWidth, in: 120...340)
Button("Change sample label") {
showLongLabel.toggle()
}
}
.frame(width: 360, alignment: .leading)
.padding(20)
}
}
De observer leest proxy.size.width; Apple definieert GeometryProxy.size als de afmetingen van de containerweergave. De geobserveerde weergave omvat hier de tekst en de opvulling, terwijl het daaropvolgende frame een onafhankelijk breedtevoorstel biedt. Zie GeometryProxy.size.
De meting werkt een afzonderlijke uitlezing bij. Ze stelt de breedte van de gemeten badge niet in. Het vaste demogebied en de breedte van de uitlezing maken het resultaat makkelijker te inspecteren zonder dat de diagnostische tekst de omringende stack vergroot of verkleint.
Door alleen CGFloat terug te geven, blijft de observatie ook gericht op de breedte: een verandering in alleen de hoogte verandert de omgezette waarde niet. Apple raadt aan geometrie om te zetten naar de specifieke gegevens die je nodig hebt, omdat geometrie vaak kan veranderen. Zie onGeometryChange(for:of:action:).
Deze voorbeelden zijn gecontroleerd aan de hand van de opgehaalde API-declaraties. Ze zijn hier niet gecompileerd of uitgevoerd, omdat de beschikbare omgeving Linux is en geen Swift-toolchain bevat. Bouw en inspecteer de interface in Xcode op een Mac.
Waar moet de meetmodifier staan?
Lees de modifierketen als een reeks omhulde weergaven. De meting hoort direct na de laatste modifier die de grootte beïnvloedt en die je wilt meenemen. Apples uitleg over lay-out beschrijft hoe frames weergaven omhullen en hoe opvulling de beschikbare ruimte voor de inhoud verandert. Zie Een eenvoudige weergave indelen.
| Meetlocatie in het voorbeeld | Wat er wordt gemeten |
|---|---|
| Vóór beide opvullingsmodifiers | De huidige lay-outbreedte van de tekstweergave |
| Na beide opvullingsmodifiers | De huidige breedte van de badge met opvulling |
| Na het expliciete frame met breedte | De breedte van het buitenste frame |
Op de omringende VStack |
De breedte van de stack |
Stel ter illustratie dat een tekstweergave bij het huidige voorstel 96 punten rapporteert. Door aan beide horizontale zijden 12 punten toe te voegen, ontstaat een breedte van 120 punten inclusief opvulling. Een buitenste frame van 260 punten is een andere, afzonderlijke meting. Deze getallen illustreren de grenzen tussen modifiers; het zijn geen gemeten resultaten voor de voorbeeldtekst.
Plaats tijdelijk een rand op dezelfde positie in de modifierketen als de observer om een onverwachte waarde te onderzoeken. Vergelijk die grens met het beoogde meetdoel. Een reader die aan een hele rij is gekoppeld, kan terecht de breedte van de rij rapporteren, ook als het zichtbare label slechts een klein deel daarvan inneemt.
Gebruik een reader op de achtergrond voor een macOS 12-target
Gebruik voor een deploymenttarget van macOS 12 een GeometryReader op de achtergrond om de breedte via een preference door te geven. De API voor de achtergrond met afsluitende closure die hieronder wordt gebruikt, is beschikbaar vanaf macOS 12. Apple raadt achtergronden aan wanneer de gewijzigde weergave de lay-out moet bepalen. Zie background(alignment:content:).
Dit is een volledig alternatief programma. Vervang het vorige voorbeeld erdoor in plaats van een tweede appdeclaratie toe te voegen.
import SwiftUI
import Foundation
private struct BadgeWidthKey: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(
value: inout CGFloat,
nextValue: () -> CGFloat
) {
value = nextValue()
}
}
@main
struct LegacyWidthDemoApp: App {
var body: some Scene {
WindowGroup {
LegacyWidthDemoView()
}
}
}
struct LegacyWidthDemoView: View {
@State private var measuredWidth: CGFloat = 0
@State private var showLongLabel = false
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text(showLongLabel ? "Blue circles collection" : "Blue circles")
.font(.system(size: 16))
.lineLimit(1)
.padding(.horizontal, 12)
.padding(.vertical, 8)
.background(Color.blue.opacity(0.12))
.background {
GeometryReader { proxy in
Color.clear.preference(
key: BadgeWidthKey.self,
value: proxy.size.width
)
}
}
.onPreferenceChange(BadgeWidthKey.self) { newWidth in
measuredWidth = newWidth
}
Text(String(
format: "Measured badge: %.1f pt",
Double(measuredWidth)
))
.frame(width: 340, alignment: .leading)
Button("Change sample label") {
showLongLabel.toggle()
}
}
.frame(width: 360, alignment: .leading)
.padding(20)
}
}
PreferenceKey geeft een waarde door aan bovenliggende weergaven, en onPreferenceChange ontvangt wijzigingen in die waarde. De reader geeft de meting door in plaats van state toe te wijzen binnen de content-closure. Zie Apples documentatie over PreferenceKey en onPreferenceChange(_:perform:).
Deze reducer gaat er bewust van uit dat er één badge is die een waarde doorgeeft. Als je dezelfde sleutel voor meerdere onderliggende weergaven gebruikt, worden hun waarden gecombineerd; deze implementatie bewaart daarbij de laatst gereduceerde waarde. Ontwerp voor labels die afzonderlijk moeten worden bijgehouden een verzameling met sleutels voor de metingen, in plaats van één scalaire waarde voor meerdere weergaven te gebruiken.
Hoe controleer je of de meting onafhankelijk blijft?
Gebruik het eerste programma als een korte diagnostische reeks:
- Begin met
Blue circlesen een ruim aangeboden breedte. Vergelijk de gekleurde badge met de uitlezing. - Versleep de schuifregelaar. Het buitenste frame verandert onafhankelijk; de badge kan compact blijven zolang er voldoende ruimte is.
- Selecteer
Change sample labelen verklein vervolgens de aangeboden breedte. Bekijk de huidige begrensde badge, zonder de ideale breedte van de volledige tekst te verwachten. - Verplaats de observer onder het expliciete frame. De observer hoort nu de breedte van dat frame te rapporteren.
- Zet de observer terug boven het frame voordat je de badgemeting ergens anders voor gebruikt.
Gebruik de geobserveerde breedte niet in de formule die de breedte van diezelfde weergave bepaalt. Als je het frame bijvoorbeeld instelt op de geobserveerde breedte plus opvulling, kan een groter resultaat steeds opnieuw in de lay-out worden teruggekoppeld. Dat volgt rechtstreeks uit de afhankelijkheid die je creëert: observatie verandert state, state verandert de grootte en de nieuwe grootte leidt tot een volgende observatie.
Als je eigenlijk moet kiezen tussen een volledig label en een compact alternatief, kan ViewThatFits op macOS 13 en hoger het eerste onderliggende element selecteren waarvan de ideale grootte binnen de begrensde assen past. Daardoor hoef je mogelijk helemaal geen breedte op te slaan. Zie ViewThatFits.
Als meerdere onderliggende weergaven samen moeten worden gemeten en gepositioneerd, gebruik dan op macOS 13 en hoger een aangepaste Layout. Met de meet- en plaatsingsmethoden kan de container de onderliggende weergaven opvragen voordat deze ze positioneert. Zo blijft die beslissing onderdeel van het lay-outproces. Zie Layout.
Kunnen de ideale grootte of AppKit-tekstmetingen dezelfde vraag beantwoorden?
Gebruik .fixedSize(horizontal: true, vertical: false) alleen als je de ideale breedte van de tekst wilt behouden. Apple documenteert dat tekst daardoor buiten de grenzen van de bovenliggende weergave kan vallen. Als je dit toevoegt om een meting te verkrijgen, verandert het de groottebepaling van de weergegeven weergave. De oorspronkelijke begrensde lay-out blijft dus niet behouden. Zie fixedSize(horizontal:vertical:).
Ook het aantal tekens is ongeschikt als breedtemeting. Swift’s String.count telt waarden van het type Character, die worden weergegeven door uitgebreide clusters van grafemen; het retourneert geen typografische afmetingen. Dat aantal vermenigvuldigen met een geschatte tekenbreedte levert geen meting van de SwiftUI-weergave op. Zie Tekenreeksen en tekens.
Voor een tekentaak met AppKit geeft NSAttributedString.size() de grootte terug die nodig is om die geannoteerde tekenreeks te tekenen, mogelijk met fractionele afmetingen. Apple adviseert naar boven af te ronden wanneer je dit resultaat gebruikt om de grootte van een weergave te bepalen. Dit beantwoordt een vraag over teksttekenen; de opvulling van een SwiftUI-weergave of het omringende frame is hierin niet inbegrepen. Zie size().
Bij een bestaande TextKit 1-lay-out rapporteert NSLayoutManager.usedRect(for:) het gebied dat wordt ingenomen door glyphs die momenteel in de tekstcontainer zijn ingedeeld. Deze methode genereert zelf geen glyphs en voert geen lay-out uit; roep ensureLayout(for:) aan als de lay-out eerst moet worden voltooid. Deze API’s meten de geconfigureerde tekstcontainer. Houd de resultaten daarom gescheiden van de SwiftUI-geometrie. Zie usedRect(for:) en ensureLayout(for:).
Observeer voor het label dat al in je SwiftUI-interface voor Mac wordt weergegeven de geometrie op de grens die je nodig hebt, houd de uitlezing onafhankelijk en test wijzigingen in zowel de inhoud als de beschikbare ruimte. Zo krijg je een bruikbare breedte en blijft de lay-out behouden die je wilde inspecteren.
Bronnen
onGeometryChange(for:of:action:) — Apple
Een eenvoudige weergave indelen — Apple
GeometryReader — Apple
size — Apple
background(alignment:content:) — Apple
PreferenceKey — Apple
onPreferenceChange(_:perform:) — Apple
ViewThatFits — Apple
Layout — Apple
fixedSize(horizontal:vertical:) — Apple
Tekenreeksen en tekens — Swift.org
size() — Apple
usedRect(for:) — Apple
ensureLayout(for:) — Apple