09 maj 2025·6 min czytania

Wzorce NavigationStack w SwiftUI dla przewidywalnych wieloetapowych przepływów

Wzorce NavigationStack w SwiftUI dla wieloetapowych przepływów — z jasnym routingiem, bezpiecznym zachowaniem przy powrocie i praktycznymi przykładami dla onboardingów i kreatorów zatwierdzania.

Wzorce NavigationStack w SwiftUI dla przewidywalnych wieloetapowych przepływów

Co może pójść źle w wieloetapowych przepływach

Wieloetapowy przepływ to każda sekwencja, w której krok 1 musi się zdarzyć zanim krok 2 nabierze sensu. Typowe przykłady to onboarding, wniosek o zatwierdzenie (przegląd, potwierdź, wyślij) oraz wprowadzanie danych w stylu kreatora, gdzie ktoś buduje wersję roboczą przez kilka ekranów.

Te przepływy wydają się proste tylko wtedy, gdy przycisk Wstecz zachowuje się tak, jak użytkownicy tego oczekują. Jeśli Wstecz zabiera ich w zaskakujące miejsce, przestają ufać aplikacji. Objawia się to błędnymi zgłoszeniami, porzuceniem onboardingu i zgłoszeniami do supportu typu „Nie mogę wrócić do ekranu, na którym byłem”.

Bałagan w nawigacji zwykle wygląda tak:

  • Aplikacja skacze na niewłaściwy ekran albo opuszcza przepływ za wcześnie.
  • Ten sam ekran pojawia się dwukrotnie, bo został wypchnięty dwa razy.
  • Krok resetuje się po Wstecz i użytkownik traci draft.
  • Użytkownik może przejść do kroku 3 bez ukończenia kroku 1, co tworzy nieprawidłowy stan.
  • Po deep linku lub restarcie aplikacji widać właściwy ekran, ale z nieprawidłowymi danymi.

Przydatny model mentalny: wieloetapowy przepływ to tak naprawdę dwie rzeczy poruszające się razem.

Po pierwsze: stos ekranów (to, przez co użytkownik może cofać się). Po drugie: współdzielony stan przepływu (dane robocze i postęp, które nie powinny znikać tylko dlatego, że ekran zniknął).

Wiele konfiguracji NavigationStack rozpada się, kiedy stos ekranów i stan przepływu zaczynają dryfować osobno. Na przykład onboarding może wypchnąć „Utwórz profil” dwukrotnie (duplikat trasy), podczas gdy roboczy profil żyje wewnątrz widoku i jest odtwarzany przy rerenderze. Użytkownik naciska Wstecz, widzi inną wersję formularza i uznaje aplikację za zawodną.

Przewidywalne zachowanie zaczyna się od nazwaniu przepływu, zdefiniowania, co ma robić Wstecz na każdym kroku, i dania stanowi przepływu jednego, jasnego miejsca.

Podstawy NavigationStack, które naprawdę potrzebujesz

Dla wieloetapowych przepływów używaj NavigationStack zamiast starszego NavigationView. NavigationView może zachowywać się inaczej w różnych wersjach i trudniej go rozsądnie kontrolować przy push/pop/restore. NavigationStack to nowoczesne API, które traktuje nawigację jak prawdziwy stos.

NavigationStack przechowuje historię miejsc, które odwiedził użytkownik. Każde wypchnięcie dodaje destynację do stosu. Każde cofnięcie usuwa jedną destynację. Ta prosta zasada sprawia, że przepływ wydaje się stabilny: UI powinno odzwierciedlać klarowną sekwencję kroków.

Co tak naprawdę trzyma stos

