09 मई 2025·8 मिनट पढ़ने में

पूर्वानुमेय बहु-चरण फ्लो के लिए SwiftUI NavigationStack पैटर्न

SwiftUI NavigationStack के पैटर्न बहु-चरण फ्लो के लिए — स्पष्ट राउटिंग, सुरक्षित Back व्यवहार और ऑनबोर्डिंग/अप्रूवल विज़ार्ड के व्यावहारिक उदाहरण।

पूर्वानुमेय बहु-चरण फ्लो के लिए SwiftUI NavigationStack पैटर्न

बहु-चरण फ्लो में क्या गलत हो जाता है

बहु-चरण फ्लो वह अनुक्रम है जहाँ स्टेप 1 के बिना स्टेप 2 का कोई अर्थ नहीं होता। सामान्य उदाहरणों में ऑनबोर्डिंग, अप्रूवल रिक्वेस्ट (रिव्यू, कन्फर्म, सबमिट), और विज़ार्ड-स्टाइल डेटा एंट्री शामिल हैं जहाँ किसी व्यक्ति ने कई स्क्रीन पर ड्राफ्ट बनाना होता है।

ये फ्लो तभी सहज लगते हैं जब Back उसी तरह काम करे जैसा उपयोगकर्ता उम्मीद करते हैं। अगर Back उन्हें कहीं अचंभित करने वाली जगह ले जाता है, तो उपयोगकर्ता ऐप पर भरोसा करना बंद कर देते हैं। इसका नतीजा गलत सबमिशन, अधूरी ऑनबोर्डिंग, और ऐसे सपोर्ट टिकट्स में दिखता है: “मैं उस स्क्रीन पर वापस नहीं जा पा रहा हूँ।”

अव्यवस्थित नेविगेशन आमतौर पर इन में से किसी एक की तरह दिखता है:

  • ऐप गलत स्क्रीन पर कूद जाता है, या फ्लो बहुत जल्दी बंद हो जाता है।
  • वही स्क्रीन दो बार दिखाई देती है क्योंकि उसे दो बार push किया गया था।
  • Back पर एक स्टेप रिसेट हो जाता है और उपयोगकर्ता अपना ड्राफ्ट खो देता है।
  • उपयोगकर्ता स्टेप 1 पूरा किए बिना स्टेप 3 तक पहुँच सकता है, जिससे invalid state बनती है।
  • डीप लिंक या ऐप रिस्टार्ट के बाद, ऐप सही स्क्रीन दिखाता है पर डेटा गलत होता है।

एक उपयोगी मानसिक मॉडल: एक बहु-चरण फ्लो दो चीज़ें साथ में चलती हैं।

पहले, स्क्रीन का एक stack (जिसमें उपयोगकर्ता पीछे जा सकता है)। दूसरे, साझा फ्लो स्टेट (ड्राफ्ट डेटा और प्रगति जो स्क्रीन गायब होने पर नहीं मिटनी चाहिए)।

कई NavigationStack सेटअप तब बिगड़ जाते हैं जब स्क्रीन स्टैक और फ्लो स्टेट अलग हो जाते हैं। उदाहरण के लिए, एक ऑनबोर्डिंग फ्लो "Create profile" को दो बार push कर सकता है (duplicate routes), जबकि ड्राफ्ट प्रोफ़ाइल व्यू के अंदर रहती है और re-render पर फिर से बन जाती है। उपयोगकर्ता Back दबाता है, फ़ॉर्म का अलग संस्करण देखता है, और मानता है कि ऐप अविश्वसनीय है।

पूर्वानुमेय व्यवहार की शुरुआत फ्लो का नामकरण करने, यह परिभाषित करने से होती है कि हर स्टेप पर Back क्या करना चाहिए, और फ्लो स्टेट को एक स्पष्ट स्थान देना।

मल्टी-स्टेप फ्लो के लिए, पुराने NavigationView की बजाय NavigationStack का उपयोग करें। NavigationView iOS संस्करणों के बीच अलग व्यवहार कर सकता है और push/pop या restore करते समय सोचना कठिन बनाता है। NavigationStack आधुनिक API है जो नेविगेशन को एक वास्तविक स्टैक की तरह ट्रीट करता है।

