För att mäta en etikett i din Mac-app utan att sträcka ut den, koppla onGeometryChange till vyn du vill inspektera. För ett äldre distributionsmål, placera en GeometryReader i dess bakgrund. Modifierarens position bestämmer vilken bredd du läser.
Den här genomgången mäter en synthetisk etikett, inklusive dess padding, samtidigt som den håller dess omgivande layout oberoende av resultatet. Den förklarar också hur man skiljer etikettens nuvarande bredd från dess idealiska bredd och dess containers bredd.
Varför får en geometry reader layouten att expandera?
SwiftUI-layout innebär förslag och svar: en förälder föreslår utrymme, och dess barn rapporterar sina beräknade storlekar. Att lägga till en frame introducerar en annan vy i den hierarkin, så etiketten och dess frame kan ha olika bredder. Apple förklarar processen i Laying out a simple view.
En GeometryReader är en container med en flexibel föredragen storlek. Att packa in en kompakt etikett i en sådan ändrar alltså layoutdeltagaren som presenteras för föräldern. Du mäter readerns container, som kan uppta avsevärt mer utrymme än texten behöver. Se Apples GeometryReader-dokumentation.
För observation, koppla mätningen till den befintliga vyn. När en bakgrunds-reader är nödvändig, spelar dess placering roll: Apple dokumenterar att en bakgrund tar emot storleken på vyn som den är kopplad till. Detta låter etiketten bestämma mätområdet. Se Adding a background to your view.
Innan du väljer implementation, bestäm vilken fråga ditt gränssnitt behöver besvara:
| Din fråga | Lämplig metod |
|---|---|
| Hur bred är den här etiketten i dess nuvarande layout? | Observera etikettens geometri. |
| Hur bred är den paddade badgen runt etiketten? | Observera efter padding. |
| Hur bred är dess innehållande rad? | Observera raden eller dess frame. |
| Hur bred skulle hela texten föredra att vara? | Undersök idealisk dimensionering separat. |
| Vilken av flera presentationer passar? | Överväg ViewThatFits. |
| Hur ska flera barn mätas och positioneras tillsammans? | Överväg en anpassad Layout. |
Skillnaden förhindrar ett vanligt misstag: att behandla bredden på en begränsad, möjligen trunkerad etikett som det utrymme som krävs för att visa hela strängen.
Mät den aktuella bredden på macOS 13 och senare
Apples nuvarande dokumentation listar den enkla värde-åtgärdsövergången av onGeometryChange(for:of:action:) som tillgänglig på macOS 13 och senare. Den transformerar en geometri-proxy till ett likvärdigt värde och anropar en åtgärd när värdet ändras. Använd ett SDK som innehåller detta API, och behåll den envariabla åtgärden som visas här. Se onGeometryChange(for:of:action:).
Skapa ett macOS-appprojekt i Xcode med SwiftUI och ett distributionsmål på macOS 13 eller senare. Ersätt de genererade app- och content-view-deklarationerna med detta fullständiga program, och lämna bara en @main app-deklaration i målet. Exemplet använder bara lokala syntetiska etiketter.
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)
}
}
Observeraren läser proxy.size.width; Apple definierar GeometryProxy.size som container-vyns storlek. Här inkluderar den observerade vyn texten och padding, medan den efterföljande frame ger ett oberoende kontrollerat breddförslag. Se GeometryProxy.size.
Mätningen uppdaterar en separat avläsning. Den sätter inte den mätta badgens bredd. Den fasta demo-ytan och avläsningsbredden gör det enklare att inspektera resultatet utan att låta diagnostiktexten ändra storlek på den omgivande stacken.
Att returnera bara CGFloat håller också observationen fokuserad på bredd: en höjdendast förändring ändrar inte det transformerade värdet. Apple rekommenderar att transformera geometrin till de specifika data du behöver eftersom geometri kan ändras ofta. Se onGeometryChange(for:of:action:).
Dessa exempel granskades mot de hämtade API-deklarationerna. De kompilerades inte eller kördes här eftersom den tillgängliga miljön är Linux utan en Swift-toolchain; bygg och inspektera gränssnittet i Xcode på en Mac.
Var ska mät-modifieraren placeras?
Läs modifierarkedjan som en uppföljning av inpackade vyer. Mätningen tillhör omedelbart efter den sista storleksinverkan-modifierare du vill inkludera. Apples layoutförklaring beskriver hur frames packar in vyer och padding ändrar utrymmet tillgängligt för deras innehåll. Se Laying out a simple view.
| Mätplats i exemplet | Vad det observerar |
|---|---|
| Före båda padding-modifierarna | Text-vyns nuvarande layoutbredd |
| Efter båda padding-modifierarna | Den paddade badgens nuvarande bredd |
| Efter den explicita bredd-framen | Den yttre framens bredd |
På den omgivande VStack |
Stackens bredd |
Som en illustrativ beräkning, anta att en text-vy rapporterar 96 poäng under sitt nuvarande förslag. Att lägga till 12 poäng på varje horisontell kant ger en paddad bredd på 120 poäng. En yttre frame på 260 poäng är en annan distinkt mätning. Dessa siffror illustrerar modifierargränserna; de är inte mätta resultat för exempeltesten.
För att undersöka ett oväntat värde, placera tillfälligt en border på samma position i modifierarkedjan som observeraren. Jämför den gränsen med det avsedda målet. En reader kopplad till en hel rad kan korrekt rapportera radens bredd även när den synliga etiketten bara upptar en liten del av den.
Använd en bakgrunds-reader för ett macOS 12-mål
För ett macOS 12-distributionsmål, använd en bakgrunds GeometryReader för att publicera bredden genom en preferens. Det avslutande closure-bakgrunds-API:et som används nedan är tillgängligt från macOS 12. Apple rekommenderar bakgrunder när den modifierade vyn ska dominera layouten. Se background(alignment:content:).
Detta är ett fullständigt alternativt program. Ersätt föregående exempel med det istället för att lägga till en andra app-deklaration.
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 bär ett värde mot förfäder, och onPreferenceChange tar emot ändringar av det värdet. Readern utsänder mätningen snarare än att tilldela state inuti sitt innehålls-closure. Se Apples PreferenceKey och onPreferenceChange(_:perform:) dokumentation.
Denna reducer antagar medvetet en utsändande badge. Att återanvända samma nyckel för flera barn skulle kombinera deras värden, med denna implementation som behåller det senast reducerade värdet. För oberoende spårade etiketter, designa en nycklad samling av mätningar istället för att låta en skalar stå för flera vyer.
Hur kan du verifiera att mätningen förblir oberoende?
Använd det första programmet som en liten diagnostisk sekvens:
- Starta med
Blue circlesoch en generös erbjuden bredd. Jämför den färglagda badgen med avläsningen. - Dra slidern. Den yttre frame ändras oberoende; badgen kan förblå kompakt medan tillräckligt med utrymme finns.
- Välj
Change sample label, och minska sedan den erbjudna bredden. Inspektera den nuvarande begränsade badgen snarere än att förvänta dig den fulla strängens idealiska bredd. - Flytta observeraren under den explicita frame. Den bör nu rapportera framens bredd.
- Återställ observeraren ovanför frame innan du använder badgmätningen någon annanstans.
Håll den observerade bredden utanför formeln som bestämmer samma vys bredd. Till exempel, att sätta dess frame till den observerade bredden plus padding kan upprepade gånger mata tillbaka ett större resultat in i layouten. Detta följer direkt av den beroende du skapar: observation ändrar state, state ändrar storlek, och den nya storleken producerar en annan observation.
Om din faktiska uppgift är att välja mellan en full etikett och ett kompakt alternativ, kan ViewThatFits på macOS 13 och senare välja det första barnet vars idealiska storlek får plats på de begränsade axlarna. Det kan ta bort behovet av att lagra en bredd alls. Se ViewThatFits.
Om flera barn måste mätas och positioneras tillsammans, använd en anpassad Layout på macOS 13 och senare. Dess mätnings- och placeringsmetoder låter containern fråga undervyer innan de positioneras, och håller beslutet inuti layoutprocessen. Se Layout.
Kan idealisk dimensionering eller AppKit-textmått svara på samma fråga?
Använd .fixedSize(horizontal: true, vertical: false) bara när du avser att bevara textens idealiska bredd. Apple dokumenterar att detta kan göra att texten överstiger sin förälders gränser. Att lägga till det för att få en mätning ändrar den visade vyns storleksbeteende, så det bevarar inte den ursprungliga begränsade layouten. Se fixedSize(horizontal:vertical:).
Teckenantal är också olämpliga som breddmått. Swifts String.count räknar Character-värden representerade av utökade grapheme-kluster; det returnerar inte typografiska dimensioner. Att multiplicera det antalet med en gissad teckenbredd ger inget mått på SwiftUI-vyn. Se Strings and Characters.
För en AppKit-rituppgift, returnerar NSAttributedString.size() storleken som behövs för att rita den attribuerade strängen, potentiellt inklusive bråkdelar av dimensioner. Apple rådrund att avrunda uppåt när man använder resultatet för att dimensionera en vy. Detta svarar på en textritningsfråga; det inkluderar inte en SwiftUI-vys padding eller omslutande frame. Se size().
För ett befintligt TextKit 1-layout, rapporterar NSLayoutManager.usedRect(for:) området upptaget av glyfer som för närvarande är utlagda i dess textcontainer. Det genererar inte själv glyfer eller utför layout; anropa ensureLayout(for:) när layout först måste slutföras. Dessa API:er mäter den konfigurerade textcontainern, så håll deras resultat separata från SwiftUI-geometri. Se usedRect(for:) och ensureLayout(for:).
För etiketten som redan visas i ditt SwiftUI Mac-gränssnitt, observera dess geometri på den gräns du behöver, håll avläsningen oberoende, och testa ändringar av både innehåll och tillgängligt utrymme. Det producerar en användbar bredd samtidigt som det bevarar layouten du avsåg att inspektera.
Källor
onGeometryChange(for:of:action:) — Apple
Laying out a simple view — Apple
GeometryReader — Apple
Adding a background to your view — Apple
size — Apple
background(alignment:content:) — Apple
PreferenceKey — Apple
onPreferenceChange(_:perform:) — Apple
ViewThatFits — Apple
Layout — Apple
fixedSize(horizontal:vertical:) — Apple
Strings and Characters — Swift.org
size() — Apple
usedRect(for:) — Apple
ensureLayout(for:) — Apple