You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中导航栈多视图的批量关闭与重置实现问题

SwiftUI 导航栈与Sheet层级控制问题

我在SwiftUI中通过NavigationLink在列表内堆叠了多个视图,视图层级为:ContentView → MediatorView(sheet内)→ FirstListView → SecondListView → ThirdListView。目前需要实现两个功能:

  • 点击ThirdListView的「Done」按钮时,关闭所有视图并退出sheet,回到ContentView;
  • 点击ThirdListView的「Reset」按钮时,回到sheet的初始视图MediatorView。

原代码未实现上述需求,相关代码如下:

import SwiftUI
import Foundation

struct ContentView: View {
    @State var openMediatorView: Bool = false
    var body: some View {
        VStack {
            Button {
                self.openMediatorView.toggle()
            } label: {
                Text("Click Me").font(.body.bold())
            }
        }
        .sheet(isPresented: self.$openMediatorView) {
            MediatorView()
        }
    }
}

struct MediatorView: View {
    let firstList: [String] = ["One", "Two", "Three", "Four", "Five"]
    let secondList: [String] = ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"]
    let thirdList: [String] = ["One", "Two", "Three", "Four", "Five", "Six", "Seven"]
    
    @State var showFirstRow: Bool = false

    var body: some View {
        NavigationStack {
            Group {
                if firstList.count > 1 {
                    FirstListView(isHidden: .constant(true), shortList: firstList, longList: secondList, thirdList: thirdList)
                } else {
                    SecondListView(isHidden: .constant(true), longList: secondList, thirdList: thirdList).navigationBarBackButtonHidden()
                }
            }
            .interactiveDismissDisabled()
        }
    }
}

struct FirstListView: View {
    init(isHidden: Binding<Bool>, shortList: [String], longList: [String], thirdList: [String]) {
        self.isHidden = isHidden
        self.shortList = shortList
        self.longList = longList
        self.thirdList = thirdList
    }
    
    var isHidden: Binding<Bool>
    var shortList: [String]
    var longList: [String]
    var thirdList: [String]
    
    var body: some View {
        ScrollViewReader { proxy in
            VStack {
                List(shortList, id: \.hashValue) { value in
                    NavigationLink(destination: {
                        SecondListView(isHidden: .constant(true), longList: longList, thirdList: thirdList)
                    },
                    label: {
                        HStack {
                            Text(value)
                            Spacer()
                        }
                        .contentShape(Rectangle())
                    })
                }
                .listStyle(.plain)
                .contentShape(Rectangle())
            }
        }
        .navigationTitle("First List")
        .interactiveDismissDisabled()
    }
}

struct SecondListView: View {
    init(isHidden: Binding<Bool>, longList: [String], thirdList: [String]) {
        self.isHidden = isHidden
        self.longList = longList
        self.thirdList = thirdList
    }
    
    var isHidden: Binding<Bool>
    var longList: [String]
    var thirdList: [String]
    
    var body: some View {
        ScrollViewReader { proxy in
            VStack {
                List(longList, id: \.hashValue) { value in
                    NavigationLink(destination: {
                        ThirdListView(isHidden: .constant(true), thirdList: thirdList)
                    },
                    label: {
                        HStack {
                            Text(value)
                            Spacer()
                        }
                        .contentShape(Rectangle())
                    })
                }
                .listStyle(.plain)
                .contentShape(Rectangle())
            }
        }
        .navigationTitle("Second List")
        .interactiveDismissDisabled()
    }
}

struct ThirdListView: View {
    @Environment(\.presentationMode) private var presentationMode
    
    init(isHidden: Binding<Bool>, thirdList: [String]) {
        self.isHidden = isHidden
        self.thirdList = thirdList
    }
    
    var isHidden: Binding<Bool>
    var thirdList: [String]
    
    var body: some View {
        ScrollViewReader { proxy in
            VStack {
                List(thirdList, id: \.hashValue) { value in
                    HStack {
                        Text(value)
                        Spacer()
                    }
                    .contentShape(Rectangle())
                }
                .listStyle(.plain)
                .contentShape(Rectangle())
                
                Button {
                    isHidden.wrappedValue = false
                    presentationMode.wrappedValue.dismiss()
                } label: {
                    Text("Reset to Mediator View").font(.body.bold())
                }
            }
        }
        .interactiveDismissDisabled()
        .navigationTitle("Third List")
        .toolbar {
            ToolbarItem(placement: .topBarTrailing) {
                Button {
                    isHidden.wrappedValue = false
                    presentationMode.wrappedValue.dismiss()
                } label: {
                    Text("Done").font(.body.bold())
                }
            }
        }
    }
}

解决方案

