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

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重绘导致失效

需求

  1. 仅在AddItemView显示时,点击TabView的TabItem弹出类似现有“Close the window alert”的弹窗,阻止直接返回ListView;
  2. 实现类似Apple Mail的功能:无需关闭AddItemView即可临时显示ListView,返回时保留AddItemView中@State存储的输入数据(关闭视图会重置这些值)。曾考虑用@AppStorage单独存储各变量,但因变量过多不具可行性,寻求更优方案。

解决方案

针对需求1:拦截TabItem点击并弹出确认弹窗

核心思路是通过全局状态跟踪AddItemView的显示状态,并自定义TabItem的点击行为,替代系统默认切换逻辑。

  1. 创建全局状态管理类:
class AppState: ObservableObject {
    @Published var isAddItemViewPresented = false
    @Published var showTabSwitchAlert = false
}
  1. 自定义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
        }
    }
}
  1. 在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)
    }
}
  1. 修改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中,避免视图销毁时丢失数据。

  1. 创建AddItem的ViewModel:
class AddItemViewModel: ObservableObject {
    // 所有输入字段用@Published替代@State
    @Published var title = ""
    @Published var content = ""
    // 其他业务字段...
    
    // 可选:重置状态方法
    func reset() {
        title = ""
        content = ""
    }
}
  1. 在全局AppState中注入ViewModel:
class AppState: ObservableObject {
    @Published var isAddItemViewPresented = false
    @Published var showTabSwitchAlert = false
    // 全局共享,确保状态不丢失
    let addItemViewModel = AddItemViewModel()
}
  1. 修改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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 10:04:58