Verwende für eine kleine Beschriftungsreihe in einer Mac-App ViewThatFits, um das längste vollständige Präfix auszuwählen, das passt, einschließlich der Anzahl ausgeblendeter Elemente. Dieses Tutorial erstellt die Reihe, ohne Beschriftungen abzuschneiden oder den Zustand während des Layouts zu aktualisieren.

Die konkrete Aufgabe ist ein in der Größe veränderbares macOS-Fenster mit sechs synthetischen Beschriftungen. Ihre Reihenfolge soll erhalten bleiben, nur vollständige Beschriftungen sollen angezeigt werden, und für den ausgelassenen Rest soll ein Badge wie 3 weitere erscheinen. Die Lösung setzt genügend horizontale Fläche für das Badge mit der Anzahl sowie genügend vertikale Fläche für eine Zeile voraus.

Vollständige Zeilenvarianten auswählen

Apple beschreibt ViewThatFits als Auswahl des ersten untergeordneten Elements, dessen ideale Größe in den vorgeschlagenen Platz auf den eingeschränkten Achsen passt. Die Funktion ist ab macOS 13 verfügbar. Verwende hier .horizontal, da es um die Zeilenbreite geht; die umgebende Benutzeroberfläche muss die Höhe der Zeile bereitstellen. ViewThatFits

Bei sechs Beschriftungen stellst du sieben Varianten in dieser Reihenfolge bereit: alle sechs Beschriftungen ohne Badge, fünf Beschriftungen plus 1 weitere, vier plus 2 weitere und so weiter, bis nur noch 6 weitere übrig ist. Jede Variante ist eine vollständige Zeile. Das Badge fließt in die Passformentscheidung ein, sodass das Hinzufügen der Anzahl nicht unerwartet die letzte sichtbare Beschriftung verdrängt.

Damit wird eine Präfixregel umgesetzt: Eine lange erste Beschriftung kann dazu führen, dass alle Beschriftungen ausgelassen werden, selbst wenn eine spätere kurze Beschriftung passen würde. Das ist beabsichtigt. Wenn deine Aufgabe stattdessen das Überspringen einzelner Beschriftungen zulässt, definiere diese andere Auswahlregel, bevor du sie implementierst.

Wende fixedSize() auf jede Zeilenvariante an, damit Beschriftungen und Badge ihre idealen Abmessungen behalten. Apple erklärt, dass dieser Modifikator eine Ansicht über die Grenzen ihres übergeordneten Elements hinaus vergrößern kann; allein garantiert er keine passende Größe. Hier übernimmt der umschließende Auswahlcontainer die Passformentscheidung. fixedSize()

Das vollständige macOS-Beispiel erstellen

Erstelle in Xcode eine macOS-SwiftUI-App und setze das Deployment-Ziel auf macOS 13 oder neuer. Ersetze die generierte App-Deklaration und die Inhaltsansicht durch das folgende Programm in einer einzigen Datei. Im Ziel muss genau eine App-Deklaration mit @main markiert sein.

Das Beispiel verwendet explizite Bezeichner statt Beschriftungstext als Identität. Apples Dokumentation zu ForEach verlangt identifizierte Elemente oder einen expliziten Schlüsselpfad als Bezeichner; unterschiedliche Bezeichner ermöglichen außerdem, dass zwei Elemente denselben angezeigten Titel haben. 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()
        }
    }
}

Die Bezeichner der Varianten sind die möglichen Anzahlen sichtbarer Elemente, absteigend von der Gesamtzahl bis null. Innerhalb jeder Variante identifizieren Elementbezeichner die Beschriftungen. Jede Iteration erstellt eine PrefixCandidate, die eine horizontale Zeile enthält.

Der äußere flexible Frame ermöglicht es der Zeile, den verfügbaren horizontalen Platz zu erhalten, und richtet den ausgewählten Inhalt zugleich am führenden Rand aus. Behalte fixedSize() für die Variante innerhalb von ViewThatFits bei. Wird es auf den gesamten Auswahlcontainer angewendet, ändert sich, wie dieser Container auf den Breitenvorschlag seines übergeordneten Elements reagiert.

Dies ist ein vollständiges Quellcodebeispiel, wurde hier jedoch nicht kompiliert oder ausgeführt, da keine Swift-Toolchain verfügbar war. Die Beispielrechnungen im nächsten Abschnitt wurden separat überprüft; die tatsächlichen Textbreiten müssen in der Mac-App geprüft werden.

