Użyj sheet(item:onDismiss:content:) z jednym opcjonalnym wybranym elementem i twórz arkusz na podstawie elementu przekazanego do jego domknięcia. Dzięki temu aplikacja na Maca ma jedną wartość, która określa zarówno to, czy pokazać arkusz, jak i to, co ma on wyświetlać.

Ten samouczek implementuje niewielkie zadanie na komputerze: kliknij lokalny przykładowy element i od razu sprawdź jego szczegóły. Zawiera też sekwencję diagnostyczną dla arkusza, który otwiera się pusty, pokazuje poprzedni wybór albo wyświetla nieaktualny tekst w widoku AppKit.

Dlaczego warto użyć arkusza opartego na elemencie?

SwiftUI udostępnia dwie powiązane metody prezentacji. Przeciążenie z wartością logiczną pokazuje arkusz, gdy powiązanie zmieni się na true. Przeciążenie z elementem przyjmuje opcjonalną wartość identyfikowalną i przekazuje ją do domknięcia zawartości. Wybierz wariant logiczny dla stałego panelu, a wariant z elementem, gdy zawartość panelu zależy od wyboru. Zobacz dokumentację Apple dotyczącą arkuszy opartych na wartości logicznej i arkuszy opartych na elemencie.

Projekt z osobnymi właściwościami wyboru i prezentacji dopuszcza kombinacje takie jak żądanie prezentacji bez wybranego elementu. Połączenie ich w jedną opcjonalną wartość eliminuje taką kombinację z własnego modelu stanu: nil oznacza brak żądanego elementu, a wypełniona wartość oznacza element do sprawdzenia. To wniosek projektowy wynikający z obu interfejsów API, a nie twierdzenie o nieudokumentowanym błędzie harmonogramowania w SwiftUI.

Jeśli istniejąca implementacja przypisuje wybór, a następnie przełącza wartość logiczną, te dwie instrukcje same w sobie nie wyjaśniają, dlaczego pierwsza prezentacja się nie udaje. Zanim przypiszesz objaw problemowi z synchronizacją, sprawdź ścieżkę danych zawartości. Warto ustalić, czy arkusz renderuje przekazane mu dane wejściowe, odczytuje inną właściwość, czy wyświetla osobno przechowywaną kopię.

Zbuduj kompletny przykład dla macOS

Utwórz w Xcode projekt aplikacji macOS z interfejsem SwiftUI. W tym przykładzie ustaw cel wdrożenia na macOS 12 lub nowszy. API arkusza opartego na elemencie jest dostępne od macOS 10.15, natomiast użyta poniżej akcja zamykania z otoczenia wymaga macOS 12. Apple dokumentuje te granice dostępności dla arkuszy opartych na elemencie i wartości zamknięcia z otoczenia.

Zastąp wygenerowaną deklarację aplikacji i widok zawartości tym kompletnym programem. W danym celu pozostaw tylko jeden punkt wejścia aplikacji. Wszystkie poniższe etykiety i opisy to syntetyczne lokalne dane przykładowe.

import SwiftUI

struct SampleItem: Identifiable {
    let id: String
    let title: String
    let detail: String
}

@main
struct FirstSheetDemoApp: App {
    var body: some Scene {
        WindowGroup {
            ItemListView()
        }
    }
}

struct ItemListView: View {
    private let items = [
        SampleItem(
            id: "tile-a",
            title: "Amber Tile",
            detail: "A square sample with an amber finish."
        ),
        SampleItem(
            id: "tile-b",
            title: "Blue Tile",
            detail: "A rectangular sample with a blue finish."
        )
    ]

    @State private var presentedItem: SampleItem? = nil

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text("Sample Items")
                .font(.title2)

            ForEach(items) { item in
                Button("Inspect \(item.title)") {
                    presentedItem = item
                }
            }
        }
        .padding(24)
        .frame(minWidth: 360, minHeight: 200)
        .sheet(item: $presentedItem) { item in
            ItemDetailView(item: item)
        }
    }
}

struct ItemDetailView: View {
    let item: SampleItem
    @Environment(\.dismiss) private var dismiss

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text(item.title)
                .font(.title)

            Text(item.detail)

            Text("Item ID: \(item.id)")
                .font(.caption)

            HStack {
                Spacer()
                Button("Done") {
                    dismiss()
                }
            }
        }
        .padding(24)
        .frame(width: 380, height: 220)
    }
}

Przycisk przypisuje klikniętą wartość do presentedItem. Następnie domknięcie prezentacji przekazuje własny parametr item do ItemDetailView. Zwróć uwagę, że widok szczegółów ma zwykłą właściwość wejściową let item, a nie drugą właściwość stanu przechowującą wybrany element. Apple zaleca zachowanie jednego źródła prawdy i udostępnianie tylko do odczytu wartości przez właściwości, a gdy widok podrzędny potrzebuje uprawnień do zapisu — używanie powiązań. Zobacz State.

