SwiftUI:TabView切换时保留AddItemView及弹窗确认需求问询
现有代码实现
File: ContentView
struct ContentView: View { var body: some View { TabView { ListView() .tabItem { Image(systemName: "book") } } } }
File: ListView
struct ListView: View { var ListViewContent: some View { ZStack { EmptyView() // for only description VStack { Spacer() HStack { Spacer() NavigationLink(destination: AddItemView(viewModel: videModel)) { Image(systemName: "plus.circle.fill") .resizable() .frame(width: 50, height: 50) .symbolRenderingMode(.palette) .foregroundStyle(.white,.blue) } .padding(.trailing, 24) .padding(.bottom, 24) } } } } .toolbar { ToolbarItem(placement: .navigationBarTrailing) { NavigationLink { AddItemView(viewModel: videModel) } label: { Text("Add some Item") } } }
File: AddItemView
struct AddFileView: View { var body: some View { NavigationStack { Form { // Forms... } } .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button("Cancle") { showAlert = true } .alert(isPresented: $showAlert) { Alert( title: Text("Close the window alert"), message: Text(""), primaryButton: .destructive(Text("Close")) { dismiss() }, secondaryButton: .cancel(Text("Cancel")) ) } } } .interactiveDismissDisabled(true) .navigationBarBackButtonHidden(true) } }
当前问题
当AddItemView处于显示状态时,点击TabView的TabItem会直接关闭AddItemView。尝试过以下方案但未解决:
- 使用
onDismiss阻止行为,效果不符合预期 - 通过绑定或类检测
AddItemView的显示状态 - 将TabItem替换为Button,但
ListView未触发TabView重绘导致失效
需求
- 仅在
AddItemView显示时,点击TabView的TabItem弹出类似现有“Close the window alert”的弹窗,阻止直接返回ListView; - 实现类似Apple Mail的功能:无需关闭
AddItemView即可临时显示ListView,返回时保留AddItemView中@State存储的输入数据(关闭视图会重置这些值)。曾考虑用@AppStorage单独存储各变量,但因变量过多不具可行性,寻求更优方案。
解决方案
针对需求1:拦截TabItem点击并弹出确认弹窗
核心思路是通过全局状态跟踪AddItemView的显示状态,并自定义TabItem的点击行为,替代系统默认切换逻辑。
- 创建全局状态管理类:
class AppState: ObservableObject { @Published var isAddItemViewPresented = false @Published var showTabSwitchAlert = false }
- 自定义TabView并拦截点击事件(通过UIKit代理实现精准拦截):
struct CustomTabView: UIViewControllerRepresentable { @EnvironmentObject var appState: AppState var viewControllers: [UIViewController] func makeUIViewController(context: Context) -> UITabBarController { let tabBarController = UITabBarController() tabBarController.viewControllers = viewControllers tabBarController.delegate = context.coordinator return tabBarController } func updateUIViewController(_ uiViewController: UITabBarController, context: Context) {} func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, UITabBarControllerDelegate { let parent: CustomTabView init(_ parent: CustomTabView) { self.parent = parent } func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool { if parent.appState.isAddItemViewPresented { parent.appState.showTabSwitchAlert = true return false // 阻止默认切换 } return true } } }
- 在
ContentView中集成自定义TabView并绑定弹窗:
struct ContentView: View { @StateObject private var appState = AppState() var body: some View { CustomTabView( viewControllers: [UIHostingController(rootView: ListView().environmentObject(appState))] ) .alert(isPresented: $appState.showTabSwitchAlert) { Alert( title: Text("Close the window alert"), message: Text(""), primaryButton: .destructive(Text("Close")) { appState.isAddItemViewPresented = false }, secondaryButton: .cancel(Text("Cancel")) ) } .environmentObject(appState) } }
- 修改
ListView,更新AddItemView的显示状态:
struct ListView: View { @EnvironmentObject private var appState: AppState @State private var navigationPath = NavigationPath() var body: some View { NavigationStack(path: $navigationPath) { ZStack { EmptyView() VStack { Spacer() HStack { Spacer() Button { navigationPath.append("AddItem") appState.isAddItemViewPresented = true } label: { Image(systemName: "plus.circle.fill") .resizable() .frame(width: 50, height: 50) .symbolRenderingMode(.palette) .foregroundStyle(.white,.blue) } .padding(.trailing, 24) .padding(.bottom, 24) } } } .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button { navigationPath.append("AddItem") appState.isAddItemViewPresented = true } label: { Text("Add some Item") } } } .navigationDestination(for: String.self) { value in if value == "AddItem" { AddItemView(navigationPath: $navigationPath) .onDisappear { appState.isAddItemViewPresented = false } } } } } }
针对需求2:保留AddItemView输入状态
核心思路是将AddItemView的输入状态从@State提升到全局共享的ViewModel中,避免视图销毁时丢失数据。
- 创建
AddItem的ViewModel:
class AddItemViewModel: ObservableObject { // 所有输入字段用@Published替代@State @Published var title = "" @Published var content = "" // 其他业务字段... // 可选:重置状态方法 func reset() { title = "" content = "" } }
- 在全局
AppState中注入ViewModel:
class AppState: ObservableObject { @Published var isAddItemViewPresented = false @Published var showTabSwitchAlert = false // 全局共享,确保状态不丢失 let addItemViewModel = AddItemViewModel() }
- 修改
AddItemView使用ViewModel的状态:
struct AddItemView: View { @EnvironmentObject private var appState: AppState @Binding var navigationPath: NavigationPath @State private var showAlert = false var body: some View { NavigationStack { Form { TextField("标题", text: $appState.addItemViewModel.title) TextEditor(text: $appState.addItemViewModel.content) // 其他表单字段... } } .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button("取消") { showAlert = true } .alert(isPresented: $showAlert) { Alert( title: Text("Close the window alert"), message: Text(""), primaryButton: .destructive(Text("关闭")) { navigationPath.removeLast() // 如需关闭时重置状态,调用reset() // appState.addItemViewModel.reset() }, secondaryButton: .cancel(Text("取消")) ) } } } .interactiveDismissDisabled(true) .navigationBarBackButtonHidden(true) } }
此方案下,即使切换Tab或临时返回ListView,AddItemViewModel的状态会被全局保留,再次打开AddItemView时输入数据不会丢失。若要实现类似Apple Mail的后台保留编辑视图,可结合NavigationSplitView(iPad)或多窗口布局,让AddItemView始终处于存活状态。
内容的提问来源于stack exchange,提问作者Luna
相关产品推荐
相关产品推荐