Das Badge bei der Berechnung der passenden Größe berücksichtigen

Die hilfreiche Entscheidungsregel lautet: Wähle die größte Anzahl sichtbarer Elemente, deren vollständige Variante hineinpasst. Die Anzahl ausgeblendeter Elemente entspricht immer der Gesamtzahl der Elemente abzüglich der Anzahl sichtbarer Elemente in dieser Variante. Es gibt keine separate Schätzung, die synchron gehalten werden müsste.

Betrachte eine illustrative Zeile mit vier Elementen, deren Breiten 72, 96, 64 und 88 Punkte betragen, einschließlich der Abstände innerhalb der Elemente. Gehe von einem Abstand von 8 Punkten und einem 60 Punkte breiten Badge für jede nicht null betragende Anzahl ausgeblendeter Elemente aus. Dies sind erfundene Eingabewerte für die Rechnung, keine Messwerte der Beispiel-App.

Wenn alle vier Elemente sichtbar sind, beträgt die benötigte Breite 72 + 96 + 64 + 88 + 3 × 8 = 344 Punkte. Bei drei sichtbaren Elementen ersetzt das Badge den ausgelassenen Rest, sodass die Breite 72 + 96 + 64 + 3 × 8 + 60 = 316 Punkte beträgt. Es werden drei Abstände benötigt, da die Variante drei Elemente und ein Badge enthält.

Verfügbare Breite Sichtbare Elemente Ausgeblendete Elemente Breite der ausgewählten Variante
344 Punkte 4 0 344 Punkte
343 Punkte 3 1 316 Punkte
300 Punkte 2 2 244 Punkte
220 Punkte 1 3 140 Punkte
60 Punkte 0 4 60 Punkte

Im Programm enthält jede Variante den tatsächlichen Badge-Text. So werden Unterschiede zwischen Badge-Texten berücksichtigt, ohne sich auf die Annahme einer konstanten Breite aus der Beispielrechnung zu stützen. Die Breiten der Varianten müssen sich nicht gleichmäßig ändern; die absteigende Anzahl sichtbarer Elemente drückt die Auswahlpräferenz aus.

Die Variante, die nur die Anzahl enthält, legt die minimale nutzbare Breite fest. Wenn selbst dieses Badge nicht hineinpasst, ist es unter diesen Regeln unmöglich, die vollständige Anzahl innerhalb der verfügbaren Breite anzuzeigen. Stelle eine ausreichende Mindestbreite bereit, ermögliche eine andere Anordnung oder platziere die Anzahl an anderer Stelle. Abschneiden gilt nicht als erfolgreiches Ergebnis mit vollständigen Elementen.

Das Beispiel gibt eine Mindestbreite für den Inhalt und eine moderate explizite Schriftgröße für sein kurzes synthetisches Badge vor. Wenn du den Wortlaut, die Schrift, die Innenabstände oder das umgebende Layout änderst, prüfe den Fall mit der bloßen Anzahl erneut. Eine für dieses Beispiel gewählte Mindestbreite ist kein allgemeingültiger Messwert für Badges.

Die Layoutauswahl vom Lebenszyklus der Ansichten getrennt halten

Die Anzahl ausgeblendeter Elemente gehört zu den unveränderlichen Eingaben jeder Variante. Das Programm schreibt keinen Binding-Wert aus einer Messmethode, plant kein asynchrones Anzahlen-Update und leitet die Sichtbarkeit nicht aus Appearance-Callbacks ab. Mit der ausgewählten Zeile wird zugleich das passende Badge ausgewählt.

Halte den wiederholten Inhalt der Varianten einfach. Eine Beschriftung über mehrere Varianten hinweg zu wiederholen, verleiht ihr nicht automatisch eine gemeinsame Identität in allen Varianten. Apple erklärt, dass die Zustandsspeicherung an die Identität einer Ansicht gebunden ist und dass eine Änderung der Identität den Zustand ersetzen kann. Demystify SwiftUI

Wenn du die Beschriftungen später durch bearbeitbare Steuerelemente ersetzt, speichere ihre gemeinsamen Werte in einem gemeinsamen übergeordneten Element oder Modell und übergib die passenden Eingaben oder Bindings an jede Darstellung. Apple empfiehlt, gemeinsam genutzten Zustand im niedrigsten gemeinsamen Vorfahren der Ansichten abzulegen, die ihn benötigen. Verlasse dich nicht darauf, dass der lokale Zustand einer Variante einen Darstellungswechsel übersteht. Benutzeroberflächenzustand verwalten

