Aby kolumny etykiet i statusów w oknie Maca zachowywały stałą szerokość, gdy opis się rozszerza, nadaj skrajnym kolumnom jawne szerokości, a opisowi elastyczną ramkę. Gdy elementy podrzędne konkurują o miejsce, użyj priorytetu układu, a idealny rozmiar zachowuj tylko na potrzebnych osiach.
W tym samouczku zbudujemy małe, zmienialne okno zawierające przykładowe etykiety i opisy elementów. Zadanie polega na wyrównaniu kolumn przy jednoczesnym umożliwieniu zawijania dłuższych opisów, gdy okno się zwęża. Wszystkie elementy należą do przykładowej aplikacji; układ korzysta ze standardowych widoków SwiftUI.
Jakiej kontrolki rozmiaru powinien używać każdy podwidok?
Układ SwiftUI zaczyna się od zaproponowania przez widok nadrzędny rozmiaru. Każdy element podrzędny odpowiada preferowanym rozmiarem, a kontener rozmieszcza wyniki. Dobrym punktem wyjścia jest określenie, które wymiary każdego elementu mogą się zmieniać. Apple wyjaśnia ten proces propozycji i odpowiedzi w dokumencie Przewodnik po układach w SwiftUI.
W tym oknie przypisz trzy role: kolumnę etykiet o wybranej szerokości, kolumnę opisu, która przejmuje zmiany szerokości, oraz kolumnę statusu o wybranej szerokości. Odróżnij tę stałą wartość liczbową od idealnego rozmiaru widoku: wyrażają różne wymagania.
| Wymaganie | Kontrolka | Praktyczny skutek |
|---|---|---|
| Zarezerwowanie określonej szerokości kolumny | .frame(width: 96) |
Wynikowa ramka ma tę szerokość. |
| Umożliwienie opisowi wykorzystania dostępnej szerokości | .frame(minWidth: 160, maxWidth: .infinity, alignment: .leading) |
Ramka przyjmuje zaproponowaną szerokość, z uwzględnieniem minimum. |
| Zachowanie idealnego rozmiaru zawartości na wybranych osiach | .fixedSize(horizontal: false, vertical: true) |
Szerokość może reagować na układ, a wysokość zachowuje idealny rozmiar. |
| Preferowanie jednego elementu podrzędnego przy przydziale miejsca | .layoutPriority(1) |
Widok nadrzędny daje temu elementowi pierwszeństwo przed elementami o niższym priorytecie. |
Apple opisuje wymiary liczbowe w dokumentacji modyfikatora stałej ramki, ograniczenia minimalne i maksymalne w dokumentacji modyfikatora elastycznej ramki, idealny rozmiar dla wybranych osi w dokumentacji fixedSize(horizontal:vertical:), a preferencje między elementami podrzędnymi w dokumentacji layoutPriority(_:).
Nieskończona wartość maksymalna pozwala rozszerzać się w ramach propozycji; nie oznacza żądania okna o nieskończonej szerokości. Podanie zarówno ograniczeń minimalnych, jak i maksymalnych sprawia, że ramka przyjmuje zaproponowany wymiar, z zachowaniem tych ograniczeń. Parametr wyrównania określa położenie elementu podrzędnego wewnątrz ramki. Te szczegóły mają znaczenie, gdy sam tekst jest węższy niż przestrzeń przydzielona jego kolumnie. Dokumentacja elastycznej ramki Apple
Zbuduj kompletny, zmienialny przykład dla macOS
Utwórz w Xcode projekt aplikacji macOS używający SwiftUI i ustaw docelową wersję wdrożeniową na macOS 13 lub nowszą. Zastąp wygenerowane deklaracje aplikacji i widoku zawartości tym jednym plikiem, pozostawiając tylko jeden punkt wejścia aplikacji. Przykład korzysta z ustawienia zmienialności okna dostępnego w macOS 13. Dokumentacja WindowResizability Apple
Poniżej znajduje się kompletny przykład aplikacji. Zawiera przykładowe teksty i przełącznik zmiany krótkich opisów na długie. Nie udało się skompilować aplikacji SwiftUI w środowisku badawczym z powodu braku toolchaina Swift; przed integracją z istniejącym projektem sprawdź jej działanie w Xcode.
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)
}
}
}
Opis to jedyna kolumna elastyczna w poziomie. Wywołanie fixedSize pozostawia elastyczne dopasowanie szerokości, zachowując zarazem miejsce w pionie potrzebne zawijanemu tekstowi. Elastyczna ramka wypełnia następnie przydzieloną kolumnę. Status zachowuje idealną szerokość tekstu wewnątrz osobnej ramki o szerokości 72 punktów. Apple ostrzega, że zawartość zachowująca idealny rozmiar może wykraczać poza granice widoku nadrzędnego, dlatego takie rozwiązanie dla statusu zakłada użycie krótkich wartości: Ready i Draft. Dokumentacja fixedSize Apple
Wiersze nie mają stałej wysokości. To celowe: węższe opisy mogą wymagać większej liczby wierszy tekstu. Widok przewijany umożliwia dotarcie do wierszy, gdy ich łączna wysokość przekracza dostępną przestrzeń. Unikaj dodawania ograniczającej wysokości wiersza wyłącznie po to, by początkowy układ wyglądał równo.
Okno korzysta z .contentMinSize, które wyznacza minimalne wymiary na podstawie zawartości i nie nakłada maksymalnego rozmiaru. W tym przykładzie minimalna szerokość widoku głównego wynosząca 420 punktów stanowi praktyczną dolną granicę. To ustawienie dotyczy okien tworzonych przez scenę aplikacji. Dokumentacja contentMinSize Apple
Deklaracje zwracające some View korzystają z niejawnych typów wynikowych Swift: ich konkretne typy pozostają ukryte, a zgodność z View jest dostępna. Możesz modyfikować łańcuchy modyfikatorów bez konieczności zapisywania zagnieżdżonych typów zwracanych. Język programowania Swift: niejawne i opakowane typy protokołów
Oblicz budżet szerokości, zanim zmienisz priorytety
Budżet szerokości jasno określa ograniczenia układu. W tym przykładzie od szerokości obszaru zawartości odejmij marginesy poziome, obie przerwy i dwie stałe kolumny. Reszta przypada na opis. Są to przykładowe wymiary projektu, a nie pomiary natywnych kontrolek.
Szerokość opisu
= szerokość obszaru zawartości - marginesy poziome - przerwy - stałe kolumny
= szerokość obszaru zawartości - (2 × 16) - (2 × 12) - 96 - 72
= szerokość obszaru zawartości - 224
Przy 640 punktach: na opis pozostaje 416 punktów.
Przy 420 punktach: na opis pozostaje 196 punktów.
Minimalny budżet wiersza:
32 + 24 + 96 + 160 + 72 = 384 punkty.
Obliczenia sprawdzono za pomocą deterministycznego lokalnego wyliczenia. Opisują ten wiersz, gdy otrzymuje szerokość obszaru zawartości pomniejszoną o podane marginesy; nie uwzględniają ozdobników okna. Wybrane minimalne 420 punktów szerokości okna pozostawia zapas ponad obliczone minimum wiersza wynoszące 384 punkty. Jeśli otaczający kontener dodaje wcięcia, uwzględnij je również w budżecie.
To obliczenie wskazuje też niewykonalne wymaganie. Poniżej łącznego budżetu minimalnego wszystkie trzy kolumny nie mogą jednocześnie zachować szerokości i odstępów. Wybierz większą minimalną szerokość okna, mniejsze wymiary minimalne albo inny układ. Zwiększenie priorytetu nie tworzy dodatkowego miejsca.
Kiedy pomaga priorytet układu?
Priorytet przydaje się, gdy dwa elementy podrzędne mogą się zwężać, a chcesz, by jeden z nich zachował więcej miejsca. Na przykład opisowy tytuł może mieć pierwszeństwo przed dodatkową adnotacją. Umieść .layoutPriority(1) na elemencie podrzędnym uczestniczącym w decyzji tego widoku nadrzędnego o przydziale miejsca. Według Apple widoki o wyższym priorytecie zwężają się później i rozszerzają wcześniej, przy czym zachowywane jest miejsce na minimalne wymagania elementów o niższym priorytecie. Dokumentacja layoutPriority Apple
Kompletny przykład nie wymaga modyfikatora priorytetu: zasady przydziału miejsca wynikają już z dwóch stałych kolumn i jednej elastycznej. Wartość priorytetu nie jest też proporcjonalną wagą. Przypisanie priorytetów 2 i 1 nie ustanawia proporcji szerokości dwa do jednego; zgodnie z dokumentacją priorytet służy do określania preferencji podczas przydziału. Dokumentacja layoutPriority Apple
Unikaj dodawania poziomego Spacer między opisem a statusem, chyba że chcesz mieć osobną elastyczną przerwę. Spacja rozszerza się wzdłuż głównej osi stosu i rozsuwa elementy podrzędne. Ponieważ ramka opisu już przejmuje zmiany szerokości, dodatkowy element nie jest tu potrzebny. Dokumentacja Spacer Apple
Co powinno się stać, gdy tekst przestaje się mieścić?
Osobno zdecyduj, czy każde pole tekstowe ma zawijać tekst, obcinać go, czy wymagać innego układu. Opisy w przykładzie mogą zawijać się bez limitu wierszy. Zastosowanie .lineLimit(1) ograniczyłoby każdy objęty nim element tekstowy do jednego wiersza, zmieniając wymaganie. Limit wierszy nil usuwa to ograniczenie, ale nie znosi ograniczeń nakładanych przez ramki nadrzędne. Dokumentacja lineLimit Apple
W przypadku dłuższych statusów ponownie rozważ szerokość kolumny wynoszącą 72 punkty. Usuń zachowanie idealnego rozmiaru w poziomie, jeśli zawijanie jest dopuszczalne, zwiększ szerokość kolumny albo przenieś status pod opis. Zachowanie .fixedSize(horizontal: true, vertical: false) przy zwężaniu otaczającej ramki może sprawić, że tekst wyjdzie poza jej granice. Dokumentacja fixedSize Apple
Jeśli kompaktowe okno powinno zmieniać układ zawartości, macOS 13 i nowsze udostępniają ViewThatFits. Najpierw podaj układ poziomy, a następnie układ piętrowy. Mechanizm wybiera pierwszy element podrzędny, którego idealny rozmiar mieści się w ograniczonych osiach, dlatego przy ocenie dostępnego miejsca w poziomie użyj ViewThatFits(in: .horizontal), jeśli to szerokość decyduje o wyborze. Zadbaj, by ostatni wariant był użyteczny przy obsługiwanej minimalnej szerokości. Dokumentacja ViewThatFits Apple
Potraktuj to jako alternatywną zasadę układu. Obecny przykład celowo zachowuje kolumny i ustala minimalną szerokość okna. Użyj układu piętrowego jako wariantu zapasowego, gdy ważniejsza od zachowania układu poziomego jest możliwość zwężenia okna.
Jak osadzone widoki AppKit i TextKit zmieniają podejście?
Jeśli opis stanie się później komponentem tekstowym AppKit, użyj NSViewRepresentable, aby zintegrować ten widok. Apple wyraźnie stwierdza, że SwiftUI kontroluje właściwości reprezentowanego widoku frame i bounds; ich bezpośrednia zmiana we własnym kodzie koliduje z układem SwiftUI. Konfiguruj zawartość w cyklu życia obiektu reprezentującego, a rozmiar zewnętrzny określaj w SwiftUI. Dokumentacja NSViewRepresentable Apple
W macOS 13 i nowszych metoda sizeThatFits(_:nsView:context:) obiektu reprezentującego może zwracać preferowany rozmiar dla danej propozycji. W jednym przebiegu układu może być wywoływana wielokrotnie z różnymi propozycjami. Zwrócenie nil oznacza żądanie użycia domyślnego algorytmu określania rozmiaru. Pomiar komponentu zawijającego tekst powinien uwzględniać zaproponowaną szerokość, zamiast zawsze zwracać szerokość tekstu bez zawijania. Dokumentacja określania rozmiaru widoków reprezentowanych przez Apple
Rozróżniaj nieokreślone wymiary od zera: SwiftUI używa nieokreślonej propozycji, by zapytać o idealny rozmiar, a zera może użyć do sprawdzenia rozmiaru minimalnego. Niestandardowa procedura pomiarowa musi celowo obsługiwać oba przypadki. Dokumentacja ProposedViewSize Apple
W układzie AppKit odporność na kompresję określa, jak silnie NSView opiera się zmniejszeniu poniżej rozmiaru wewnętrznego. Sprawdź to ustawienie, gdy zawijany komponent nie chce się zwężać, równolegle z jego zachowaniem w układzie SwiftUI. Dokumentacja setContentCompressionResistancePriority Apple
W przypadku NSTextView korzystającego z TextKit 2 użyj nowoczesnej ścieżki textLayoutManager. Aktualny przegląd TextKit firmy Apple odróżnia ją od starszego layoutManager, a wskazówki dotyczące migracji wyjaśniają, że dostęp do starszej właściwości może spowodować jednokierunkowe przełączenie tej instancji na TextKit 1. Zmiana silnika tekstowego nie jest potrzebna do przydzielania miejsca kolumnom podrzędnym. Przegląd TextKit Apple, Nowości w TextKit i widokach tekstu
Sprawdź działanie przy granicznych rozmiarach okna
Uruchom przykład i sprawdź poniższe warunki, zanim zastosujesz ten wzorzec w większym interfejsie:
- Poszerz okno: kolumna opisu powinna się rozszerzyć, a ramki etykiety i statusu zachować szerokość.
- Zwęź okno do obsługiwanej wartości minimalnej: dłuższe opisy powinny się zawijać, zwiększając wysokość wierszy.
- Włącz i wyłącz opcję
Pokaż dłuższe opisy: krótkie opisy powinny zachować wyrównanie w tych samych kolumnach. - Zastąp status dłuższym przykładowym tekstem: zdecyduj, czy poszerzyć to pole, zawijać tekst, czy zmienić układ.
- Zmniejsz dostępną wysokość: upewnij się, że nadal można dotrzeć do wszystkich wierszy.
Aby sprawdzić, która warstwa otrzymuje miejsce, tymczasowo dodaj obramowania bezpośrednio przed i za elastyczną ramką opisu. Wewnętrzne obramowanie pokazuje granice tekstu, a zewnętrzne — przydzieloną kolumnę. Apple poleca kolorowe obramowania jako praktyczny sposób diagnozowania rozmiarów stosu i marginesów. Przewodnik po układach w SwiftUI
W miarę rozbudowy interfejsu jawnie określaj zasady rozmiaru: rezerwuj szerokość tam, gdzie wymaga tego wyrównanie, przydziel zmiennej zawartości określony elastyczny obszar i ustal, jak każde pole zachowuje się przy minimalnej szerokości. Dzięki tym zasadom i budżetowi szerokości możesz zmieniać etykiety lub dodawać zawartość bez zgadywania, który modyfikator powinien otrzymać wyższy priorytet.
Źródła
- Przewodnik po układach w 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
- Nowości w TextKit i widokach tekstu — Apple