SwiftUI nie przechowuje instancji twoich widoków. Przechowuje dane, których użyłeś do nawigacji (wartość trasy) i wykorzystuje je, żeby odtworzyć widok docelowy w razie potrzeby. Ma to kilka praktycznych konsekwencji:

  • Nie polegaj na tym, że widok pozostanie przy życiu, by przechować ważne dane.
  • Jeśli ekran potrzebuje stanu, trzymaj go w modelu (np. ObservableObject) poza wypychanym widokiem.
  • Jeśli wypchniesz ten sam ekran dwa razy z różnymi danymi, SwiftUI potraktuje je jako różne wpisy w stosie.

NavigationPath to narzędzie, do którego sięgasz, gdy twój przepływ to nie tylko jeden czy dwa stałe pushy. Traktuj go jak edytowalną listę wartości „dokąd idziemy”. Możesz dopisać trasę, by iść dalej, usunąć ostatnią, by cofnąć się, albo zastąpić całą ścieżkę, by przeskoczyć do późniejszego kroku.

Pasuje dobrze, gdy potrzebujesz kroków w stylu kreatora, zresetować przepływ po zakończeniu albo chcesz przywrócić częściowy przepływ ze zapisanego stanu.

Przewidywalne jest lepsze niż sprytne. Mniej ukrytych reguł (automatyczne skoki, niejawne popy, side effecty sterowane widokiem) oznacza mniej dziwnych błędów stosu przycisku Wstecz w przyszłości.

Zamodeluj przepływ małym enumem tras

Przewidywalna nawigacja zaczyna się od jednej decyzji: trzymaj routing w jednym miejscu i spraw, by każdy ekran w przepływie był małą, jasną wartością.

Stwórz jedno źródło prawdy, na przykład FlowRouter (ObservableObject), które posiada NavigationPath. To utrzymuje wszystkie push/pop spójne, zamiast rozsypywać nawigację po widokach.

Prosta struktura routera

Użyj enuma do reprezentacji kroków. Dodawaj associated values tylko dla lekkich identyfikatorów (np. ID), nie całych modeli.

enum Step: Hashable {
    case welcome
    case profile
    case verifyCode(phoneID: UUID)
    case review(applicationID: UUID)
    case done
}

final class FlowRouter: ObservableObject {
    @Published var path = NavigationPath()

    func go(_ step: Step) { path.append(step) }
    func back() { if !path.isEmpty { path.removeLast() } }
    func reset() { path = NavigationPath() }
}

Trzymaj stan przepływu oddzielnie od stanu nawigacji

Traktuj nawigację jako „gdzie jest użytkownik”, a stan przepływu jako „co wpisał do tej pory”. Umieść dane przepływu w własnym store (np. OnboardingState z imieniem, emailem, przesłanymi dokumentami) i utrzymuj je stabilne, gdy ekrany przychodzą i odchodzą.

Prosta zasada:

  • FlowRouter.path zawiera tylko wartości Step.
  • OnboardingState zawiera dane wpisane przez użytkownika i draft.
  • Krok niesie ID do wyszukania danych, nie same dane.

To unika kruchego hashowania, ogromnych pathów i niespodziewanych resetów przy odtwarzaniu widoków przez SwiftUI.

Krok po kroku: buduj kreatora z NavigationPath

Dla ekranów w stylu kreatora najprostsze podejście to kontrolować stos samodzielnie. Dąż do jednego źródła prawdy „gdzie jestem w przepływie?” i jednego sposobu przechodzenia do przodu lub do tyłu.

Zacznij od NavigationStack(path:) powiązanego z NavigationPath. Każdy wypchnięty ekran jest reprezentowany przez wartość (często przypadek enuma), i rejestrujesz destynacje raz.

import SwiftUI

enum WizardRoute: Hashable {
    case profile
    case verifyEmail
    case permissions
    case review
}

struct OnboardingWizard: View {
    @State private var path = NavigationPath()
    @State private var currentIndex = 0

    private let steps: [WizardRoute] = [.profile, .verifyEmail, .permissions, .review]

