ContentView 重绘未触发 modalView 中的更改

ContentView redraw from change in modalView not triggered

自从 swiftUI 2.0 出现以来,我一直无法根据在另一个模态呈现的视图(设置视图)中所做的更改来更新视图。

我在主 ContentView 上显示一个字符串,该字符串从 SettingsView 上的分段选择器值派生其内容。 问题在于用户更改设置并丢弃 SettingsView 后,ContentView 中的字符串没有更新。正文未重绘。

我正在使用@ObservableObject 和@StateObject,所以对它的每次更改都应该触发重绘,但我无法让它工作...

我创建了一个符合 ObservableObject 协议的 class:AppState 我正在使用 class 尝试传递数据,更重要的是 - 视图之间的数据 changes 以便根据用户的设置重绘我的 ContentView。 为了实例化这个 class,我在我的 AppDelegate 文件中注册了一个 UserDefaults。

我还将 Combine Framework 导入到我的项目中,并在每个文件中添加了 import Combine 行!

为了说明问题,我尽可能地简化了我的代码,所以下面的内容可能看起来有点曲折,但它是从一个更复杂的应用程序派生出来的,对此感到抱歉。

这是我的 ContentView 代码:

import SwiftUI
import Combine


struct ContentView: View {
   
   @StateObject var appState: AppState
   
   @State var modalViewCaller = 0 // used to present correct modalView
   @State var modalIsPresented = false // to present the modal views
   
