Per mantenere stabili le colonne delle etichette e dello stato di una finestra Mac mentre la descrizione si espande, assegna larghezze esplicite alle colonne esterne e una cornice flessibile alla descrizione. Usa la priorità di layout quando le viste sorelle competono e conserva la dimensione ideale solo sugli assi che ne hanno bisogno.
Questo tutorial crea una piccola finestra ridimensionabile con etichette e descrizioni di elementi fittizi. L’obiettivo è mantenere allineate le colonne consentendo alle descrizioni più lunghe di andare a capo quando la finestra si restringe. Tutto appartiene all’app di esempio; il layout usa normali viste SwiftUI.
Quale controllo di dimensionamento dovrebbe usare ogni sottovista?
Il layout SwiftUI inizia con una dimensione proposta dal genitore. Ogni elemento figlio risponde con la propria dimensione preferita e il contenitore dispone i risultati. Un buon punto di partenza è decidere quali dimensioni può modificare ciascun elemento. Apple spiega questo processo di proposta e risposta in A guide to layout in SwiftUI.
Per questa finestra, assegna tre ruoli: una colonna delle etichette con larghezza scelta, una colonna della descrizione che assorbe le variazioni di larghezza e una colonna dello stato con larghezza scelta. Distingui questa larghezza fissa numerica dalla dimensione ideale di una vista: esprimono requisiti diversi.
| Requisito | Controllo | Conseguenza pratica |
|---|---|---|
| Riservare una larghezza specifica per una colonna | .frame(width: 96) |
La cornice risultante ha quella larghezza. |
| Consentire a una descrizione di assorbire la larghezza disponibile | .frame(minWidth: 160, maxWidth: .infinity, alignment: .leading) |
La cornice accetta la larghezza proposta, nel rispetto del minimo. |
| Conservare la dimensione ideale del contenuto sugli assi selezionati | .fixedSize(horizontal: false, vertical: true) |
La larghezza può adattarsi al layout, mentre l’altezza conserva la dimensione ideale. |
| Dare la preferenza a un elemento fratello durante l’allocazione dello spazio | .layoutPriority(1) |
Il genitore preferisce quell’elemento ai fratelli con priorità inferiore. |
Apple documenta le dimensioni numeriche in the fixed frame modifier, i vincoli minimi e massimi in the flexible frame modifier, il dimensionamento ideale specifico per asse in fixedSize(horizontal:vertical:) e la preferenza tra elementi fratelli in layoutPriority(_:).
Un limite massimo infinito consente di espandersi entro una proposta; non richiede una finestra infinitamente larga. Specificando vincoli minimi e massimi, la cornice adotta la dimensione proposta, limitata da quei vincoli. Il parametro di allineamento posiziona l’elemento figlio all’interno della cornice. Questi dettagli contano quando il testo è più stretto dello spazio assegnato alla sua colonna. Apple’s flexible frame documentation
Creare un esempio macOS completo e ridimensionabile
Crea in Xcode un progetto app macOS con SwiftUI e imposta come destinazione di distribuzione macOS 13 o versione successiva. Sostituisci le dichiarazioni generate per l’app e la vista del contenuto con questo singolo file, lasciando un solo punto di ingresso dell’app. L’esempio usa il controllo di ridimensionabilità delle finestre introdotto in macOS 13. Apple’s WindowResizability documentation
Quello che segue è un esempio completo di app. Include le stringhe di esempio e un interruttore per passare da descrizioni brevi a descrizioni lunghe. Non è stato possibile compilare l’app SwiftUI nell’ambiente di ricerca perché non era disponibile una toolchain Swift; controllane il comportamento in Xcode prima di integrarla in un progetto esistente.
import SwiftUI
@main
struct ColumnAllocationApp: App {
var body: some Scene {
WindowGroup {
AllocationView()
.frame(minWidth: 420, minHeight: 260)
}
.windowResizability(.contentMinSize)
}
}
struct AllocationView: View {
@State private var showLongDescriptions = true
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Item overview")
.font(.title2)
Toggle(
"Show longer descriptions",
isOn: $showLongDescriptions
)
ScrollView {
VStack(alignment: .leading, spacing: 16) {
AllocationRow(
label: "Item A",
summary: showLongDescriptions
? "A blue folder with several sample notes and a longer descriptive label."
: "Blue folder",
status: "Ready"
)
AllocationRow(
label: "Item B",
summary: showLongDescriptions
? "A green notebook containing a short list of synthetic example entries."
: "Green notebook",
status: "Draft"
)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
.padding(16)
}
}
struct AllocationRow: View {
let label: String
let summary: String
let status: String
var body: some View {
HStack(alignment: .top, spacing: 12) {
Text(label)
.frame(width: 96, alignment: .leading)
Text(summary)
.fixedSize(horizontal: false, vertical: true)
.frame(
minWidth: 160,
maxWidth: .infinity,
alignment: .leading
)
Text(status)
.fixedSize(horizontal: true, vertical: false)
.frame(width: 72, alignment: .trailing)
}
}
}
La descrizione è l’unica colonna flessibile orizzontalmente. La chiamata fixedSize lascia che il dimensionamento orizzontale si adatti, conservando al contempo lo spazio verticale necessario al testo su più righe. La cornice flessibile riempie poi la colonna assegnata. Lo stato conserva la larghezza ideale del testo all’interno di una cornice separata di 72 punti. Apple avverte che il contenuto a dimensione ideale può estendersi oltre i limiti del genitore, quindi questo trattamento dello stato presuppone i valori brevi forniti, Ready e Draft. Apple’s fixedSize documentation
Le righe non hanno un’altezza fissa. È una scelta intenzionale: le descrizioni più strette possono richiedere più righe. La vista di scorrimento consente di raggiungere le righe quando la loro altezza combinata supera l’area disponibile. Evita di aggiungere un’altezza restrittiva alle righe solo per rendere uniforme la disposizione iniziale.
La finestra usa .contentMinSize, che ricava le dimensioni minime dal contenuto e non impone una dimensione massima. In questo caso, la larghezza minima di 420 punti della vista radice stabilisce un limite inferiore pratico per l’esempio. Questo controllo si applica alle finestre create dalla scena dell’app. Apple’s contentMinSize documentation
Le dichiarazioni che restituiscono some View usano i tipi risultato opachi di Swift: i loro tipi concreti restano nascosti, mentre la conformità a View rimane disponibile. Puoi modificare le catene di modificatori senza specificare i tipi annidati restituiti. The Swift Programming Language: Opaque and Boxed Protocol Types
Calcolare il budget di larghezza prima di modificare le priorità
Un budget di larghezza rende espliciti i limiti del layout. In questo esempio, sottrai dalla larghezza del contenuto della finestra il padding orizzontale, entrambi gli spazi e le due colonne fisse. Il resto spetta alla descrizione. Sono dimensioni illustrative di progetto, non misure di controlli nativi.
Larghezza della descrizione
= larghezza del contenuto - padding orizzontale - spazi - colonne fisse
= larghezza del contenuto - (2 × 16) - (2 × 12) - 96 - 72
= larghezza del contenuto - 224
A 640 punti: restano 416 punti per la descrizione.
A 420 punti: restano 196 punti per la descrizione.
Budget minimo della riga:
32 + 24 + 96 + 160 + 72 = 384 punti.
Il calcolo è stato verificato con un’elaborazione locale deterministica. Descrive questa riga quando riceve la larghezza del contenuto meno il padding indicato; esclude le decorazioni della finestra. La larghezza minima scelta di 420 punti lascia un margine rispetto al minimo calcolato di 384 punti per la riga. Se un contenitore circostante aggiunge inset, includili nel budget.
Questo calcolo individua anche una richiesta impossibile. Sotto il budget minimo complessivo, non è possibile mantenere simultaneamente le larghezze e gli spazi di tutte e tre le colonne. Scegli una larghezza minima maggiore per la finestra, dimensioni minime più piccole oppure una disposizione diversa. Aumentare la priorità non può creare spazio aggiuntivo.
Quando è utile la priorità di layout?
La priorità è utile quando due elementi fratelli possono entrambi restringersi e vuoi che uno conservi più spazio. Per esempio, un titolo descrittivo può meritare la precedenza rispetto a un’annotazione secondaria. Applica .layoutPriority(1) all’elemento figlio che partecipa alla decisione di allocazione del genitore. Apple afferma che le viste con priorità più alta si restringono più tardi e si espandono prima, riservando spazio ai requisiti minimi degli elementi figli con priorità inferiore. Apple’s layoutPriority documentation
L’esempio completo non richiede alcun modificatore di priorità: la sua politica di allocazione è già espressa da due colonne fisse e una flessibile. Inoltre, un valore di priorità non è un peso proporzionale. Assegnare priorità 2 e 1 non stabilisce un rapporto di larghezza di due a uno; lo scopo documentato è indicare una preferenza durante l’allocazione. Apple’s layoutPriority documentation
Evita di aggiungere uno Spacer orizzontale tra la descrizione e lo stato, a meno che tu non voglia uno spazio flessibile separato. Uno spacer si espande lungo l’asse principale del suo stack e allontana gli elementi fratelli. Poiché la cornice della descrizione assorbe già le variazioni di larghezza, qui non serve aggiungere un altro elemento all’allocazione. Apple’s Spacer documentation
Cosa dovrebbe succedere quando il testo non entra più?
Decidi separatamente se ogni campo di testo deve andare a capo, essere troncato o richiedere una disposizione diversa. Le descrizioni dell’esempio possono andare a capo senza un limite di righe. Applicare .lineLimit(1) limiterebbe ogni elemento di testo interessato a una sola riga, modificando il requisito. Un limite di righe pari a nil rimuove quel limite, ma non elimina i vincoli imposti dalle cornici che lo contengono. Apple’s lineLimit documentation
Per valori di stato più lunghi, riconsidera la colonna da 72 punti. Rimuovi la conservazione orizzontale della dimensione ideale se andare a capo è accettabile, aumenta la larghezza della colonna oppure sposta lo stato sotto la descrizione. Mantenere .fixedSize(horizontal: true, vertical: false) restringendo la cornice che lo contiene può consentire al testo di estendersi oltre quella cornice. Apple’s fixedSize documentation
Se una finestra compatta deve ridisporre il contenuto, macOS 13 e versioni successive offrono ViewThatFits. Fornisci prima una disposizione orizzontale e poi una impilata. Seleziona il primo elemento figlio la cui dimensione ideale rientra sugli assi vincolati, quindi valuta lo spazio orizzontale con ViewThatFits(in: .horizontal) quando è la larghezza a determinare la scelta. Assicurati che l’ultima opzione sia utilizzabile alla larghezza minima supportata. Apple’s ViewThatFits documentation
Considerala una politica di layout alternativa. L’esempio attuale mantiene intenzionalmente le colonne e stabilisce una larghezza minima per la finestra. Usa una disposizione impilata di ripiego quando mantenere quella orizzontale è meno importante che consentire una finestra più stretta.
In che modo le viste AppKit e TextKit integrate cambiano l’approccio?
Se in seguito la descrizione diventa un componente di testo AppKit, usa NSViewRepresentable per integrare la vista. Apple afferma esplicitamente che SwiftUI controlla frame e bounds della vista rappresentata; modificarle direttamente nel proprio codice entra in conflitto con il layout SwiftUI. Configura il contenuto attraverso il ciclo di vita del representable ed esprimi il dimensionamento esterno con SwiftUI. Apple’s NSViewRepresentable documentation
A partire da macOS 13, il metodo sizeThatFits(_:nsView:context:) del representable può restituire la dimensione preferita in base a una proposta. Può essere chiamato più volte con proposte diverse durante un singolo passaggio di layout. Restituire nil richiede l’algoritmo di dimensionamento predefinito. La misurazione di un componente con testo a capo dovrebbe tenere conto della larghezza proposta invece di restituire sempre la larghezza del testo senza ritorni a capo. Apple’s representable sizing documentation
Mantieni distinte le dimensioni non specificate da quelle pari a zero: SwiftUI usa una proposta non specificata per richiedere la dimensione ideale, mentre zero può sondare la dimensione minima. Una routine di misurazione personalizzata deve gestire deliberatamente questi casi. Apple’s ProposedViewSize documentation
All’interno di un layout AppKit, la resistenza alla compressione controlla quanto una NSView resiste a diventare più piccola della propria dimensione intrinseca. Se un componente con testo a capo resiste alla compressione, esamina questa impostazione insieme al suo comportamento di dimensionamento SwiftUI. Apple’s setContentCompressionResistancePriority documentation
Per un NSTextView che usa TextKit 2, adotta il percorso moderno textLayoutManager. La panoramica aggiornata di Apple su TextKit lo distingue dal layoutManager legacy, e le indicazioni per la migrazione spiegano che l’accesso alla proprietà legacy può attivare per quell’istanza un passaggio di compatibilità a senso unico a TextKit 1. Cambiare motore di testo non serve per allocare le colonne tra elementi fratelli. Apple’s TextKit overview, What’s new in TextKit and text views
Verificare il risultato ai limiti della finestra
Esegui l’esempio e controlla queste condizioni prima di applicare il modello a un’interfaccia più ampia:
- Allarga la finestra: la colonna della descrizione dovrebbe espandersi, mentre le cornici delle etichette e dello stato mantengono le proprie larghezze.
- Restringila fino al minimo supportato: le descrizioni più lunghe dovrebbero andare a capo e aumentare l’altezza delle righe.
- Attiva e disattiva
Show longer descriptions: le descrizioni brevi dovrebbero restare allineate alle stesse colonne. - Sostituisci lo stato con un valore fittizio più lungo: decidi se allargare, mandare a capo o ridisporre quel campo.
- Riduci l’altezza disponibile: verifica che tutte le righe restino raggiungibili.
Per vedere quale livello riceve lo spazio, aggiungi temporaneamente dei bordi subito prima e subito dopo la cornice flessibile della descrizione. Il bordo interno mostra i limiti del testo; quello esterno mostra la colonna assegnata. Apple consiglia i bordi colorati come metodo pratico per diagnosticare il dimensionamento e il padding degli stack. A guide to layout in SwiftUI
Man mano che l’interfaccia cresce, mantieni esplicita la politica di dimensionamento: riserva larghezze dove serve l’allineamento, assegna ai contenuti variabili una regione flessibile definita e decidi come si comporta ogni campo alla larghezza minima. Con questa politica e un budget di larghezza, puoi modificare le etichette o aggiungere contenuti senza dover indovinare a quale modificatore assegnare una priorità maggiore.
Fonti
- A guide to layout in SwiftUI — Apple
- frame(width:height:alignment:) — Apple
- frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:) — Apple
- fixedSize(horizontal:vertical:) — Apple
- layoutPriority(_:) — Apple
- WindowResizability — Apple
- contentMinSize — Apple
- Opaque and Boxed Protocol Types — Swift.org
- Spacer — Apple
- lineLimit(_:) — Apple
- ViewThatFits — Apple
- NSViewRepresentable — Apple
- sizeThatFits(_:nsView:context:) — Apple
- ProposedViewSize — Apple
- setContentCompressionResistancePriority(_:for:) — Apple
- TextKit — Apple
- What’s new in TextKit and text views — Apple