एक NavigationStack उपयोगकर्ता की यात्रा का इतिहास स्टोर करता है। हर push एक destination जोड़ता है। हर back एक destination को pop कर देता है। यही सरल नियम फ्लो को स्थिर बनाता है: UI को एक स्पष्ट स्टेप अनुक्रम को प्रतिबिंबित करना चाहिए।

स्टैक वास्तव में क्या रखता है

SwiftUI आपके view ऑब्जेक्ट्स को स्टोर नहीं कर रहा। यह उस डेटा (route value) को स्टोर करता है जिसका आप नेविगेशन के लिए उपयोग करते हैं और आवश्यकता पर destination view को rebuild करने के लिए उसे उपयोग करता है। इसके कुछ व्यवहारिक नतीजे हैं:

  • किसी view के जीवित रहने पर महत्वपूर्ण डेटा निर्भर न रखें।
  • अगर किसी स्क्रीन को state चाहिए, तो उसे pushed view के बाहर एक मॉडल (जैसे ObservableObject) में रखें।
  • अगर आप एक ही destination को अलग डेटा के साथ दो बार push करते हैं, SwiftUI उन्हें अलग stack entries के रूप में ट्रीट करता है।

NavigationPath उस वक्त उपयोगी है जब आपका फ्लो सिर्फ एक या दो फिक्स्ड pushes नहीं है। इसे एक edit करने योग्य सूची की तरह सोचें जिनमें "हम कहाँ जा रहे हैं" के मान होते हैं। आप आगे बढ़ने के लिए routes append कर सकते हैं, पीछे जाने के लिए last route हटा सकते हैं, या किसी बाद के स्टेप पर jump करने के लिए पूरा path replace कर सकते हैं।

यह विज़ार्ड-स्टाइल स्टेप्स, फ्लो को completion के बाद reset करने, या सेव्ड स्टेट से आंशिक फ्लो restore करने के लिए अच्छा फिट है।

पूर्वानुमेयता चालाकी से बेहतर है। कम छिपे नियम (automatic jumps, implicit pops, view-driven side effects) का मतलब बाद में कम अजीब back stack बग होंगे।

फ़्लो को छोटे route enum के साथ मॉडल करें

पूर्वानुमेय नेविगेशन एक निर्णय से शुरू होता है: routing को एक जगह रखें, और फ्लो की हर स्क्रीन को एक छोटा, स्पष्ट मान बनाएं।

एक single source of truth बनाएं, जैसे FlowRouter (एक ObservableObject) जो NavigationPath का मालिक हो। इससे हर push और pop consistent रहेगा, बजाय इसके कि नेविगेशन अलग-अलग views में बिखर जाए।

एक सरल router संरचना

स्टेप्स का प्रतिनिधित्व करने के लिए enum का उपयोग करें। associated values केवल हल्के identifiers (जैसे IDs) के लिए जोड़ें, पूरे मॉडल के लिए नहीं।

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() }
}

नेविगेशन स्टेट को फ्लो स्टेट से अलग रखें

नेविगेशन को "यूज़र कहाँ है" के रूप में और फ्लो स्टेट को "उन्होंने अब तक क्या भरा" के रूप में ट्रीट करें। फ्लो डेटा को अपनी ही store में रखें (उदाहरण के लिए OnboardingState जिसमें नाम, ईमेल, अपलोड किए गए दस्तावेज़) और इसे स्थिर रखें जब स्क्रीन आएँ और गायब हों।

एक सरल नियम:

  • FlowRouter.path में केवल Step मान हों।
  • OnboardingState उपयोगकर्ता के इनपुट और ड्राफ्ट डेटा रखे।
  • Steps में डेटा के बजाय IDs हों, ताकि डेटा lookup करके लिया जा सके।

यह नाजुक hashing, बड़े paths, और SwiftUI द्वारा views rebuild होने पर अचानक रिसेट से बचाता है।

Step-बाय-स्टेप: NavigationPath से एक विज़ार्ड बनाएं

विज़ार्ड-स्टाइल स्क्रीन के लिए, सबसे सरल तरीका खुद स्टैक को नियंत्रित करना है। "मैं फ्लो में कहाँ हूँ?" के लिए एक स्रोत और आगे/पीछे जाने का एक ही तरीका रखें।

NavigationStack(path:) को एक NavigationPath से बाइंड करके शुरू करें। हर pushed स्क्रीन एक मान (अक्सर enum case) द्वारा दर्शाई जाती है, और आप destinations को एक बार रजिस्टर करते हैं।

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
    }
}