Akcja zamknięcia należy do ItemDetailView, gdzie odczytuje otoczenie arkusza. Apple wyraźnie ostrzega, że odczytanie jej w widoku prezentującym, a następnie wywołanie tej akcji z zawartości arkusza spowoduje użycie otoczenia widoku prezentującego. Umieść deklarację obok zawartości samego arkusza, jak pokazano. Zobacz dismiss.

Program został sprawdzony pod kątem spójności danych wejściowych i odwołań; nie został tu skompilowany ani uruchomiony, ponieważ w tym środowisku nie ma łańcucha narzędzi Swift. Wykonaj poniższe kontrole w Xcode na docelowej wersji macOS.

Sprawdź pierwszą prezentację, wykonując określoną sekwencję kliknięć

Każde sprawdzenie pierwszej prezentacji rozpocznij od świeżego uruchomienia. Testowanie dopiero po otwarciu innego elementu może ukryć pierwotny objaw. Poniższe oczekiwania wynikają z jawnych etykiet i przypisań danych w przykładzie; to kryteria akceptacji, a nie zgłoszone wyniki testów.

Czynność Oczekiwana zawartość arkusza Co sprawdza
Uruchom aplikację i jako pierwszy kliknij Inspect Blue Tile Blue Tile, opis prostokątnego kształtu i tile-b Pierwsza prezentacja używa klikniętego elementu
Kliknij Done, a następnie sprawdź Amber Tile Amber Tile, opis kwadratowego kształtu i tile-a Kolejny wybór trafia do zawartości
Zamknij arkusz i ponownie sprawdź Amber Tile Te same szczegóły bursztynowego kafelka Ponowne otwarcie elementu pozostaje spójne
Uruchom ponownie aplikację i jako pierwszy sprawdź Amber Tile Szczegóły bursztynowego kafelka od razu Poprawność nie zależy od tego, który przykładowy element otwierasz jako pierwszy

Wyświetlanie zarówno tytułu, jak i identyfikatora czyni to sprawdzenie bardziej użytecznym niż sprawdzanie samego tytułu. Jeśli rzeczywisty interfejs dopuszcza powtarzające się nazwy, podczas tworzenia aplikacji użyj dwóch syntetycznych elementów o tym samym tytule, ale różnych identyfikatorach i opisach. Ujawni to przypadkowe wybieranie na podstawie tekstu wyświetlanego na ekranie.

Diagnozuj nieaktualną zawartość, śledząc drogę od przycisku do widoku końcowego

Wykonuj kolejno poniższe kontrole. Każdy krok zawęża miejsce rozbieżności, dzięki czemu możesz zmienić odpowiednią warstwę bez dodawania niezwiązanych obejść prezentacji.

  1. Sprawdź dane wejściowe akcji. Ustaw punkt przerwania w akcji klikniętego przycisku i sprawdź item.id. Upewnij się, że akcja otrzymuje oczekiwany rekord.
  2. Sprawdź dane wejściowe prezentacji. Sprawdź parametr item wewnątrz domknięcia arkusza. Przekaż ten parametr do widoku szczegółów zamiast ponownie odczytywać osobny wybór.
  3. Sprawdź właściwość wyświetlaną na końcu. Upewnij się, że tytuł i opis pochodzą z danych wejściowych widoku szczegółów. Poszukaj buforowanej właściwości lub lokalnego szkicu zawierającego wcześniejszą wartość.
  4. Sprawdź ewentualny most AppKit. Jeśli zwykły tekst SwiftUI jest poprawny, ale tekst w opakowanym widoku jest nieaktualny, sprawdź metodę aktualizacji representable.

Zachowaj stabilną tożsamość

API oparte na elemencie wymaga zgodności z Identifiable. Swift definiuje ten protokół wokół stabilnej tożsamości, a czas jej trwania i zakres pozostawia typowi implementującemu protokół oraz jego odbiorcy. W przykładzie użyto zapisanych identyfikatorów tekstowych odróżniających te dwa rekordy. Zobacz Identifiable.

W przypadku rzeczywistej listy wybierz identyfikator, który nadal wskazuje ten sam rekord przy aktualizacjach obsługiwanych przez interfejs. Unikaj wyprowadzania tożsamości z tytułu, który może się zmienić lub powtarzać dla wielu elementów. Unikaj też obliczanego id, który za każdym odczytem tworzy nowe UUID; nie zapewniałoby to stabilnej tożsamości.