    var body: some View {
        NavigationStack(path: $path) {
            StartScreen {
                goToStep(0) // push first step
            }
            .navigationDestination(for: WizardRoute.self) { route in
                switch route {
                case .profile:
                    ProfileStep(onNext: { goToStep(1) })
                case .verifyEmail:
                    VerifyEmailStep(onNext: { goToStep(2) })
                case .permissions:
                    PermissionsStep(onNext: { goToStep(3) })
                case .review:
                    ReviewStep(onEditProfile: { popToStep(0) })
                }
            }
        }
    }

    private func goToStep(_ index: Int) {
        currentIndex = index
        path.append(steps[index])
    }

    private func popToStep(_ index: Int) {
        let toRemove = max(0, currentIndex - index)
        if toRemove > 0 { path.removeLast(toRemove) }
        currentIndex = index
    }
}

Żeby utrzymać przewidywalność Wstecz, trzymaj się kilku przyzwyczajeń. Dopisuj dokładnie jedną trasę, by iść dalej, „Dalej” powinno być liniowe (tylko push następnego kroku), a gdy musisz cofnąć się skokiem (np. „Edytuj profil” z Podsumowania), przytnij stos do znanego indeksu.

To unika przypadkowych duplikatów ekranów i sprawia, że Wstecz odpowiada oczekiwaniu użytkownika: jedno tapnięcie to jeden krok.

Utrzymuj dane stabilne, gdy ekrany przychodzą i odchodzą

Build full-stack without glue code
Generuj gotowe do produkcji backendy, aplikacje webowe i mobilne z tego samego projektu przepływu.
Wygeneruj aplikację

Wieloetapowy przepływ wydaje się zawodny, gdy każdy ekran trzyma własny stan. Wpisujesz imię, idziesz dalej, wracasz i pole jest puste, bo widok został odtworzony.

Naprawa jest prosta: traktuj przepływ jako jeden obiekt draft i pozwól każdemu krokowi go edytować.

W SwiftUI zwykle oznacza to współdzielone ObservableObject tworzone raz na wejściu do przepływu i przekazywane do każdego kroku. Nie trzymaj wartości roboczych w @State widoku, jeśli naprawdę nie należą tylko do tego ekranu.

final class OnboardingDraft: ObservableObject {
    @Published var fullName = ""
    @Published var email = ""
    @Published var wantsNotifications = false

    var canGoNextFromProfile: Bool {
        !fullName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
        && email.contains("@")
    }
}

Utwórz go przy wejściu, a potem udostępnij przez @StateObject i @EnvironmentObject (lub przekaż explicite). Teraz stos może się zmieniać bez utraty danych.

Zdecyduj, co przetrwa cofanie się

Nie wszystko musi przetrwać wiecznie. Ustal zasady wcześniej, żeby przepływ był spójny.

Zachowaj dane użytkownika (pola tekstowe, przełączniki, wybory) chyba że użytkownik jawnie zresetuje. Resetuj UI specyficzny dla kroku (spinnery ładowania, tymczasowe alerty, krótkie animacje). Czyść pola wrażliwe (kody jednorazowe) po opuszczeniu kroku. Jeśli wybór wpływa na kolejne kroki, czyść tylko zależne pola.

Walidacja pasuje tu naturalnie. Zamiast pozwalać użytkownikowi iść dalej i potem pokazywać błąd na następnym ekranie, zatrzymaj go na obecnym kroku, aż będzie prawidłowy. Wyłączanie przycisku na podstawie właściwości obliczonej jak canGoNextFromProfile często wystarcza.

Zapisuj checkpointy, ale bez przesady

Niektóre drafty mogą żyć tylko w pamięci. Inne powinny przetrwać restarty aplikacji lub awarie. Praktyczne domyślne podejście:

  • Trzymaj dane w pamięci, gdy użytkownik aktywnie przechodzi przez kroki.
  • Persistuj lokalnie w jasnych kamieniach milowych (konto utworzone, zgłoszenie wysłane, rozpoczęta płatność).
  • Persistuj wcześniej, jeśli przepływ jest długi lub wprowadzanie danych zajmuje ponad minutę.