Back को पूर्वानुमेय बनाए रखने के लिए कुछ आदतें अपनाएँ। आगे बढ़ने के लिए ठीक एक रूट append करें, "Next" को linear रखें (सिर्फ अगला स्टेप push करें), और जब jump करने की ज़रूरत हो (जैसे Review से "Edit profile"), तो stack को एक ज्ञात index तक trim करें।

इससे अनजाने में duplicate स्क्रीन push होने से बचता है और Back वही करेगा जो उपयोगकर्ता उम्मीद करते हैं: एक टैप = एक स्टेप।

स्क्रीन आते और जाते हुए डेटा को स्थिर रखें

Own your app’s codebase
कस्टम होस्टिंग या कस्टम काम के लिए असली सोर्स कोड एक्सपोर्ट करके नियंत्रण रखें।
Export Code

बहु-चरण फ्लो तब अविश्वसनीय लगता है जब हर स्क्रीन अपनी state खुद रखती है। आप नाम टाइप करते हैं, आगे जाते हैं, फिर वापस आते हैं और फ़ील्ड खाली होती है क्योंकि व्यू recreate हो गया।

सुलह सीधी है: फ्लो को एक ड्राफ्ट ऑब्जेक्ट के रूप में ट्रीट करें, और हर स्टेप उसे एडिट करे।

SwiftUI में, इसका मतलब अक्सर यह होता है कि एक साझा ObservableObject फ्लो के शुरुआत में एक बार बनाया जाए और हर स्टेप को पास किया जाए। जब तक स्टेप्स बदलते हैं, तब तक ड्राफ्ट को व्यू की @State में न रखें जब तक वह सचमुच सिर्फ उसी स्क्रीन का स्थानीय state न हो।

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

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

इसे entry point पर बनाएं, फिर @StateObject और @EnvironmentObject (या स्पष्ट रूप से पास कर के) हर स्टेप पर शेयर करें। अब स्टैक बदल सकता है बिना डेटा खोए।

तय करें कि Back नेविगेशन क्या बचाएगा

सब कुछ हमेशा स्थायी नहीं रहना चाहिए। पहले अपने नियम तय करें ताकि फ्लो consistent रहे।

यूज़र इनपुट (टेक्स्ट फील्ड, टॉगल, चयन) को तब तक रखें जब तक वे स्पष्ट रूप से reset न करें। स्टेप-विशिष्ट UI स्टेट (लोडिंग स्पिनर, अस्थायी अलर्ट, छोटी एनीमेशन) को reset करें। संवेदनशील फ़ील्ड (जैसे one-time कोड) को उस स्टेप छोड़ते ही साफ़ कर दें। अगर कोई विकल्प बाद के स्टेप्स को बदलता है, तो केवल dependent फ़ील्ड्स को क्लियर करें।

वैधता (validation) यहाँ प्राकृतिक रूप से फिट होती है। उपयोगकर्ता को आगे जाने देने के बजाय वर्तमान स्टेप पर रखें जब तक वह वैध न हो। canGoNextFromProfile जैसे computed प्रॉपर्टी के आधार पर बटन disable कर देना अक्सर काफी होता है।

चेकपॉइंट्स सेव करें पर जरूरत से ज्यादा न करें

कुछ ड्राफ्ट केवल मेमोरी में ठीक रहते हैं। कुछ को ऐप रिस्टार्ट या क्रैश के बाद भी बचाना चाहिए। एक प्रैक्टिकल डिफ़ॉल्ट:

  • उपयोगकर्ता जब सक्रिय रूप से स्टेप्स पार कर रहा हो तब डेटा मेमोरी में रखें।
  • स्पष्ट माइलस्टोन्स पर लोकली persist करें (खाता बना, अप्रूवल सबमिट हुआ, भुगतान शुरू हुआ)।
  • अगर फ्लो लंबा है या डेटा एंट्री एक मिनट से अधिक लेती है, तो पहले persist करें।

इस तरह, स्क्रीन आ-जा सकती हैं और उपयोगकर्ता की प्रगति स्थिर और उनके समय के प्रति सम्मानजनक लगेगी।

डीप लिंक और आंशिक फ्लो को रिस्टोर करना

