Um Seitenleisten-Steuerelemente an eine SwiftUI-Vorschau mit Seitenverhältnis anzupassen, berechnen Sie die eingepasste Vorschaugröße anhand des verfügbaren Fensterbereichs und geben Sie den Steuerelementen anschließend dieselbe Höhe. Dieses macOS-Beispiel hält eine synthetische Vorschau proportional und ermöglicht das Scrollen überlaufender Steuerelemente.

Die Aufgabe besteht darin, einen in der Größe veränderbaren Desktop-Editor mit einer Vorschau links und einem Steuerelemente-Bereich fester Breite rechts zu erstellen. Hier bezeichnet „Vorschau“ den Inhalt innerhalb Ihrer laufenden App. Ziel ist es, die oberen und unteren Layoutkanten der beiden Bereiche bei Größenänderungen des Fensters auszurichten.

Welche Höhe sollten die Steuerelemente übernehmen?

Legen Sie zunächst das Rechteck fest, an das Sie die Steuerelemente anpassen möchten. Eine Vorschau mit eingepasstem Seitenverhältnis kann weniger Platz einnehmen als ihr umgebender Container. Übernehmen die Steuerelemente die Höhe des Containers, können sie unter die sichtbare Vorschau hinausragen, obwohl beide Ansichten korrekt angeordnet sind.

Apple definiert das Seitenverhältnis als Breite geteilt durch Höhe. Bei größenveränderbarem Inhalt bewahrt aspectRatio(_:contentMode:) mit .fit dieses Verhältnis, während der Inhalt in den vorgeschlagenen Bereich eingepasst wird. Das Ellipsenbeispiel in der Dokumentation zeigt, warum die eingepasste Form und ein umgebender quadratischer Rahmen unterschiedliche Abmessungen haben können. Apples Dokumentation zum Seitenverhältnis.

Diese Implementierung macht das eingepasste Rechteck explizit. Sie reserviert eine feste Breite für die Steuerelemente und einen Abstand zwischen den Bereichen und passt die Vorschau dann in die verbleibende Breite und verfügbare Höhe ein. Beide Bereiche erhalten die berechnete Vorschauhöhe.

Das ist eine Layoutregel für dieses Beispiel: Die Vorschau bestimmt die Höhe des Ansichtsbereichs für die Steuerelemente. Eine lange Steuerelementliste lässt sich innerhalb dieses Ansichtsbereichs scrollen, statt die Vorschau zu vergrößern. Apple beschreibt ScrollView als eine scrollbare Region, in der ein verschiebbarer Ausschnitt des zugrunde liegenden Inhalts angezeigt wird. Apples Dokumentation zu Scroll-Views.

Das eingepasste Rechteck einmal berechnen

Seien W und H die verfügbaren Inhaltsabmessungen nach dem äußeren Innenabstand, C die Breite der Steuerelemente, G der Abstand und R das Verhältnis von Vorschau-Breite zu -Höhe. Die Berechnung lautet:

remainingWidth = max(0, W - C - G)
previewWidth = min(remainingWidth, H * R)
previewHeight = previewWidth / R
controlsHeight = previewHeight

Die erste Begrenzung verhindert, dass die Vorschau den horizontalen Platz der Steuerelemente nutzt. Die zweite verhindert, dass ein breites Fenster eine Vorschau erzeugt, die höher als der verfügbare Bereich ist. Wird die Höhe aus der gewählten Breite berechnet, bleibt das Verhältnis in beiden Fällen erhalten.

Angenommen, der Inhaltsbereich ist vor dem Innenabstand 960 × 600 Punkte groß. Bei 16 Punkten Innenabstand an jeder Kante, einem 220 Punkte breiten Steuerelemente-Bereich, einem Abstand von 16 Punkten und einer Vorschau im Verhältnis 16:9 ist der verfügbare Bereich 928 × 568 Punkte groß. Die verbleibende Vorschau-Breite beträgt 692 Punkte, daraus ergibt sich eine Vorschau-Höhe von 389,25 Punkten. Der Ansichtsbereich für die Steuerelemente ist daher 220 × 389,25 Punkte groß.

Dies sind veranschaulichende Eingabewerte, keine gemessenen Anwendungsergebnisse. Sie zeigen außerdem, warum die Steuerelemente an die volle verfügbare Höhe von 568 Punkten anzupassen, zu einer falschen Ausrichtung führen würde.

Verwenden Sie für die Layoutabmessungen CGFloat und ein positives, endliches Verhältnis. In Swift macht let ratio: CGFloat = 16.0 / 9.0 die beabsichtigte Gleitkommarechnung explizit. Wenn Ihre App später konfigurierbare Verhältnisse zulässt, validieren Sie diese vor der Division.

Das vollständige macOS-Beispiel erstellen

Erstellen Sie in Xcode eine macOS-SwiftUI-App und legen Sie das Bereitstellungsziel auf macOS 13 oder neuer fest. Ersetzen Sie die generierten App- und Content-View-Deklarationen durch diese einzelne Datei und belassen Sie genau eine @main-Deklaration im Ziel. Das Beispiel benötigt weder Assets noch externe Pakete.