Dzięki temu ekrany mogą swobodnie przychodzić i odchodzić, a postęp użytkownika nadal będzie odczuwalny i szanujący jego czas.

Deep linki i przywracanie częściowo zakończonego przepływu

Deep linki są ważne, bo prawdziwe przepływy rzadko zaczynają się na kroku 1. Ktoś kliknie email, powiadomienie push lub link i oczekuje trafienia na właściwy ekran, np. krok 3 onboardingu lub końcowy ekran zatwierdzenia.

Z NavigationStack traktuj deep link jako instrukcję do zbudowania poprawnej path, a nie rozkaz teleportacji do jednego widoku. Zacznij od początku przepływu i dopisuj tylko te kroki, które są prawdziwe dla tego użytkownika i tej sesji.

Dobrym wzorcem jest: sparsuj zewnętrzne ID, załaduj minimalne dane, których potrzebujesz, a potem skonwertuj je na sekwencję tras.

enum Route: Hashable {
    case start
    case profile
    case verifyEmail
    case approve(requestID: String)
}

func pathForDeepLink(requestID: String, hasProfile: Bool, emailVerified: Bool) -> [Route] {
    var routes: [Route] = [.start]
    if !hasProfile { routes.append(.profile) }
    if !emailVerified { routes.append(.verifyEmail) }
    routes.append(.approve(requestID: requestID))
    return routes
}

Te sprawdzenia są twoimi barierami ochronnymi. Jeśli brakuje warunków wstępnych, nie zrzucaj użytkownika na krok 3 z błędem i bez drogi naprzód. Wyślij go do pierwszego brakującego kroku i upewnij się, że stos Wstecz nadal opowiada spójną historię.

Przywracanie częściowego przepływu

Aby przywrócić po ponownym uruchomieniu, zapisz dwie rzeczy: ostatni znany stan tras oraz dane robocze wpisane przez użytkownika. Potem zdecyduj, jak wznowić bez zaskakiwania ludzi.

Jeśli draft jest świeży (minuty lub godziny), zaoferuj wyraźną opcję „Wznów”. Jeśli jest stary, zacznij od początku, ale użyj draftu do wstępnego wypełnienia pól. Jeśli wymagania się zmieniły, odbuduj ścieżkę używając tych samych reguł warunkowych.

Push vs modal: spraw, by opuszczenie przepływu było proste

Test Back behavior early
Szybko prototypuj cały przepływ, a potem dopracuj ekrany bez łamania podróży użytkownika.
Prototypuj teraz

Przepływ wydaje się przewidywalny, gdy jest jeden główny sposób przejścia do przodu: push ekranów na jednym stosie. Używaj sheet i fullScreenCover do zadań pobocznych, nie do głównej ścieżki.

Push (NavigationStack) pasuje, gdy użytkownik oczekuje, że Wstecz odtwarza kroki. Modale (sheet lub fullScreenCover) pasują, gdy użytkownik wykonuje zadanie poboczne, dokonuje szybkiego wyboru lub potwierdza ryzykowną akcję.

Prosty zestaw reguł zapobiega większości problemów:

  • Push dla głównej ścieżki (Krok 1, Krok 2, Krok 3).
  • Użyj sheet dla małych, opcjonalnych zadań (wybór daty, kraju, skan dokumentu).
  • Użyj fullScreenCover dla „odrębnych światów” (logowanie, przechwytywanie kamery, długi dokument prawny).
  • Użyj modala dla potwierdzeń (anuluj przepływ, usuń draft, wyślij do zatwierdzenia).

Częsty błąd to umieszczanie głównych ekranów w sheetach. Jeśli Krok 2 jest sheetem, użytkownik może go odsłonić gestem, stracić kontekst i skończyć ze stosem mówiącym, że jest na Kroku 1, podczas gdy dane mówią, że skończył Krok 2.