डीप लिंक मायने रखते हैं क्योंकि वास्तविक फ्लो अक्सर स्टेप 1 से शुरू नहीं होते। कोई ईमेल, पुश नोटिफिकेशन, या शेयर लिंक टैप करता है और उम्मीद करता है कि वह सही स्क्रीन पर पहुँचे, जैसे ऑनबोर्डिंग का स्टेप 3 या फाइनल अप्रूवल स्क्रीन।

NavigationStack के साथ, डीप लिंक को एक valid path बनाने के निर्देश के रूप में देखें, किसी एक view पर कूदने के कमान के रूप में नहीं। फ्लो की शुरुआत से शुरू करें और केवल वे स्टेप्स append करें जो उस उपयोगकर्ता और सत्र के लिए सच हैं।

बाहरी लिंक को सुरक्षित रूट अनुक्रम में बदलना

एक अच्छा पैटर्न है: external ID पार्स करें, न्यूनतम डेटा लोड करें जिसकी आपको ज़रूरत है, फिर इसे रूट्स के अनुक्रम में बदलें।

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
}

ये चेक्स आपकी गार्डरेल्स हैं। अगर prerequisites गायब हैं, तो उपयोगकर्ता को step 3 पर error के साथ न छोड़े बिना पहले उस missing स्टेप पर भेजें, और सुनिश्चित करें कि back stack coherent कहानी ही बता रहा हो।

आंशिक फ्लो को रिस्टोर करना

रिस्टोर के लिए दो चीजें सेव करें: आखिरी ज्ञात route स्टेट और उपयोगकर्ता द्वारा भरा ड्राफ्ट डेटा। फिर यह तय करें कि बिना उपयोगकर्ता को हैरान किए कैसे resume करना है।

अगर ड्राफ्ट ताज़ा है (कुछ मिनट या घंटे), तो एक स्पष्ट "Resume" विकल्प दें। अगर पुराना है, तो शुरुआत से शुरू करें पर फ़ील्ड्स को prefill रखें। अगर आवश्यकताएँ बदल गईं हों, तो वही guardrails उपयोग करके path rebuild करें।

Push बनाम modal: फ्लो से बाहर निकलना आसान रखें

Build full-stack without glue code
एक ही फ्लो डिज़ाइन से प्रोडक्शन-रेडी बैकएंड, वेब और मोबाइल ऐप जनरेट करें।
Generate App

एक फ्लो तभी पूर्वानुमेय लगता है जब आगे बढ़ने का एक मुख्य तरीका हो: एकल स्टैक पर स्क्रीन push करना। साइड टास्क के लिए sheets और full-screen covers का प्रयोग करें, मुख्य रास्ते के लिए नहीं।

Push (NavigationStack) उस समय फिट बैठता है जब उपयोगकर्ता Back से अपने कदम retrace करने की उम्मीद करता है। Modals (sheet या fullScreenCover) उन मामलों के लिए हैं जब उपयोगकर्ता कोई साइड टास्क कर रहा है, जल्दी विकल्प चुन रहा है, या किसी रिस्की एक्शन की पुष्टि कर रहा है।

कुछ सरल नियम अधिकांश नेविगेशन अजीबताओं को रोकते हैं:

  • मुख्य पथ के लिए push करें (Step 1, Step 2, Step 3)।
  • छोटे वैकल्पिक टास्क के लिए sheet का उपयोग करें (तारीख चुनना, देश चुनना, डॉक्युमेंट स्कैन)।
  • अलग दुनिया के अनुभवों के लिए fullScreenCover का उपयोग करें (login, camera capture, लंबा कानूनी दस्तावेज़)।
  • पुष्टि करने के लिए modal का उपयोग करें (flow रद्द करना, ड्राफ्ट हटाना, अप्रूवल के लिए सबमिट करना)।

सामान्य गलती यह है कि मुख्य फ्लो स्क्रीन को sheets में रखें। अगर स्टेप 2 एक sheet है, तो उपयोगकर्ता उसे swipe से dismiss कर सकता है, संदर्भ खो सकता है, और स्टैक कह सकता है कि वे स्टेप 1 पर हैं जबकि उनका डेटा कहता है कि वे स्टेप 2 पूरा कर चुके हैं।

पुष्टिकरण इसके विपरीत: एक "Are you sure?" स्क्रीन को विज़ार्ड में push करने से स्टैक clutter होता है और लूप बन सकते हैं (Step 3 -> Confirm -> Back -> Step 3 -> Back -> Confirm)।