   var body: some View {
       
       let stringArray = generateString() // func to generate string according to user's pref
       let recapString = stringArray[0]
       
       return ZStack {
               NavigationView {
                   VStack {
 // MARK: -  texts :
                   VStack {
                   Text(recapString)
                       .bold()
                       .multilineTextAlignment(/*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)

                   } // end of VStack
                       .padding()
                       .overlay(RoundedRectangle(cornerRadius: 10)
                       .stroke(Color(UIColor.systemBlue), lineWidth: 4))
                       .padding()
                           } // END of VStack
           .onAppear() {
               self.modalViewCaller = 0
               print("\n\n*********** Content View onAppear triggered ! ************\n")
           }
           .navigationBarTitle("DataFun", displayMode: .inline)
           .navigationBarItems(leading: (
               Button(action: {
                   self.modalViewCaller = 1 // SettingsView
                   self.modalIsPresented = true
               }
                   ) {
                       Image(systemName: "gear")
                           .imageScale(.large)
                    }
           ))
       } // END of NavigationView
               .onAppear() {
                   self.appState.updateValues()
               }
       } // End of ZStack
       .sheet(isPresented: $modalIsPresented) {
           sheetContent(modalViewCaller: $modalViewCaller, appState: AppState())
               }
       .navigationViewStyle(StackNavigationViewStyle())
   }
   
   // MARK: - struct sheetContent() :
     
     struct sheetContent: View {
         @Binding var modalViewCaller: Int // Binding to the @State modalViewCaller variable from ContentView
         @StateObject var appState: AppState
         
         var body: some View {
           if modalViewCaller == 1 { // The settings view is called
               SettingsView(appState: AppState())
                     .navigationViewStyle(StackNavigationViewStyle())
                     .onDisappear { self.modalViewCaller = 0 }
             } else if modalViewCaller == 2 { // the "other view" is called
                     OtherView()
                     .navigationViewStyle(StackNavigationViewStyle())
                     .onDisappear { self.modalViewCaller = 0 }
             }
         }
     } // END of func sheetContent
   
   // MARK: - generateString()
 func generateString() -> [String] {
       
       var recapString = "" // The recap string
       var myArray = [""]
   
       // We create the recap string :
   if UserDefaults.standard.integer(forKey: "rules selection") == 0 { // ICAO
             recapString = "User chose LEFT"
   } else if UserDefaults.standard.integer(forKey: "rules selection") == 1 { // AF Rules
           recapString = "User chose RIGHT"
       }
   
   myArray = [recapString]
   
           return myArray
   } // End of func generateString()
}

struct ContentView_Previews: PreviewProvider {
   static var previews: some View {
       ContentView(appState: AppState())
   }
}

这是我的 AppState 代码:

import Foundation
import SwiftUI
import Combine

class AppState: ObservableObject {

   @Published var rulesSelection: Int = UserDefaults.standard.integer(forKey: "rules selection")
   
   func updateValues() { // When the user changes a setting, the UserDefault is updated. Here, we align the AppState's value with what is now in the UserDefaults
       self.rulesSelection = UserDefaults.standard.integer(forKey: "rules selection")
       print("\nappState value (ruleSelection) updated from Appstate class func \"updateValues")
   }
}

这是我的 SettingsView 代码:

import SwiftUI
import Combine


struct SettingsView: View {
   
   @Environment(\.presentationMode) var presentationMode // in order to dismiss the Sheet

   @StateObject var appState: AppState
   @State private var rulesSelection = UserDefaults.standard.integer(forKey: "rules selection") // 0 is LEFT, 1 is RIGHT

   var body: some View {
       NavigationView {
           VStack {
               Spacer()
       Text("Choose a setting below")
           .padding()
       Picker("", selection: $rulesSelection) {
                   Text("LEFT").tag(0)
                   Text("RIGHT").tag(1)
               }
       .pickerStyle(SegmentedPickerStyle())
       .padding()
               Spacer()
           }
           .navigationBarItems(
               leading:
               Button("Done") {
                       self.saveDefaults() // We set the UserDefaults
                       self.presentationMode.wrappedValue.dismiss() // This dismisses the view
                //   self.modalViewCaller = 0
               }
           ) // END of NavBarItems
       } // END of NavigationBiew
   } // END of body
   
   func saveDefaults() {
       
       UserDefaults.standard.set(rulesSelection, forKey: "rules selection")
       
       self.appState.updateValues() // This is a func from the AppState class that will align the appState's value to the UserDefaults
       
   }
}

struct SettingsView_Previews: PreviewProvider {
   static var previews: some View {
       SettingsView(appState: AppState())
   }
}

还有一个工作项目,如果有人有时间检查这个“实时”的话:

https://github.com/Esowes/dataFun

感谢您的指点。

此致。

嗯...它...简而言之有很多变化,所以这里是完整的 ContentView.swift 修复。

注意:你只需要一个StateObject,并在其中设置一个实例,并且你需要在视图中发布属性个可观察对象,否则不会刷新,并且UserDefaults中的更改不会刷新视图直到您使用 AppStorage 等

验证 Xcode 12.1 / iOS 14.1

import SwiftUI
import Combine


struct ContentView: View {
    
    @StateObject var appState: AppState
    
    @State var modalViewCaller = 0 // used to present correct modalView
    @State var modalIsPresented = false // to present the modal views
    
    var body: some View {
        
        return ZStack {
                NavigationView {
                    VStack {
  // MARK: -  texts :
                    VStack {
                            RecapStringView(appState: appState)

                    } // end of VStack
                        .padding()
                        .overlay(RoundedRectangle(cornerRadius: 10)
                        .stroke(Color(UIColor.systemBlue), lineWidth: 4))
                        .padding()
                            } // END of VStack
            .onAppear() {
                self.modalViewCaller = 0
                print("\n\n*********** Content View onAppear triggered ! ************\n")
            }
            .navigationBarTitle("DataFun", displayMode: .inline)
            .navigationBarItems(leading: (
                Button(action: {
                    self.modalViewCaller = 1 // SettingsView
                    self.modalIsPresented = true
                }
                    ) {
                        Image(systemName: "gear")
                            .imageScale(.large)
                     }
            ))
        } // END of NavigationView
                .onAppear() {
                    self.appState.updateValues()
                }
        } // End of ZStack
        .sheet(isPresented: $modalIsPresented) {
            sheetContent(modalViewCaller: $modalViewCaller, appState: appState)
                }
        .navigationViewStyle(StackNavigationViewStyle())
    }
    
    // MARK: - struct sheetContent() :
      
      struct sheetContent: View {
          @Binding var modalViewCaller: Int // Binding to the @State modalViewCaller variable from ContentView
          @ObservedObject var appState: AppState
          
          var body: some View {
            if modalViewCaller == 1 { // The settings view is called
                SettingsView(appState: appState)
                      .navigationViewStyle(StackNavigationViewStyle())
                      .onDisappear { self.modalViewCaller = 0 }
              } else if modalViewCaller == 2 { // the "other view" is called
                      OtherView()
                      .navigationViewStyle(StackNavigationViewStyle())
                      .onDisappear { self.modalViewCaller = 0 }
              }
          }
      } // END of func sheetContent
}

struct RecapStringView: View {
    @ObservedObject var appState: AppState
    var body: some View {
        Text("User chose " + "\(appState.rulesSelection == 0 ? "LEFT" : "RIGHT")")
        .bold()
        .multilineTextAlignment(.center)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(appState: AppState())
    }
}