Gebruik voor een kleine rij labels in een Mac-app ViewThatFits om het langste volledige beginstuk te kiezen dat past, inclusief het aantal verborgen items. Deze tutorial bouwt die rij zonder labels af te kappen of de status tijdens de layout bij te werken.
De concrete taak is een aanpasbaar macOS-venster met zes synthetische labels. Behoud hun volgorde, toon alleen volledige labels en geef een badge weer, zoals 3 more, voor het weggelaten achtervoegsel. De oplossing gaat uit van voldoende horizontale ruimte voor de badge met alleen het aantal en voldoende verticale ruimte voor één rij.
Kies volledige rijalternatieven
Apple beschrijft ViewThatFits als een keuze voor het eerste onderliggende element waarvan de ideale grootte binnen de voorgestelde grootte past op de beperkte assen. Het is beschikbaar vanaf macOS 13. Gebruik hier .horizontal, omdat de taak om de rijbreedte draait; de omringende interface moet de hoogte van de rij leveren. ViewThatFits
Geef voor zes labels zeven alternatieven op, in deze volgorde: alle zes labels zonder badge, vijf labels plus 1 more, vier plus 2 more, enzovoort, eindigend met alleen 6 more. Elk alternatief is een volledige rij. De badge telt mee bij de pasvormbeslissing, zodat het toevoegen van het aantal niet onverwacht het laatste zichtbare label verdringt.
Dit implementeert een beleid dat het beginstuk behoudt: een lang eerste label kan ertoe leiden dat alle labels worden weggelaten, zelfs als een later kort label wel zou passen. Dat is opzettelijk. Als je taak in plaats daarvan toestaat individuele labels over te slaan, bepaal dan eerst dat andere selectiebeleid.
Pas fixedSize() toe op elke kandidaatrij, zodat de labels en badge hun ideale afmetingen behouden. Apple legt uit dat deze modifier een weergave buiten de grenzen van de bovenliggende weergave kan laten vallen; op zichzelf garandeert dit geen passende weergave. Hier voert de omvattende selectiecontainer de pasvormbeslissing uit. fixedSize()
Bouw het volledige macOS-voorbeeld
Maak in Xcode een macOS SwiftUI-app en stel het implementatiedoel in op macOS 13 of hoger. Vervang de gegenereerde app-declaratie en contentweergave door het volgende programma in één bestand. Houd precies één app-declaratie met de markering @main in het doel.
Het voorbeeld gebruikt expliciete identificatiegegevens in plaats van labeltekst als identiteit. Apple’s documentatie voor ForEach vereist geïdentificeerde elementen of een expliciet identificatiesleutelpad; met verschillende identificatiegegevens kunnen ook twee items dezelfde weergegeven titel hebben. 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()
}
}
}
De kandidaatidentificatiegegevens zijn de mogelijke aantallen zichtbare items, aflopend van het totale aantal items tot nul. Binnen elke kandidaat identificeren itemidentificatiegegevens de labels. Elke iteratie maakt één PrefixCandidate, die één horizontale rij bevat.
Het flexibele buitenste frame laat de rij de beschikbare horizontale ruimte innemen en houdt de geselecteerde inhoud links uitgelijnd. Laat fixedSize() op de kandidaat binnen ViewThatFits staan. Als je dit toepast op de volledige selectiecontainer, verandert de manier waarop die container reageert op het breedtevoorstel van de bovenliggende weergave.
Dit is een volledig broncodevoorbeeld, maar het is hier niet gecompileerd of uitgevoerd omdat er geen Swift-toolchain beschikbaar was. De rekenkundige illustratie in de volgende sectie is afzonderlijk gecontroleerd; de werkelijke tekstbreedtes moeten in de Mac-app worden geverifieerd.
Neem de badge mee bij de berekening van wat past
De bruikbare beslisregel is: selecteer het grootste aantal zichtbare items waarvan de volledige kandidaat past. Het verborgen aantal is altijd het totale aantal items min het zichtbare aantal van die kandidaat. Er is geen aparte schatting die gesynchroniseerd moet worden.
Neem een illustratieve rij met vier items met breedtes van 72, 96, 64 en 88 punten, gemeten inclusief itemopvulling. Ga uit van een tussenruimte van 8 punten en een badge van 60 punten voor elk niet-nul aantal verborgen items. Dit zijn verzonnen invoerwaarden voor de berekening, geen metingen van de voorbeeldapp.
Wanneer alle vier items zichtbaar zijn, is de benodigde breedte 72 + 96 + 64 + 88 + 3 × 8 = 344 punten. Met drie zichtbare items vervangt de badge het weggelaten achtervoegsel, waardoor de breedte 72 + 96 + 64 + 3 × 8 + 60 = 316 punten wordt. Er zijn drie tussenruimtes nodig, omdat de kandidaat drie items en één badge bevat.
| Beschikbare breedte | Zichtbare items | Verborgen items | Breedte van geselecteerde kandidaat |
|---|---|---|---|
| 344 punten | 4 | 0 | 344 punten |
| 343 punten | 3 | 1 | 316 punten |
| 300 punten | 2 | 2 | 244 punten |
| 220 punten | 1 | 3 | 140 punten |
| 60 punten | 0 | 4 | 60 punten |
In het programma bevat elke kandidaat de daadwerkelijke badgetekst. Daardoor houdt die rekening met verschillen tussen badgeteksten, zonder afhankelijk te zijn van de aanname van een constante breedte in de illustratie. De kandidaatbreedtes hoeven niet telkens met dezelfde hoeveelheid te veranderen; de aflopende aantallen zichtbare items geven de selectievoorkeur weer.
De kandidaat met alleen het aantal bepaalt de minimaal bruikbare breedte. Als zelfs die badge niet past, is het onder deze regels onmogelijk om het volledige aantal binnen de beschikbare breedte weer te geven. Zorg voor voldoende minimum breedte, sta een andere indeling toe of verplaats het aantal. Beschouw afkappen niet als een geslaagd resultaat waarbij volledige items worden getoond.
Het voorbeeld stelt een minimale inhoudsbreedte en een bescheiden expliciet lettertype in voor de korte synthetische badge. Als je de tekst, het lettertype, de opvulling of de omringende indeling wijzigt, controleer dan de situatie met alleen het aantal opnieuw. Een minimum breedte die voor dit voorbeeld is gekozen, is geen universele badgebreedte.
Houd de layoutkeuze los van de levenscyclus van weergaven
Het verborgen aantal hoort bij de onveranderlijke invoer van elke kandidaat. Het programma schrijft geen binding vanuit een meetmethode, plant geen asynchrone update van het aantal en leidt de zichtbaarheid niet af uit callbacks voor weergave. De gekozen rij bepaalt ook de bijbehorende badge.
Houd de herhaalde kandidaatinhoud eenvoudig. Een label herhalen in verschillende alternatieven geeft dat label niet één gedeelde identiteit in al die alternatieven. Apple legt uit dat statusopslag aan de identiteit van de weergave is gekoppeld en dat een gewijzigde identiteit de status kan vervangen. Demystify SwiftUI
Als je de labels later vervangt door bewerkbare bedieningselementen, houd hun gedeelde waarden dan in een gemeenschappelijke bovenliggende weergave of model en geef de juiste invoerwaarden of bindings door aan elke presentatie. Apple raadt aan gedeelde status te plaatsen in de laagste gemeenschappelijke bovenliggende weergave van de weergaven die deze nodig hebben. Ga er niet van uit dat lokale status van een kandidaat behouden blijft wanneer de presentatie verandert. Managing user interface state
Deze oplossing toont het verborgen aantal in de geselecteerde rij. Het geselecteerde aantal wordt niet beschikbaar gemaakt voor een naastliggende weergave of het applicatiemodel. De gedocumenteerde ViewThatFits-initializer van Apple accepteert beperkte assen en kandidaatinhoud; hij biedt geen binding voor het geselecteerde aantal. Behandel het exporteren van die waarde als een aanvullende ontwerpeis. init(in:content:)
Weet wanneer een aangepaste layout een ander ontwerp vereist
Een aangepaste Layout is geschikt wanneer je gespecialiseerde geometrie nodig hebt. Apple omschrijft de kerntaken ervan als het rapporteren van de samengestelde grootte en het toewijzen van posities aan onderliggende weergaven via layoutproxies. De proxies bieden bewerkingen voor meten en plaatsen, geen directe toegang tot de onderliggende weergaven. Layout
Implementeer het weglaten niet door simpelweg te stoppen met plaatsen na het laatste item dat past. Apple’s documentatie voor LayoutSubview schrijft voor dat je elke onderliggende weergave één keer plaatst. Plaatsing overslaan is geen gedocumenteerde manier om iets te verbergen, en een layoutproxy biedt geen algemene bewerking voor het wijzigen van inhoud waarmee je een andere onderliggende weergave omzet in een nieuw label met het aantal. LayoutSubview
Als je een aangepaste layout bouwt, zorg dan dat meting en plaatsing overeenstemmen, inclusief de tussenruimte en het gebruikte groottevoorstel. Apple vereist expliciet dat die berekeningen consistent zijn. Dat contract geldt ook wanneer de layout verschillende mogelijke indelingen onderzoekt. placeSubviews(in:proposal:subviews:cache:)
De kandidaatbenadering heeft bewust een beperkte reikwijdte. Voor zes items beschrijft ze zeven kandidaatrijen en 21 itemweergaven verspreid over die rijen. In het algemeen groeit het aantal itemweergaven volgens n × (n + 1) / 2. Dit is een structurele telling, geen prestatiebenchmark en geen bewering dat elke kandidaat bij elke update wordt weergegeven. Gebruik dit voor een kleine verzameling labels; meet de prestaties voordat je het uitbreidt naar een grote verzameling of inhoud die veel werk kost.
Controleer de rij op de grensgevallen
Start de app en wijzig de venstergrootte langzaam. Controleer de volgende voorwaarden voordat je de component aanpast:
- Bij een brede breedte verschijnt elk label en is er geen badge
0 more. - Wanneer het venster smaller wordt, verdwijnen labels aan het einde zonder gedeeltelijke labels of weglatingstekens.
- Het aantal zichtbare items plus het verborgen aantal op de badge is altijd zes.
- Bij één uitzonderlijk lang eerste label blijft de kandidaat met alleen het aantal beschikbaar.
- Bij een lege array toont de rij
No items. - Bij dubbele titels en verschillende identificatiegegevens blijven beide items in de telling vertegenwoordigd.
- Nadat je lettertypen of badgetekst hebt gewijzigd, past de kandidaat met alleen het aantal nog steeds binnen de kleinste ondersteunde breedte.
Voor een kleine, geordende rij maken volledige alternatieven de pasvormregel eenvoudig te controleren: elke kandidaat bevat de labels, tussenruimtes en het exacte verborgen aantal. Houd de voorwaarde voor de minimum breedte expliciet en de herhaalde inhoud licht, en de rij kan zich aan het wijzigen van de venstergrootte aanpassen zonder gedeeltelijke items of een aparte cyclus voor het bijwerken van het aantal.
Bronnen
- 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