"Done" के बाद सब कुछ साफ़ बंद कैसे करें

पहले तय करें कि "Done" का क्या मतलब है: होम स्क्रीन पर लौटना, सूची पर लौटना, या एक सफलता स्क्रीन दिखाना।

अगर फ्लो push किया गया है, तो NavigationPath को खाली करके reset करें ताकि सभी pushed स्क्रीन pop हो जाएँ। अगर फ्लो modal में प्रस्तुत किया गया है, तो Environment से dismiss() कॉल करें। अगर दोनों हैं (एक modal जिसमें NavigationStack है), तो modal dismiss करें, न कि हर pushed स्क्रीन। सफल सबमिट के बाद ड्राफ्ट स्टेट भी क्लियर करें ताकि दोबारा खुलने पर फ्लो fresh ही शुरू हो।

Back बटन व्यवहार और “क्या आप सुनिश्चित हैं?” क्षण

Give draft data a home
अपना फ्लो डेटा एक विज़ुअल डेटाबेस डिज़ाइनर में मॉडल करें और स्टेप्स के बीच लगातार रखें।
Create App

अधिकांश बहु-चरण फ्लो के लिए सबसे अच्छा काम यही है: कुछ न करें—सिस्टम back बटन और swipe-back जेस्चर को जीने दें। यह उपयोगकर्ता की अपेक्षा से मेल खाता है और ऐसे बग से बचाता है जहाँ UI कुछ कहता है पर नेविगेशन स्टेट कुछ और।

Intercept केवल तब विवेकपूर्ण है जब वापस जाने से वास्तविक नुकसान होगा, जैसे लंबे unsaved फॉर्म का नुकसान या irreversible action को छोड़ना। अगर उपयोगकर्ता सुरक्षित रूप से वापस आकर जारी रख सकता है, तो friction न बढ़ाएँ।

एक व्यावहारिक तरीका है कि सिस्टम नेविगेशन रखें, पर confirmation केवल तब जोड़ें जब स्क्रीन "dirty" हो (संपादित)। इसका मतलब है कि आप अपनी खुद की back क्रिया प्रदान करें और एक बार पूछें, स्पष्ट तरीके से बाहर निकलने का रास्ता दें।

@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) {}
    }
}

इसे एक जाल में बदलने से रोकें:

  • केवल तब पूछें जब आप एक छोटे वाक्य में नतीजा समझा सकें।
  • एक सुरक्षित विकल्प (Cancel, Keep Editing) और एक स्पष्ट बाहर निकलने का विकल्प (Discard, Leave) दें।
  • back बटन को छुपाएँ नहीं जब तक आप उसे एक स्पष्ट Back या Close न दें।
  • irreversible action (जैसे “Approve”) की पुष्टि करना ज़्यादा पसंद करें बजाए हर जगह नेविगेशन ब्लॉक करने के।

अगर आप बार-बार back gesture के खिलाफ लड़ रहे हैं, तो आमतौर पर इसका अर्थ है कि फ्लो को autosave, saved draft, या छोटे स्टेप्स की ज़रूरत है।

अजीब back स्टैक्स बनाने वाली आम गलतियाँ

अधिकांश "क्यों यह वहाँ वापस गया?" बग SwiftUI की यादृच्छिकता नहीं होती। वे आमतौर पर पैटर्न की वजह से आते हैं जो नेविगेशन स्टेट को अस्थिर बनाते हैं। पूर्वानुमेय व्यवहार के लिए, बैक स्टैक को ऐप डेटा की तरह समझें: स्थिर, टेस्टेबल, और एक ही जगह द्वारा मालिकाना होना चाहिए।

आकस्मिक अतिरिक्त स्टैक्स

एक आम陷ाव यह है कि अनजाने में कई NavigationStack बन जाते हैं। उदाहरण के लिए, हर tab का अपना root stack होता है, और फिर एक child view फ्लो के अंदर और एक stack जोड़ देता है। नतीजा confusing back व्यवहार, गायब नेविगेशन बार, या ऐसी स्क्रीन होती हैं जो उम्मीद के मुताबिक pop नहीं होतीं।

एक और अक्सर होने वाली समस्या यह है कि आप अपना NavigationPath बार-बार recreate कर रहे हैं। अगर path किसी ऐसे view के अंदर बनता है जो re-renders होता है, तो यह state बदलने पर reset हो सकता है और उपयोगकर्ता को फ़ील्ड टाइप करने के बाद step 1 पर उछाल सकता है।

