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())
}
}
自从 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())
}
}