Potwierdzenia to odwrotność: wypychanie „Czy na pewno?” do kreatora zaśmieca stos i może tworzyć pętle (Krok 3 -> Potwierdź -> Wstecz -> Krok 3 -> Wstecz -> Potwierdź).

Jak wszystko zamknąć czysto po „Gotowe”

Najpierw zdecyduj, co znaczy „Gotowe”: powrót do ekranu głównego, powrót do listy czy pokazanie ekranu sukcesu.

Jeśli przepływ był wypchnięty, zresetuj NavigationPath do pustego, żeby cofnąć się do początku. Jeśli przepływ był zaprezentowany modalnie, wywołaj dismiss() z environmentu. Jeśli masz oba (modal zawierający NavigationStack), zamknij modal, a nie każdy wypchnięty ekran. Po pomyślnym wysłaniu też wyczyść draft, żeby ponowne otwarcie zaczynało świeżo.

Zachowanie przycisku Wstecz i chwile „Jesteś pewien?”

Keep navigation and state aligned
Projektuj ekrany, dane i logikę w jednym miejscu, żeby przycisk Wstecz zawsze zachowywał się jak użytkownik oczekuje.
Zacznij budować

Dla większości przepływów najlepszym posunięciem jest nic nie robić: pozwól systemowemu przyciskowi Wstecz (i gestowi) działać. Pasuje to do oczekiwań użytkownika i unika błędów, gdzie UI mówi jedno, a stan nawigacji drugie.

Przechwytywanie warto rozważyć tylko wtedy, gdy cofnięcie spowoduje realne szkody, jak utrata długiego niezapisanego formularza czy porzucenie nieodwracalnej akcji. Jeśli użytkownik może bezpiecznie wrócić i kontynuować, nie dodawaj przeszkód.

Praktyczne podejście: zachowaj systemową nawigację, ale dodaj potwierdzenie tylko wtedy, gdy ekran jest „brudny” (edytowany). To oznacza zapewnienie własnej akcji back i zapytanie raz, z jasnym wyjściem.

@Environment(\.dismiss) private var dismiss
@State private var showLeaveConfirm = false
let hasUnsavedChanges: Bool

var body: some View {
  Form { /* fields */ }
    .navigationBarBackButtonHidden(hasUnsavedChanges)
    .toolbar {
      if hasUnsavedChanges {
        ToolbarItem(placement: .navigationBarLeading) {
          Button("Back") { showLeaveConfirm = true }
        }
      }
    }
    .confirmationDialog("Discard changes?", isPresented: $showLeaveConfirm) {
      Button("Discard", role: .destructive) { dismiss() }
      Button("Keep Editing", role: .cancel) {}
    }
}

Upewnij się, że to nie zamienia się w pułapkę:

  • Pytaj tylko wtedy, gdy możesz wyjaśnić konsekwencję jednym krótkim zdaniem.
  • Daj opcję bezpieczną (Anuluj, Kontynuuj edycję) plus jasne wyjście (Usuń, Opuść).
  • Nie ukrywaj przycisków Wstecz, chyba że zastępujesz je oczywistym Back lub Close.
  • Wol preferuj potwierdzenie akcji nieodwracalnej (np. „Zatwierdź”) zamiast blokowania nawigacji wszędzie.

Jeśli często walczysz z gestem Wstecz, to zwykle znak, że przepływ potrzebuje autosave, zapisu draftu lub mniejszych kroków.

Typowe błędy powodujące dziwne stosy Wstecz

Większość „dlaczego wróciło tam?” błędów to nie przypadek SwiftUI. Zwykle pochodzą z wzorców, które czynią stan nawigacji niestabilnym. Dla przewidywalnego zachowania traktuj stos Wstecz jak dane aplikacji: stabilne, testowalne i posiadane przez jedno miejsce.