अधिकांश अजीब स्टैक्स के पीछे की गलतियाँ सरल हैं:

  • किसी stack के अंदर अनजाने में दूसरा NavigationStack नेस्ट करना (अक्सर tabs या sheet कंटेंट के भीतर)
  • view updates के दौरान NavigationPath() को फिर से initialize करना बजाय इसे लंबे समय तक रहने वाले state में रखने के
  • अपने route में अस्थिर मान रखना (जैसे कोई मॉडल ऑब्जेक्ट जो बदलता है), जो Hashable तोड़ता है और mismatched destinations बनाता है
  • नेविगेशन निर्णयों को बटन हैंडलर्स में बिखेर देना जब तक कोई स्पष्ट “next” नियम न रहे
  • एक ही समय में फ्लो को कई स्रोतों से ड्राइव करना (उदाहरण के लिए, view model और view दोनों path को mutate करना)

अगर आपको स्टेप्स के बीच डेटा पास करनी है, तो route में स्थिर identifiers (IDs, step enums) पसंद करें और वास्तविक फॉर्म डेटा साझा state में रखें।

एक ठोस उदाहरण: अगर आपका route .profile(User) है और User व्यक्ति टाइप करते हुए बदलता रहता है, तो SwiftUI इसे अलग route मान सकता है और स्टैक को फिर से वायर कर सकता है। route को .profile रखें और ड्राफ्ट प्रोफ़ाइल डेटा को साझा state में रखें।

पूर्वानुमेय नेविगेशन के लिए त्वरित चेकलिस्ट

Put these patterns into practice
यहाँ चर्चा किए गए भरोसेमंद मल्टी-स्टेप अनुभव No-code प्लेटफ़ॉर्म पर बनाएं।
Try AppMaster

जब कोई फ्लो अजीब लगे, तो आमतौर पर बैक स्टैक वही कहानी नहीं बता रहा जो उपयोगकर्ता देखना चाहता है। UI को पॉलिश करने से पहले अपनी नेविगेशन नियमों पर एक त्वरित पास करें।

रियल डिवाइस पर टेस्ट करें, सिर्फ previews नहीं, और धीरे और तेज दोनों तरह के टैप आज़माएँ। तेज़ टैप अक्सर duplicate pushes और missing state को उजागर करते हैं।

  • आखिरी स्क्रीन से पहले की तरफ़ एक-एक कदम पीछे जाएँ। पुष्टि करें कि हर स्क्रीन वही डेटा दिखाती है जो उपयोगकर्ता ने पहले भरा था।
  • हर स्टेप से Cancel ट्रिगर करें (पहला और आखिरी सहित)। पुष्टि करें कि यह हमेशा एक समझदार जगह पर वापस लौटाता है, किसी रैंडम पहले स्क्रीन पर नहीं।
  • फ्लो के बीच में ऐप को फोर्स क्विट करें और फिर relaunch करें। सुनिश्चित करें कि आप सुरक्षित रूप से resume कर सकते हैं, या तो path restore करके या सेव्ड डेटा के साथ ज्ञात स्टेप से फिर से शुरू करके।
  • डीप लिंक या ऐप शॉर्टकट से फ्लो खोलें। सत्यापित करें कि destination step वैध है; अगर आवश्यक डेटा गायब है, तो सबसे पहले उस स्टेप पर redirect करें जो उसे इकट्ठा कर सकता है।
  • Done के साथ समाप्त करें और पुष्टि करें कि फ्लो साफ़ तौर पर हटा दिया गया है। उपयोगकर्ता Back दबाकर पूरा हुआ विज़ार्ड दोबारा नहीं पहुँच सके।

परीक्षण का एक सरल तरीका: एक तीन-स्क्रीन वाला onboarding विज़ार्ड कल्पना करें (Profile, Permissions, Confirm)। एक नाम डालें, आगे बढ़ें, वापस जाएँ, उसे एडिट करें, फिर डीप लिंक के जरिए Confirm तक कूदें। अगर Confirm पुराना नाम दिखाता है, या Back आपको duplicate Profile स्क्रीन पर ले जाता है, तो आपके path अपडेट consistent नहीं हैं।