Die Bereitstellungsanforderung ergibt sich aus der hier verwendeten API zur Fenstergrößenanpassung. Die Szene verwendet .contentMinSize, wodurch laut Apple die Mindestgröße des Fensters aus seinem Inhalt abgeleitet wird, ohne eine maximale Größe festzulegen. Apples Dokumentation zur Fenstergrößenanpassung, Apples Dokumentation zur Mindestgröße des Inhalts.

import SwiftUI

@main
struct MatchedPreviewApp: App {
    var body: some Scene {
        WindowGroup {
            PreviewEditor()
                .frame(minWidth: 640, minHeight: 360)
        }
        .windowResizability(.contentMinSize)
    }
}

struct PreviewEditor: View {
    @State private var intensity: Double = 0.5
    @State private var showsLabel = true

    private let ratio: CGFloat = 16.0 / 9.0
    private let controlsWidth: CGFloat = 220
    private let gap: CGFloat = 16

    var body: some View {
        GeometryReader { geometry in
            let availableHeight = max(0, geometry.size.height)
            let remainingWidth = max(
                0,
                geometry.size.width - controlsWidth - gap
            )
            let previewWidth = min(
                remainingWidth,
                availableHeight * ratio
            )
            let previewHeight = previewWidth / ratio

            HStack(alignment: .top, spacing: gap) {
                RoundedRectangle(cornerRadius: 12)
                    .fill(Color.blue.opacity(0.2 + intensity * 0.7))
                    .aspectRatio(ratio, contentMode: .fit)
                    .overlay {
                        Text("Sample composition")
                            .font(.headline)
                            .opacity(showsLabel ? 1 : 0)
                    }
                    .frame(
                        width: previewWidth,
                        height: previewHeight
                    )

                ScrollView(.vertical) {
                    VStack(alignment: .leading, spacing: 16) {
                        Text("Preview controls")
                            .font(.headline)

                        Toggle("Show label", isOn: $showsLabel)

                        VStack(alignment: .leading, spacing: 8) {
                            Text("Color intensity")
                            Slider(value: $intensity, in: 0...1)
                        }

                        ForEach(1...8, id: \.self) { index in
                            Text("Sample setting \(index)")
                                .font(.caption)
                        }
                    }
                    .padding(16)
                    .frame(maxWidth: .infinity, alignment: .leading)
                }
                .frame(
                    width: controlsWidth,
                    height: previewHeight
                )
                .background(Color.gray.opacity(0.08))
            }
            .frame(
                width: geometry.size.width,
                height: geometry.size.height,
                alignment: .center
            )
        }
        .padding(16)
    }
}

GeometryReader liefert die Größe des Bereichs, der dem Editor zugewiesen wurde. Apple beschreibt ihn als Element, das Inhalte anhand seiner eigenen Größe und seines eigenen Koordinatenraums definiert und eine flexible bevorzugte Größe hat. Wird der Innenabstand außerhalb des Readers platziert, verwendet die Berechnung den um diesen Innenabstand verringerten Inhaltsbereich. Apples Dokumentation zu GeometryReader.

Die beiden festen Frames legen die gemeinsame Höhe fest. Laut Apple erstellt frame(width:height:alignment:) einen unsichtbaren Frame mit den angegebenen Abmessungen. Der Hintergrund der Steuerelemente folgt auf diesen Frame und bedeckt daher den Ansichtsbereich der Steuerelemente einschließlich des Bereichs unter einer kurzen Liste. Apples Dokumentation zu festen Frames.

Der abschließende Frame zentriert die gesamte Zeile im Reader. Er vergrößert keinen der beiden Bereiche. Deshalb kann ein hohes Fenster oberhalb und unterhalb der Zeile ungenutzten Platz enthalten, während die Steuerelemente weiterhin an die Vorschau angepasst bleiben.

Zustand und Lebenszyklusverhalten bei Größenänderungen erhalten

Halten Sie benutzergesteuerte Werte von der berechneten Geometrie getrennt. Im Beispiel sind intensity und showsLabel Zustände; Vorschau-Breite und -Höhe sind lokale Werte, die aus der aktuell verfügbaren Größe abgeleitet werden.

Apple beschreibt Zustand als von SwiftUI verwalteten Speicher, dessen Änderungen abhängige Teile der View-Hierarchie aktualisieren. Dadurch können Slider- und Umschaltwerte im Editor verbleiben, dem sie gehören. Apples Dokumentation zu State.

Die Layoutberechnung enthält keine Zustandszuweisungen, Appearance-Callbacks, asynchrone Arbeit oder Messrückkopplung. Beim Ändern der Fenstergröße ändern sich die Abmessungen, die von derselben View-Struktur verwendet werden. Die Beschriftung bleibt sichtbar und ändert ihre Deckkraft, wenn sich der Zustand des Umschalters ändert.