Diese Lösung zeigt die Anzahl der ausgeblendeten Elemente in der ausgewählten Zeile an. Sie stellt die ausgewählte Anzahl keiner benachbarten Ansicht oder keinem Anwendungsmodell bereit. Apples dokumentierter ViewThatFits-Initializer akzeptiert eingeschränkte Achsen und den Inhalt der Varianten; eine Binding für die ausgewählte Anzahl bietet er nicht. Behandle die Weitergabe dieses Werts als zusätzliche Designanforderung. init(in:content:)

Wissen, wann ein eigenes Layout ein anderes Design erfordert

Ein eigenes Layout eignet sich, wenn du spezielle Geometrie benötigst. Apple definiert seine Kernaufgaben als das Melden der Gesamtgröße und das Zuweisen von Positionen an Unteransichten über Layout-Proxys. Die Proxys stellen Mess- und Platzierungsoperationen bereit, keinen direkten Zugriff auf die zugrunde liegenden Ansichten. Layout

Implementiere das Auslassen nicht einfach dadurch, dass du die Platzierung nach dem letzten passenden Element beendest. Apples Dokumentation zu LayoutSubview weist an, jede Unteransicht einmal zu platzieren. Die Platzierung zu überspringen ist kein dokumentierter Mechanismus zum Ausblenden, und ein Layout-Proxy bietet keine allgemeine Operation zum Umschreiben von Inhalten, mit der eine andere Unteransicht in ein neues Anzahl-Badge umgewandelt werden kann. LayoutSubview

Wenn du ein eigenes Layout erstellst, müssen Messung und Platzierung aufeinander abgestimmt sein, einschließlich Abständen und Größenangebot. Apple verlangt ausdrücklich, dass diese Berechnungen konsistent sind. Diese Vorgabe gilt auch dann, wenn das Layout mehrere mögliche Anordnungen prüft. placeSubviews(in:proposal:subviews:cache:)

Der Ansatz mit Varianten ist bewusst begrenzt. Bei sechs Elementen beschreibt er sieben Zeilenvarianten und 21 Elementdarstellungen über diese Zeilen hinweg. Im Allgemeinen wächst die Anzahl der Elementdarstellungen als n × (n + 1) / 2. Das ist eine strukturelle Zählung, kein Leistungsbenchmark und keine Aussage, dass jede Variante bei jeder Aktualisierung gerendert wird. Verwende den Ansatz für eine kleine Beschriftungssammlung; erstelle ein Profil, bevor du ihn auf eine große Sammlung oder aufwändige Inhalte ausweitest.

Die Zeile an ihren Grenzen prüfen

Starte die App und verkleinere das Fenster langsam. Prüfe die folgenden Bedingungen, bevor du die Komponente anpasst:

  • Bei großer Breite erscheinen alle Beschriftungen und es gibt kein Badge 0 weitere.
  • Wenn das Fenster schmaler wird, verschwinden Beschriftungen am Ende, ohne dass Beschriftungen teilweise angezeigt oder durch Ellipsen ersetzt werden.
  • Die Anzahl sichtbarer Elemente und die im Badge angegebene Anzahl ausgeblendeter Elemente ergeben immer zusammen sechs.
  • Bei einer außergewöhnlich langen ersten Beschriftung bleibt die Variante mit der bloßen Anzahl verfügbar.
  • Bei einem leeren Array zeigt die Zeile Keine Elemente an.
  • Bei doppelten Titeln und unterschiedlichen Bezeichnern werden beide Elemente bei der Anzahl berücksichtigt.
  • Nach Änderungen an Schrift oder Badge-Text passt die Variante mit der bloßen Anzahl weiterhin in die kleinste unterstützte Breite.

Bei einer kleinen, geordneten Zeile machen vollständige Varianten die Passformregel leicht nachvollziehbar: Jede Variante enthält ihre Beschriftungen, Abstände und die genaue Anzahl ausgeblendeter Elemente. Wenn du die Mindestbreite ausdrücklich vorgibst und den wiederholten Inhalt einfach hältst, passt sich die Zeile beim Ändern der Fenstergröße an, ohne teilweise angezeigte Elemente oder einen separaten Aktualisierungszyklus für die Anzahl.

Quellen