अगर आप बिना सरप्राइज़ के चेकलिस्ट पास कर लेते हैं, तो आपका फ्लो शांत और पूर्वानुमेय लगेगा, भले ही उपयोगकर्ता बाद में लौटकर आएं।

एक यथार्थवादी उदाहरण और अगले कदम

एक मैनेजर अप्रूवल फ्लो की कल्पना करें। इसमें चार स्टेप हैं: Review, Edit, Confirm, और Receipt। उपयोगकर्ता एक ही बात उम्मीद करता है: Back हमेशा पिछला स्टेप पर जाए, किसी रैंडम पहले देखे गए स्क्रीन पर नहीं।

एक सरल route enum इसको पूर्वानुमेय रखता है। आपका NavigationPath केवल रूट और किसी छोटे identifier (जैसे expenseID और mode — review बनाम edit) को स्टोर करे ताकि state reload हो सके; बड़े, mutable मॉडल को path में डालने से restores और deep links fragile हो जाते हैं।

वर्किंग ड्राफ्ट को views के बाहर एक सिंगल सोर्स ऑफ ट्रुथ में रखें, जैसे @StateObject फ्लो मॉडल (या कोई स्टोर)। हर स्टेप उस मॉडल को पढ़े और लिखे, ताकि स्क्रीन जो भी दिखें वे इनपुट खोए बिना प्रकट या गायब हो सकें।

कम से कम आप तीन चीजें ट्रैक कर रहे होंगे:

  • Routes (उदा.: review(expenseID), edit(expenseID), confirm(expenseID), receipt(expenseID))
  • Data (एक ड्राफ्ट ऑब्जेक्ट जिसमें line items और नोट्स और एक status जैसे pending, approved, rejected)
  • Location (ड्राफ्ट आपके फ्लो मॉडल में, canonical रिकॉर्ड सर्वर पर, और एक छोटा restore token लोकली: expenseID + last step)

एज केस वही हैं जहाँ फ्लो भरोसा कमाता या खो देता है। अगर मैनेजर Confirm में reject कर देता है, तो तय करें कि Back Edit पर लौटता है (fix के लिए) या फ्लो से बाहर निकलता है। अगर वे बाद में लौटते हैं, तो saved token से last step restore करें और ड्राफ्ट reload करें। अगर वे डिवाइस बदलते हैं, तो सर्वर को सत्यता मानें: सर्वर स्टेट से path reconstruct करें और उन्हें सही स्टेप पर भेजें।

अगले कदम: अपने route enum का दस्तावेज़ बनाएं (किस केस का क्या मतलब है और कब उपयोग होता है), path building और restore व्यवहार के लिए कुछ बेसिक टेस्ट जोड़ें, और एक नियम अपनाएँ: views नेविगेशन निर्णयों के मालिक न हों।

अगर आप हर बार सब कुछ scratch से नहीं बनाना चाहते, तो प्लेटफ़ॉर्म्स जैसे AppMaster उसी अलगाव का पालन करते हैं: स्टेप नेविगेशन और बिजनेस डेटा को अलग रखें ताकि स्क्रीन बदलें पर उपयोगकर्ता की प्रगति न टूटे।

सामान्य प्रश्न

Back बटन को किफ़ायती कैसे बनाएँ multi-step SwiftUI फ्लो में?

Navigation के लिए एक ही NavigationPath रखें जिसे आप नियंत्रित करते हैं। हर “Next” कार्रवाई के लिए बिल्कुल एक रूट push करें और हर Back के लिए एक रूट pop करें। जब किसी तरह का jump करना हो (जैसे Review से “Edit profile”), तो स्टैक को एक ज्ञात स्टेप तक trim करें बजाय और स्क्रीन push करने के।

मेरा फॉर्म डेटा Back करने पर क्यों गायब हो जाता है?

SwiftUI destination views को वही view instance स्टोर करके नहीं रखता — वह नेविगेशन के लिए उपयोग किए गए डेटा (route value) को स्टोर करके view को फिर से बनाता है। अगर आपका फॉर्म डेटा view की @State में है, तो view के फिर से बनते ही वह रिसेट हो सकता है। ड्राफ्ट डेटा को एक साझा मॉडल (जैसे ObservableObject) में रखें जो pushed views के बाहर रहता हो।

स्टैक में मुझे वही स्क्रीन दोबारा क्यों दिख रही है?