Oddziel dane wejściowe do wyświetlania od edytowalnego szkicu

Widok inspektora może wyświetlać bezpośrednio item.title. Edytor może celowo tworzyć lokalny szkic, ale szkic wprowadza kolejną wartość, której czas życia trzeba określić. Zdecyduj, kiedy jest tworzony, czy późniejsze zmiany modelu mają go zastępować oraz co ma się stać, gdy użytkownik zakończy edycję lub ją anuluje.

Apple wyjaśnia, że SwiftUI zachowuje miejsce na stan dla tożsamości widoku i inicjalizuje je na początku czasu życia tej tożsamości. Ponowne utworzenie wartości widoku nie powoduje automatycznie utworzenia świeżego stanu. Dlatego inicjalizowanie lokalnego stanu na podstawie danych wejściowych nie jest ogólnym sposobem synchronizowania go z późniejszymi danymi wejściowymi. Zobacz Demystify SwiftUI.

W tym zadaniu tylko do odczytu zachowaj zwykłą właściwość wejściową. Jeśli rozszerzysz je o edycję, wybierz jawny projekt szkicu i zatwierdzania albo powiązanie z właściwą wartością modelu. Unikaj zmieniania tożsamości widoku wyłącznie po to, by odświeżyć tekst: zmiana tożsamości zastępuje powiązany stan, co może odrzucić niedokończony szkic. Wynika to z udokumentowanego przez Apple modelu czasu życia stanu.

Co się zmienia, gdy arkusz zawiera widok tekstowy AppKit?

Arkusz zawierający NSTextView nadal wymaga tej samej ścieżki danych wybranego elementu. Potrzebuje też poprawnie zaimplementowanego mostu. Protokół Apple NSViewRepresentable rozdziela początkowe tworzenie w makeNSView(context:) od późniejszej konfiguracji w updateNSView(_:context:). Wrapper, który przypisuje tekst tylko podczas tworzenia, nie ma implementacji stosującej późniejsze dane wejściowe tekstu. Zobacz NSViewRepresentable.

W wrapperze tylko do odczytu zainicjalizuj widok natywny przekazanym tekstem i zadbaj, by jego metoda aktualizacji w razie potrzeby doprowadzała wyświetlany tekst do zgodności z bieżącymi danymi wejściowymi. Apple dokumentuje, że SwiftUI wywołuje metodę aktualizacji w przypadku zmian wpływających na odpowiadający jej widok AppKit. Zobacz updateNSView(_:context:).

Edytowalny wrapper wymaga dodatkowej zasady: nie zastępuj bezwarunkowo jego tekstu przy każdej aktualizacji, nadpisując trwające edycje. Użyj koordynatora, aby przekazywać zmiany z natywnego delegata lub akcji celu z powrotem do SwiftUI, zgodnie z opisem Apple w dokumentacji representable. Pozwól SwiftUI kontrolować ramkę i granice reprezentowanego widoku; Apple ostrzega przed bezpośrednim ustawianiem tych właściwości układu.

TextKit obsługuje przechowywanie tekstu, układ i renderowanie. Apple dokumentuje zarówno nowoczesną ścieżkę NSTextLayoutManager, jak i starszą ścieżkę NSLayoutManager udostępniane przez NSTextView. Te narzędzia nie dostarczają wybranego rekordu arkusza. Zanim zaczniesz badać układ tekstu, upewnij się, że most otrzymuje tekst właściwego elementu, a dostępność sprawdź osobno przed wprowadzeniem dodatkowych API TextKit. Zobacz TextKit.

Wybierz model prezentacji odpowiedni do zadania

Użyj przeciążenia z wartością logiczną, gdy zawartość jest stała i zmienia się tylko widoczność. Do zadania inspektora użyj przeciążenia z elementem. Apple dokumentuje, że zmiana przekazanego elementu może zamknąć istniejący arkusz i zastąpić go nowym, więc nie traktuj podmiany elementu jako gwarancji nieprzerwanej edycji w ramach tej samej prezentacji. Zobacz sheet(item:onDismiss:content:).

Aby poprawnie wyświetlić element już przy pierwszej prezentacji, zadbaj o łatwą do prześledzenia ścieżkę wybranego rekordu: jedno przypisanie w akcji przycisku, jeden parametr elementu w domknięciu arkusza i jedno wejście widoku szczegółów. Sprawdź oba przykładowe elementy po świeżym uruchomieniu. Jeśli wynik nadal się różni, sekwencja diagnostyczna pozwoli ustalić, czy rozbieżność pojawia się przy wyborze, wynika z zachowanego stanu, czy ze ścieżki aktualizacji widoku natywnego.

Źródła