要实现这两个需求,我们需要通过NavigationStack路径管理控制sheet内的导航层级,同时通过绑定传递sheet展示状态直接关闭整个sheet。修改后的完整代码如下:

import SwiftUI

// 枚举标记导航节点,清晰管理层级
enum SheetNavigationPath: Hashable {
    case secondList
    case thirdList
}

struct ContentView: View {
    @State var openMediatorView: Bool = false
    
    var body: some View {
        VStack {
            Button {
                openMediatorView.toggle()
            } label: {
                Text("Click Me").font(.body.bold())
            }
        }
        // 将sheet展示状态绑定传递给MediatorView
        .sheet(isPresented: $openMediatorView) {
            MediatorView(isSheetPresented: $openMediatorView)
        }
    }
}

struct MediatorView: View {
    let firstList: [String] = ["One", "Two", "Three", "Four", "Five"]
    let secondList: [String] = ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"]
    let thirdList: [String] = ["One", "Two", "Three", "Four", "Five", "Six", "Seven"]
    
    @Binding var isSheetPresented: Bool
    // 管理NavigationStack的导航路径
    @State private var navigationPath = [SheetNavigationPath]()

    var body: some View {
        NavigationStack(path: $navigationPath) {
            Group {
                if firstList.count > 1 {
                    FirstListView(navigationPath: $navigationPath, shortList: firstList, longList: secondList, thirdList: thirdList)
                } else {
                    SecondListView(navigationPath: $navigationPath, longList: secondList, thirdList: thirdList)
                        .navigationBarBackButtonHidden()
                }
            }
            .interactiveDismissDisabled()
            // 映射路径对应的目标视图
            .navigationDestination(for: SheetNavigationPath.self) { path in
                switch path {
                case .secondList:
                    SecondListView(navigationPath: $navigationPath, longList: secondList, thirdList: thirdList)
                case .thirdList:
                    ThirdListView(navigationPath: $navigationPath, isSheetPresented: $isSheetPresented, thirdList: thirdList)
                }
            }
        }
    }
}

struct FirstListView: View {
    @Binding var navigationPath: [SheetNavigationPath]
    let shortList: [String]
    let longList: [String]
    let thirdList: [String]
    
    var body: some View {
        List(shortList, id: \.self) { value in
            Button(action: {
                navigationPath.append(.secondList)
            }) {
                HStack {
                    Text(value)
                    Spacer()
                }
            }
        }
        .listStyle(.plain)
        .navigationTitle("First List")
        .interactiveDismissDisabled()
    }
}

struct SecondListView: View {
    @Binding var navigationPath: [SheetNavigationPath]
    let longList: [String]
    let thirdList: [String]
    
    var body: some View {
        List(longList, id: \.self) { value in
            Button(action: {
                navigationPath.append(.thirdList)
            }) {
                HStack {
                    Text(value)
                    Spacer()
                }
            }
        }
        .listStyle(.plain)
        .navigationTitle("Second List")
        .interactiveDismissDisabled()
    }
}

struct ThirdListView: View {
    @Binding var navigationPath: [SheetNavigationPath]
    @Binding var isSheetPresented: Bool
    let thirdList: [String]
    
    var body: some View {
        VStack {
            List(thirdList, id: \.self) { value in
                HStack {
                    Text(value)
                    Spacer()
                }
            }
            .listStyle(.plain)
            
            // Reset按钮:清空导航路径回到MediatorView
            Button {
                navigationPath.removeAll()
            } label: {
                Text("Reset to Mediator View").font(.body.bold())
            }
        }
        .interactiveDismissDisabled()
        .navigationTitle("Third List")
        .toolbar {
            ToolbarItem(placement: .topBarTrailing) {
                // Done按钮:直接关闭整个sheet
                Button {
                    isSheetPresented = false
                } label: {
                    Text("Done").font(.body.bold())
                }
            }
        }
    }
}

关键改动说明

  1. 导航路径管理

    • 定义SheetNavigationPath枚举标记各个导航节点,替代原有的硬编码NavigationLink目的地
    • 在MediatorView中用@State private var navigationPath绑定到NavigationStack,通过修改路径数组控制导航层级
    • Reset按钮通过navigationPath.removeAll()清空路径,直接回到MediatorView初始视图
  2. Sheet状态传递

    • 将ContentView的openMediatorView绑定传递给MediatorView,再传递到ThirdListView
    • Done按钮直接设置isSheetPresented = false,一键关闭整个sheet回到ContentView
  3. 导航方式优化

    • 用Button+路径append操作替代原NavigationLink(destination:),更灵活控制导航流程,符合现代SwiftUI导航规范

内容的提问来源于stack exchange,提问作者tech_human

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 04:50:55