अक्सर ऐसा तब होता है जब आप एक ही रूट को एक से अधिक बार append कर देते हैं (त्वरित टैप्स या कई कोड पाथ्स के कारण)। Next बटन को navigation या validation/loading के दौरान disable कर दें, और नेविगेशन म्यूटेशन्स को एक ही जगह रखें ताकि हर स्टेप पर केवल एक ही append हो।

मेरे राउट enum में क्या स्टोर करना चाहिए और क्या बाहर रखना चाहिए?

राउट में छोटे और स्थिर मान रखें, जैसे enum case और हल्के IDs। mutable डेटा (ड्राफ्ट) को अलग साझा ऑब्जेक्ट में रखें और आवश्यकता पड़ने पर ID से lookup करें। बड़े और बदलते मॉडल को path में push करने से Hashable नियम टूट सकते हैं और destinations mismatch कर सकते हैं।

नेविगेशन स्टेट को फ्लो स्टेट से साफ़ तरीके से कैसे अलग करूँ?

Navigation बताता है “यूज़र कहाँ है” और flow state बताता है “उन्होंने क्या भरा है”। नेविगेशन पाथ को एक router या टॉप-लेवल स्टेट में रखें और ड्राफ्ट को एक अलग ObservableObject में रखें। हर स्क्रीन ड्राफ्ट को एडिट करे; router सिर्फ स्टेप बदलता है।

विज़ार्ड के स्टेप 3 में deep links को सुरक्षित तरीके से कैसे हैंडल करें?

Deep link को एक single view पर teleport की तरह न लें; उसे वैध स्टेप्स की एक sequence बनाने के निर्देश समझें। पहले जरूरी prerequisite स्टेप्स जोड़ें (उपयोगकर्ता के क्या पूरे हुए हैं इसके आधार पर), फिर target स्टेप जोड़ें। इससे back stack coherent रहता है और invalid state से बचता है।

ऐप रिस्टार्ट के बाद आंशिक रूप से पूरा हुआ फ्लो कैसे restore करूँ?

दो चीज़ें सेव करें: आखिरी meaningful route (या स्टेप पहचान) और ड्राफ्ट डेटा। रीलॉन्च पर वही prerequisite चेक्स लगाकर path वापस बनाएं और ड्राफ्ट लोड करें। अगर ड्राफ्ट पुराना है, तो फ़ील्ड्स को prefill करके फ्लो को शुरू करना अक्सर उपयोगकर्ता के लिए कम हैरान करने वाला होता है।

किस समय मैं push करूँ और कब modal प्रेजेंट करूँ मल्टी-स्टेप फ्लो में?

मुख्य step-by-step path के लिए push करें ताकि Back नैचुरल तरीके से flow को retrace करे। optional side tasks के लिए sheets और अलग अनुभवों (login, camera) के लिए fullScreenCover का उपयोग करें। कोर स्टेप्स को modal में न रखें क्योंकि dismiss gestures UI और flow state को असंरेखित कर सकती हैं।

क्या मुझे back gesture को override करके “Are you sure?” डायलॉग दिखाना चाहिए?

डिफ़ॉल्ट रूप से System Back को बदलने से बचें; वही उपयोगकर्ता की अपेक्षा से मेल खाता है। केवल तब confirmation दिखाएँ जब छोड़ने पर वास्तविक नुकसान होगा (लंबा unsaved फॉर्म या irreversible action)। स्क्रीन अगर "dirty" है तो ही कन्फ़र्मेशन दिखाएँ; autosave या ड्राफ्ट persistence पहले विकल्प होना चाहिए।

वेयर बैक स्टैक बग आमतौर पर किस वजह से बनते हैं?

आम कारणों में शामिल हैं: multiple NavigationStack का अनजाने में नेस्टिंग, NavigationPath को बार-बार re-initialize करना, और path को mutate करने के कई मालिक होना। एक फ्लो पर एक स्टैक रखें, पाथ को लंबे-जीवन वाले स्टेट (@StateObject या एक router) में रखें, और push/pop लॉजिक को केंद्रीकृत करें।

शुरू करना आसान
कुछ बनाएं अद्भुत

फ्री प्लान के साथ ऐपमास्टर के साथ प्रयोग करें।
जब आप तैयार होंगे तब आप उचित सदस्यता चुन सकते हैं।

शुरू हो जाओ