Przypadkowe dodatkowe stosy

Częstą pułapką jest posiadanie więcej niż jednego NavigationStack, nie zdając sobie z tego sprawy. Na przykład każda zakładka ma własny root stack, a potem widok potomny dodaje kolejny stack wewnątrz przepływu. Efekt to mylące zachowanie Wstecz, brak pasków nawigacji lub ekrany, które nie wypychają się tak, jak oczekujesz.

Inny częsty problem to ponowne tworzenie NavigationPath zbyt często. Jeśli path jest tworzony wewnątrz widoku, który rerenderuje się, może się resetować przy zmianach stanu i przenosić użytkownika z powrotem do kroku 1 po wpisaniu pola.

Błędy stojące za większością dziwnych stosów są proste:

  • Zagnieżdżanie NavigationStack wewnątrz innego stacka (często w zakładkach lub treści sheetu)
  • Re-inicjalizowanie NavigationPath() podczas aktualizacji widoków zamiast trzymać go w długowiecznym stanie
  • Umieszczanie niestabilnych wartości w trasie (np. modelu, który się zmienia), co łamie Hashable i powoduje niepasujące destynacje
  • Rozrzucanie decyzji nawigacyjnych po handlerach przycisków, aż nikt nie potrafi wytłumaczyć, co znaczy „dalej”
  • Sterowanie przepływem z wielu źródeł jednocześnie (np. view model i view mutują path)

Jeśli musisz przekazywać dane między krokami, preferuj stabilne identyfikatory w trasie (ID, enumy kroków) i trzymaj właściwe dane formularza we współdzielonym stanie.

Przykład: jeśli twoja trasa to .profile(User) a User zmienia się podczas wpisywania, SwiftUI może potraktować to jako inną trasę i przestawić stos. Zrób trasę .profile i przechowuj draft w współdzielonym stanie.

Szybka lista kontrolna dla przewidywalnej nawigacji

Own your app’s codebase
Zachowaj kontrolę, eksportując prawdziwy kod źródłowy, gdy potrzebujesz self-hostingu lub customizacji.
Eksportuj kod

Gdy przepływ nie czuje się dobrze, zwykle dlatego, że stos Wstecz nie opowiada tej samej historii, co użytkownik. Zanim dopracujesz UI, przejrzyj reguły nawigacyjne.

Testuj na prawdziwym urządzeniu, nie tylko w preview, i sprawdź wolne oraz szybkie tapnięcia. Szybkie tapnięcia często ujawniają duplikaty pushów i brakujące stany.

  • Cofnij się krok po kroku z ostatniego ekranu do pierwszego. Potwierdź, że każdy ekran pokazuje te same dane, które wcześniej wprowadził użytkownik.
  • Wywołaj Anuluj z każdego kroku (w tym z pierwszego i ostatniego). Potwierdź, że zawsze wraca do sensownego miejsca, a nie do losowego wcześniejszego ekranu.
  • Wymuś zamknięcie aplikacji w połowie przepływu i uruchom ponownie. Upewnij się, że możesz bezpiecznie wznowić, albo przywracając path, albo restartując na znanym kroku z zapisanymi danymi.
  • Otwórz przepływ przez deep link lub skrót aplikacji. Zweryfikuj, że krok docelowy jest ważny; jeśli brakuje wymaganych danych, przekieruj do najwcześniejszego kroku, który może je zebrać.
  • Zakończ z Gotowe i potwierdź, że przepływ został usunięty czysto. Użytkownik nie powinien móc nacisnąć Wstecz i ponownie wejść do ukończonego kreatora.

Prosty sposób testu: wyobraź sobie onboarding z trzema ekranami (Profil, Uprawnienia, Potwierdzenie). Wpisz imię, idź dalej, wróć, edytuj, a potem przejdź do Potwierdzenia przez deep link. Jeśli Potwierdzenie pokazuje stare imię, albo Wstecz zabiera do zduplikowanego ekranu Profil, twoje update'y ścieżki nie są spójne.

