如何让AppStorage变量变更仅下次App启动生效,不触发当前视图重跑
问题原因
@AppStorage 是 SwiftUI 自带的响应式属性包装器,本质是对 UserDefaults 的封装,会自动监听对应键值的变更,只要值发生修改,就会触发所在 View 的 body 重新计算渲染,所以你在注册页修改isLoggedIn等变量后,DispatcherView 会立刻响应变更刷新视图,导致当前导航栈的注册页被替换。
解决方案
核心思路是启动时只读取一次配置值存入本地状态,本次运行的渲染逻辑完全依赖本地状态,不响应@AppStorage的变更,修改后的值会在下次启动读取时生效,有两种实现方式可选:
方案1:新增本地状态缓存启动值(原有逻辑改动最小)
保留原有的@AppStorage定义,新增一组@State变量用来存储启动时读取的初始值,后续渲染判断全部使用本地@State变量,不再直接依赖@AppStorage变量。
修改后的代码示例:
struct DispatcherView: View { // 保留原@AppStorage定义,仅用于启动时读取初始值 @AppStorage("isLoggedIn") var isLoggedIn = false @AppStorage("lastLoggedEmail") var lastLoggedEmail = "" @AppStorage("lastLoggedUserType") var lastLoggedUserType = "" @AppStorage("lastLoggedUserPassword") var lastLoggedUserPassword = "" // 新增本地状态,本次运行的渲染逻辑完全依赖这组值,不会自动更新 @State private var localIsLoggedIn: Bool = false @State private var localLastLoggedEmail: String = "" @State private var localLastLoggedUserType: String = "" @State private var localLastLoggedUserPassword: String = "" @State var isActive: Bool = false @State var waitTime: Bool = false var body: some View { VStack{ if self.isActive{ // 所有判断逻辑替换为本地状态变量 if localIsLoggedIn == false{ StartUpView() .onAppear(perform: { UNUserNotificationCenter.current().requestAuthorization(options: [.alert,.sound,.badge]) { (_,_) in } }) } else{ if localLastLoggedUserType == "Driver"{ DriverContentView(email: $localLastLoggedEmail, userType: $localLastLoggedUserType) .onAppear(perform: { Auth.auth().signIn(withEmail: localLastLoggedEmail, password: localLastLoggedUserPassword) { (res, err) in if err != nil{print(err as Any)} } UNUserNotificationCenter.current().requestAuthorization(options: [.alert,.sound,.badge]) { (_,_) in } }) } else if localLastLoggedUserType == "Admin"{ AdminContentView(email: $localLastLoggedEmail, userType: $localLastLoggedUserType) .onAppear(perform: { Auth.auth().signIn(withEmail: localLastLoggedEmail, password: localLastLoggedUserPassword) { (res, err) in if err != nil{print("ERROR signing user back in\(String(describing: err))")} } UNUserNotificationCenter.current().requestAuthorization(options: [.alert,.sound,.badge]) { (_,_) in } }) } } } else{ VStack{ Spacer() Image("splashScreen") .resizable() .scaledToFit() .animation(waitTime == false ? .none : .spring()) Spacer() } .background(Color.white) .edgesIgnoringSafeArea(.all) } } .onAppear(){ // 启动时一次性读取所有配置到本地状态,后续不再更新 localIsLoggedIn = isLoggedIn localLastLoggedEmail = lastLoggedEmail localLastLoggedUserType = lastLoggedUserType localLastLoggedUserPassword = lastLoggedUserPassword DispatchQueue.main.asyncAfter(deadline: .now()+2) { withAnimation{ self.isActive = true } } DispatchQueue.main.asyncAfter(deadline: .now()+1) { waitTime = true } } } }
注册页的赋值逻辑不需要修改,修改后的值会存入UserDefaults,下次启动时会被读取到本地状态生效。
方案2:直接读取UserDefaults(更简洁)
因为DispatcherView不需要响应配置变更,也不需要在这个页面修改配置,可以直接去掉@AppStorage属性包装器,启动时直接从UserDefaults读取值存入@State即可,完全避免自动监听:
struct DispatcherView: View { // 直接从UserDefaults读取初始值存入本地状态,无自动监听 @State private var localIsLoggedIn: Bool = UserDefaults.standard.bool(forKey: "isLoggedIn") @State private var localLastLoggedEmail: String = UserDefaults.standard.string(forKey: "lastLoggedEmail") ?? "" @State private var localLastLoggedUserType: String = UserDefaults.standard.string(forKey: "lastLoggedUserType") ?? "" @State private var localLastLoggedUserPassword: String = UserDefaults.standard.string(forKey: "lastLoggedUserPassword") ?? "" @State var isActive: Bool = false @State var waitTime: Bool = false // 剩余body和onAppear逻辑和方案1一致,不需要再给本地变量赋值 }
内容的提问来源于stack exchange,提问作者Mohamed Elsayed
相关产品推荐
相关产品推荐