Wenn Sie dies in einen vorhandenen Editor übernehmen, behalten Sie dessen Modellverantwortung und Binding-Typen bei. Wenden Sie die Größenregel auf Vorschau und Steuerelemente an; verschieben Sie die Modellerstellung nicht in die Geometrieberechnung. Für die Berechnung in diesem Beispiel sind nur die verfügbaren Abmessungen und das gewählte Verhältnis erforderlich.

Größenänderungen, Überlauf und Reihenfolge der Modifier prüfen

Verwenden Sie die folgenden Abmessungen zur Überprüfung der Berechnung. Sie beschreiben den Inhaltsbereich vor dem äußeren Innenabstand des Beispiels, nicht die Außenmaße des Fensters. Die erwarteten Werte sind auf zwei Dezimalstellen gerundet.

Inhaltsbereich Vorschaugröße Ansichtsbereich der Steuerelemente
960 × 600 Punkte 692 × 389,25 220 × 389,25
640 × 360 Punkte 372 × 209,25 220 × 209,25
1200 × 400 Punkte 654,22 × 368 220 × 368
700 × 900 Punkte 432 × 243 220 × 243

Die Berechnung wurde mit einer kleinen Python-Rechnung überprüft. Dabei wurde auch geprüft, dass die eingepasste Vorschau innerhalb der verfügbaren Höhe bleibt und beide Bereiche samt Abstand horizontal hineinpassen. Swift und Xcode waren in der Forschungsumgebung nicht verfügbar. Daher wurde das vollständige SwiftUI-Programm begutachtet, aber weder kompiliert noch ausgeführt.

Führen Sie die App in Xcode aus und passen Sie ihre Größe an breite, flache und hohe Formen an. Prüfen Sie, ob die Bereichskanten ausgerichtet bleiben, die Steuerelemente scrollen, wenn ihr Inhalt den Ansichtsbereich übersteigt, und Slider sowie Umschalter ihre Werte beim Ändern der Fenstergröße beibehalten.

Wenn die Ausrichtung nach der Übernahme des Beispiels noch abweicht, prüfen Sie diese konkreten Bedingungen:

  • Zusätzlicher Innenabstand im Bereich: Ein Innenabstand, der außerhalb des festen Frames nur auf einen Bereich angewendet wird, vergrößert dessen Gesamtfläche. Platzieren Sie den Innenabstand der Steuerelemente wie gezeigt innerhalb der Scroll-View.
  • Ein größerer Vorschau-Wrapper: Wenn Sie einen größeren Frame um die Vorschau legen, entscheiden Sie, ob die Steuerelemente an diesen Wrapper oder an den eingepassten Inhalt angepasst werden sollen. Die Berechnung hier passt sie an das eingepasste Rechteck an.
  • Überlaufender benutzerdefinierter Inhalt: Ein Frame legt Layoutabmessungen fest, blendet aber nicht automatisch Zeichnungen aus, die über diese Abmessungen hinausragen. Apple dokumentiert .clipped() zum Beschneiden auf ein Rechteck. Verwenden Sie es, wenn ein beabsichtigter Überlauf der Vorschau ausgeblendet werden soll. Lassen Sie die Steuerelemente scrollbar, damit alle Einstellungen erreichbar bleiben. Apples Dokumentation zum Beschneiden.
  • Unzureichender Platz im übergeordneten Container: Das Beispiel setzt seine minimale Inhaltsgröße voraus. Wird es in einen kleineren Container eingebettet, lässt die feste Breite der Steuerelemente möglicherweise keine sinnvolle Vorschau-Breite übrig. Wählen Sie für diesen Container einen schmaleren Bereich, eine vertikale Anordnung oder eine explizite Mindestgröße.

Wann sollten Sie stattdessen ein benutzerdefiniertes Layout verwenden?

Dieser Ansatz eignet sich für einen Editor, der einen begrenzten Fensterbereich erhält und ein bekanntes Seitenverhältnis verwendet. Seine Größenregel ist explizit und hängt nicht davon ab, den internen Inhalt der Vorschau zu messen.

Wenn Ihre wiederverwendbare Komponente ihre bevorzugte Größe aus ihren untergeordneten Elementen ableiten muss, ziehen Sie ein benutzerdefiniertes Layout in Betracht. Apples dokumentierter Vertrag trennt die Angabe der zusammengesetzten Größe über sizeThatFits(proposal:subviews:cache:) von der Positionierung der untergeordneten Elemente über placeSubviews(in:proposal:subviews:cache:). Subview-Proxys bieten Funktionen zum Messen und Platzieren. Apples Dokumentation zu Layout.

Für den hier gezeigten begrenzten Desktop-Editor genügt ein gemeinsames eingepasstes Rechteck: Reservieren Sie horizontalen Platz für die Steuerelemente, passen Sie die Vorschau an beide verfügbaren Abmessungen an und weisen Sie ihre Höhe dem Ansichtsbereich der Steuerelemente zu. So erhalten Sie eine klare Regel für Größenänderungen, während der Inhalt der Steuerelemente weiterhin scrollbar bleibt.

Quellen