Jeśli przejdziesz checklistę bez niespodzianek, przepływ będzie czuł się spokojny i przewidywalny, nawet gdy użytkownicy odejdą i wrócą później.

Realistyczny przykład i następne kroki

Wyobraź sobie przepływ zatwierdzenia kosztów przez menedżera. Ma cztery kroki: Review, Edit, Confirm i Receipt. Użytkownik oczekuje jednej rzeczy: Wstecz zawsze wraca do poprzedniego kroku, a nie do jakiegoś losowego ekranu, który odwiedził wcześniej.

Prosty enum tras trzyma to w ryzach. Twój NavigationPath powinien przechowywać tylko trasę i małe identyfikatory potrzebne do ponownego załadowania stanu, takie jak expenseID i mode (review vs edit). Unikaj wypychania dużych, mutowalnych modeli do path, bo to utrudnia restore i deep linki.

Trzymaj roboczy draft w jednym źródle prawdy poza widokami, np. @StateObject modelu przepływu (lub store). Każdy krok czyta i zapisuje ten model, więc ekrany mogą się pojawiać i znikać bez utraty wpisów.

Przynajmniej śledzisz trzy rzeczy:

  • Trasy (np.: review(expenseID), edit(expenseID), confirm(expenseID), receipt(expenseID))
  • Dane (obiekt draft z pozycjami i notatkami oraz status jak pending, approved, rejected)
  • Lokalizację (draft w modelu przepływu, zapis kanoniczny na serwerze oraz mały token restore lokalnie: expenseID + ostatni krok)

Edge cases to miejsca, gdzie przepływy zdobywają zaufanie albo je tracą. Jeśli menedżer odrzuca w Confirm, zdecyduj, czy Wstecz wraca do Edit (żeby poprawić) czy wychodzi z przepływu. Jeśli wróci później, przywróć ostatni krok z zapisanego tokenu i załaduj draft. Jeśli zmieni urządzenie, traktuj serwer jako źródło prawdy: odbuduj ścieżkę ze statusu serwera i wyślij na właściwy krok.

Następne kroki: udokumentuj enum tras (co znaczy każdy przypadek i kiedy jest używany), dodaj kilka podstawowych testów dla budowania path i zachowania przy restore, i trzymaj jedną zasadę: widoki nie powinny mieć własności decyzji nawigacyjnych.

Jeśli budujesz ten sam rodzaj wieloetapowych przepływów bez pisania wszystkiego od zera, platformy takie jak AppMaster (appmaster.io) stosują tę samą separację: trzymaj nawigację kroków i dane biznesowe osobno, żeby ekrany mogły się zmieniać bez łamania postępu użytkownika.

FAQ

Jak utrzymać przewidywalne działanie przycisku Wstecz w wieloetapowym przepływie SwiftUI?

Użyj NavigationStack z jedną kontrolowaną przez ciebie NavigationPath. Dodawaj dokładnie jedną trasę przy każdej akcji „Dalej” i usuwaj dokładnie jedną przy akcji Wstecz. Gdy potrzebujesz skoku (np. „Edytuj profil” z ekranu podsumowania), przytnij ścieżkę do znanego kroku zamiast dopychać kolejne ekrany.

Dlaczego moje dane formularza znikają, gdy wracam do poprzedniego ekranu?

Ponieważ SwiftUI odtwarza widoki na podstawie wartości trasy, a nie zachowuje instancji widoku. Jeśli dane formularza są w @State widoku, mogą się zresetować przy jego ponownym utworzeniu. Przenieś robocze dane do współdzielonego modelu (np. ObservableObject) żyjącego poza wypychanymi widokami.

Dlaczego widzę ten sam ekran dwa razy w stosie?

