För en liten rad etiketter i en Mac-app använder du ViewThatFits för att välja det längsta kompletta prefix som får plats, inklusive antalet dolda objekt. Den här handledningen bygger raden utan att trunkera etiketter eller uppdatera tillstånd under layouten.
Den konkreta uppgiften är ett storleksändringsbart macOS-fönster med sex syntetiska etiketter. Bevara deras ordning, visa bara hela etiketter och visa en bricka som 3 more för den utelämnade suffixdelen. Lösningen förutsätter tillräckligt med horisontellt utrymme för brickan med enbart antal och tillräckligt med vertikalt utrymme för en rad.
Välj kompletta radalternativ
Apple beskriver ViewThatFits som en komponent som väljer det första barnet vars idealstorlek ryms inom den föreslagna storleken längs de begränsade axlarna. Den finns i macOS 13 och senare. Använd .horizontal här eftersom uppgiften gäller radens bredd; det omgivande gränssnittet måste tillhandahålla radens höjd. ViewThatFits
För sex etiketter tillhandahåller du sju alternativ i den här ordningen: alla sex etiketter utan bricka, fem etiketter plus 1 more, fyra plus 2 more och så vidare, med enbart 6 more som sista alternativ. Varje alternativ är en komplett rad. Brickan ingår i passformsbeslutet, så tillägget av antalet kan inte oväntat tränga undan den sista synliga etiketten.
Detta implementerar en prefixpolicy: en lång första etikett kan göra att alla etiketter utelämnas, även om en senare kort etikett skulle få plats. Det är avsiktligt. Om uppgiften i stället tillåter att enskilda etiketter hoppas över ska du definiera den urvalsprincipen innan du implementerar den.
Använd fixedSize() på varje kandidatrad så att etiketterna och brickan behåller sina idealmått. Apple förklarar att den här modifieraren kan göra att en vy överskrider sin förälders gränser; den garanterar inte i sig att innehållet får plats. Här utför den omslutande urvalskomponenten passformsbedömningen. fixedSize()
Bygg det kompletta macOS-exemplet
Skapa en macOS SwiftUI-app i Xcode och ange macOS 13 eller senare som distributionsmål. Ersätt den genererade appdeklarationen och innehållsvyn med följande program i en enda fil. Behåll exakt en appdeklaration märkt @main i målet.
Exemplet använder uttryckliga identifierare i stället för etikettext som identitet. Apples dokumentation för ForEach kräver identifierade element eller en uttrycklig nyckelväg för identifierare; unika identifierare gör det också möjligt för två objekt att ha samma visade titel. ForEach
import SwiftUI
struct DemoItem: Identifiable {
let id: Int
let title: String
}
struct ItemChip: View {
let title: String
var body: some View {
Text(title)
.lineLimit(1)
.padding(.horizontal, 10)
.padding(.vertical, 6)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(Color.blue.opacity(0.12))
)
}
}
struct PrefixCandidate: View {
let items: [DemoItem]
let visibleCount: Int
var body: some View {
HStack(spacing: 8) {
ForEach(items.prefix(visibleCount)) { item in
ItemChip(title: item.title)
}
if visibleCount < items.count {
Text("\(items.count - visibleCount) more")
.lineLimit(1)
.padding(.horizontal, 10)
.padding(.vertical, 6)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(Color.gray.opacity(0.15))
)
}
}
.fixedSize()
}
}
struct WholeItemRow: View {
let items: [DemoItem]
var body: some View {
if items.isEmpty {
Text("No items")
.fixedSize()
} else {
ViewThatFits(in: .horizontal) {
ForEach(
Array((0...items.count).reversed()),
id: \.self
) { visibleCount in
PrefixCandidate(
items: items,
visibleCount: visibleCount
)
}
}
}
}
}
struct ContentView: View {
let items: [DemoItem] = [
DemoItem(id: 1, title: "Sketch"),
DemoItem(id: 2, title: "Color samples"),
DemoItem(id: 3, title: "Shapes"),
DemoItem(id: 4, title: "Paper textures"),
DemoItem(id: 5, title: "Layout notes"),
DemoItem(id: 6, title: "Preview")
]
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Sample labels")
.font(.headline)
WholeItemRow(items: items)
.frame(maxWidth: .infinity, alignment: .leading)
Text("Resize the window to change how many labels fit.")
.foregroundStyle(.secondary)
}
.font(.system(size: 14))
.padding(24)
.frame(minWidth: 320, idealWidth: 680, minHeight: 160)
}
}
@main
struct WholeItemsDemoApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Kandidaternas identifierare är de möjliga antalen synliga objekt, i fallande ordning från objektantalet till noll. Inom varje kandidat identifierar objektens identifierare etiketterna. Varje iteration skapar en PrefixCandidate som innehåller en horisontell rad.
Den yttre flexibla ramen låter raden få tillgång till det tillgängliga horisontella utrymmet samtidigt som det valda innehållet hålls justerat mot den ledande kanten. Behåll fixedSize() på kandidaten inuti ViewThatFits. Om den används på hela urvalskomponenten förändras hur den komponenten reagerar på förälderns breddförslag.
Det här är ett komplett källkodsexempel, men det kompilerades eller kördes inte här eftersom ingen Swift-verktygskedja var tillgänglig. Beräkningsexemplet i nästa avsnitt kontrollerades separat; de faktiska textbredderna måste verifieras i Mac-appen.
Ta med brickan när du beräknar vad som får plats
Den användbara beslutsregeln är: välj det största antalet synliga objekt vars hela kandidat får plats. Antalet dolda objekt är alltid det totala antalet objekt minus kandidatens antal synliga objekt. Det finns ingen separat uppskattning att synkronisera.
Tänk dig en illustrativ rad med fyra objekt som är 72, 96, 64 respektive 88 punkter breda, inklusive objektens utfyllnad. Anta 8 punkters mellanrum och en bricka som är 60 punkter bred för varje antal dolda objekt som inte är noll. Detta är påhittade beräkningsvärden, inte mätningar från exempelappen.
När alla fyra objekt är synliga är den nödvändiga bredden 72 + 96 + 64 + 88 + 3 × 8 = 344 punkter. Med tre synliga objekt ersätter brickan det utelämnade suffixet, så bredden blir 72 + 96 + 64 + 3 × 8 + 60 = 316 punkter. Tre mellanrum behövs eftersom kandidaten innehåller tre objekt och en bricka.
| Tillgänglig bredd | Synliga objekt | Dolda objekt | Vald kandidatbredd |
|---|---|---|---|
| 344 punkter | 4 | 0 | 344 punkter |
| 343 punkter | 3 | 1 | 316 punkter |
| 300 punkter | 2 | 2 | 244 punkter |
| 220 punkter | 1 | 3 | 140 punkter |
| 60 punkter | 0 | 4 | 60 punkter |
I programmet innehåller varje kandidat sin faktiska bricktext. Därmed hanteras skillnader mellan bricksträngar utan att förlita sig på illustrationens antagande om konstant bredd. Kandidatbredderna behöver inte förändras med ett jämnt belopp; en minskning av antalet synliga objekt uttrycker urvalspreferensen.
Kandidaten med enbart antal anger den minsta användbara bredden. Om inte ens den brickan får plats är det omöjligt att visa hela antalet inom det tillgängliga utrymmet enligt dessa regler. Ange en tillräcklig minsta bredd, tillåt en annan layout eller placera antalet någon annanstans. Betrakta inte beskärning som ett lyckat resultat med hela objekt.
Exemplet anger en minsta innehållsbredd och ett måttligt uttryckligt teckensnitt för den korta syntetiska brickan. Om du ändrar formuleringen, teckensnittet, utfyllnaden eller den omgivande layouten ska du kontrollera fallet med enbart antal igen. En minsta bredd som valts för det här exemplet är inte ett universellt mått på brickor.
Håll layouturvalet åtskilt från vyernas livscykel
Det dolda antalet hör till varje kandidats oföränderliga indata. Programmet skriver inte till en bindning från en mätmetod, schemalägger ingen asynkron uppdatering av antalet och härleder inte synlighet från utseendeåteranrop. Valet av rad väljer också den tillhörande brickan.
Håll det upprepade kandidatinnehållet enkelt. Att upprepa en etikett mellan alternativ innebär inte att etiketten har en gemensam identitet i alla alternativ. Apple förklarar att tillståndslagring är kopplad till vyidentitet och att en ändrad identitet kan ersätta tillståndet. Demystify SwiftUI
Om du senare ersätter etiketterna med redigerbara kontroller ska du behålla deras delade värden i en gemensam förälder eller modell och skicka lämpliga indata eller bindningar till varje presentation. Apple rekommenderar att delat tillstånd placeras i den minsta gemensamma föräldern för de vyer som behöver det. Räkna inte med att tillstånd som tillhör en enskild kandidat finns kvar när presentationen ändras. Hantera gränssnittstillstånd
Den här lösningen visar det dolda antalet i den valda raden. Den exponerar inte det valda antalet för en syskonvy eller applikationsmodell. Apples dokumenterade ViewThatFits-initierare tar emot begränsade axlar och kandidatinnehåll; den tillhandahåller ingen bindning för valt antal. Behandla export av det värdet som ett ytterligare designkrav. init(in:content:)
Förstå när en anpassad layout kräver en annan utformning
En anpassad Layout passar när du behöver specialiserad geometri. Apple definierar dess kärnansvar som att rapportera den sammansatta storleken och ange under vynas positioner genom layoutproxier. Proxierna tillhandahåller mät- och placeringsoperationer snarare än direkt åtkomst till de underliggande vyerna. Layout
Implementera inte utelämning genom att helt enkelt sluta placera objekt efter det sista som får plats. Apples dokumentation för LayoutSubview instruerar dig att placera varje under vy en gång. Att hoppa över placering är ingen dokumenterad metod för att dölja innehåll, och en layoutproxy tillhandahåller ingen allmän innehållsomskrivningsoperation för att göra om en annan under vy till en ny antalsbricka. LayoutSubview
Om du bygger en anpassad layout ska du bevara överensstämmelsen mellan mätning och placering, inklusive mellanrum och den storleksförfrågan som används. Apple kräver uttryckligen att dessa beräkningar är konsekventa. Det kravet gäller även när layouten provar flera möjliga arrangemang. placeSubviews(in:proposal:subviews:cache:)
Kandidatmetoden har avsiktligt en begränsad omfattning. För sex objekt beskriver den sju kandidatrader och 21 objektförekomster över dessa rader. Generellt växer antalet objektförekomster enligt n × (n + 1) / 2. Detta är en strukturell räkning, inte ett prestandamått eller ett påstående om att varje kandidat återges vid varje uppdatering. Använd metoden för en liten samling etiketter; profilera innan du utökar den till en stor samling eller innehåll som kräver mycket beräkning.
Verifiera raden vid dess gränser
Kör appen och ändra långsamt storleken på fönstret. Kontrollera följande innan du anpassar komponenten:
- Vid en bred fönsterbredd visas alla etiketter och ingen bricka med
0 morevisas. - När fönstret blir smalare försvinner etiketterna från slutet utan att någon etikett kapas eller får ellipser.
- Antalet synliga objekt plus brickans antal dolda objekt är alltid sex.
- Med en ovanligt lång första etikett är kandidaten med enbart antal fortfarande tillgänglig.
- Med en tom array visar raden
No items. - Med dubbletter av titlar och olika identifierare representeras båda objekten i antalet.
- När du har ändrat teckensnitt eller bricktext får kandidaten med enbart antal fortfarande plats inom den minsta bredd som stöds.
För en liten, ordnad rad gör kompletta alternativ passformsregeln enkel att granska: varje kandidat innehåller sina etiketter, mellanrum och exakta dolda antal. Om du anger minsta bredd uttryckligen och håller det upprepade innehållet lättviktigt kan raden anpassas när fönstret ändrar storlek utan att objekt kapas eller en separat uppdatering av antalet krävs.
Källor
- ViewThatFits — Apple
- fixedSize() — Apple
- ForEach — Apple
- Demystify SwiftUI — Apple
- Managing user interface state — Apple
- init(in:content:) — Apple
- Layout — Apple
- LayoutSubview — Apple
- placeSubviews(in:proposal:subviews:cache:) — Apple