Zwykle dzieje się tak, gdy tę samą trasę dodaje się więcej niż raz (np. z powodu szybkich tapnięć lub kilku ścieżek kodu wywołujących nawigację). Wyłącz przycisk Dalej podczas nawigacji lub podczas walidacji/ładowania i centralizuj mutacje nawigacji, żeby tylko jedno wywołanie append miało miejsce na krok.

Co powinienem przechowywać w enumie tras, a czego trzymać poza nim?

Trzymaj w trasie małe, stabilne wartości, np. przypadek enum plus lekkie ID. Przechowuj mutowalne dane (robocze) w osobnym obiekcie współdzielonym i odwołuj się do niego przez ID. Wpychanie dużych, zmieniających się modeli do path może złamać wymagania Hashable i powodować niepasujące destynacje.

Jak oddzielić stan nawigacji od stanu przepływu w czysty sposób?

Nawigacja to „gdzie jest użytkownik”, a stan przepływu to „co wpisał”. Trzymaj path w routerze (lub jednym stanie najwyższego poziomu), a draft w oddzielnym ObservableObject. Każdy ekran edytuje draft; router tylko zmienia kroki.

Jaki jest najbezpieczniejszy sposób obsługi deep linku kierującego do kroku 3 kreatora?

Traktuj deep link jako instrukcję zbudowania poprawnej sekwencji kroków, a nie teleportacji. Zbuduj path, dopisując wymagane kroki poprzedzające (na podstawie tego, co użytkownik już ma), a dopiero potem dodaj krok docelowy. Dzięki temu stos Wstecz będzie spójny i unikniesz nieprawidłowych stanów.

Jak przywrócić częściowo zakończony przepływ po restarcie aplikacji?

Zapisz dwie rzeczy: ostatni istotny krok (lub identyfikator kroku) oraz dane robocze. Po ponownym uruchomieniu odbuduj ścieżkę używając tych samych sprawdzeń warunków wstępnych co dla deep linków, a potem załaduj draft. Jeśli draft jest przestarzały, często mniej zaskakujące jest rozpoczęcie od początku z wstępnie uzupełnionymi polami niż rzucenie użytkownika w połowie kreatora.

Kiedy powinienem użyć push, a kiedy modal w wieloetapowym przepływie?

Użyj push (NavigationStack) dla głównej, krokowej ścieżki, żeby przycisk Wstecz naturalnie odtwarzał przebieg. Używaj sheet dla opcjonalnych zadań pobocznych, a fullScreenCover dla odrębnych doświadczeń jak logowanie czy przechwytywanie kamery. Unikaj umieszczania kluczowych kroków w modalach — gesty dismiss mogą rozsynchronizować UI i stan przepływu.

Czy powinienem nadpisywać gest Wstecz, żeby pokazać dialog „Jesteś pewien?”?

Nie przechwytuj Wstecz na siłę; pozwól na działanie systemowego przycisku i gestu. Dodawaj potwierdzenie tylko wtedy, gdy opuszczenie spowoduje realną stratę (długi, niesaved formularz lub nieodwracalna akcja), i tylko gdy ekran jest rzeczywiście „brudny”. Preferuj autosave albo zapis draftu, gdy często potrzebujesz dialogów potwierdzających.

Jakie są najczęstsze błędy powodujące dziwne zachowanie stosu Wstecz?

Najczęstsze przyczyny to: zagnieżdżenie wielu NavigationStack, ponowne inicjalizowanie NavigationPath przy aktualizacjach widoku oraz kilku właścicieli mutujących path. Trzymaj jeden stos na przepływ, trzymaj path w długowiecznym stanie (@StateObject lub centralny router) i scentralizuj logikę push/pop w jednym miejscu.

Łatwy do uruchomienia
Stworzyć coś niesamowitego

Eksperymentuj z AppMaster z darmowym planem.
Kiedy będziesz gotowy, możesz wybrać odpowiednią subskrypcję.

Rozpocznij