SwiftUI @State 变量未更新
Posted
技术标签:
【中文标题】SwiftUI @State 变量未更新【英文标题】:SwiftUI @State variables aren't updated 【发布时间】:2021-05-01 18:13:24 【问题描述】:我的代码基本上和this question 一样。我遇到的问题是,当tapGesture
事件发生时,工作表显示(调用sheet
代码)但调试显示showUserEditor
为假(在这种情况下,工作表如何显示......?)并且 selectedUserId
仍然是 nil
(因此在打开它时会崩溃......)
观点:
struct UsersView: View
@Environment(\.managedObjectContext)
private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \User.nickname, ascending: true)],
animation: .default)
private var users: FetchedResults<User>
@State private var selectedUserId : NSManagedObjectID? = nil
@State private var showUserEditor = false
var body: some View
NavigationView
List
ForEach(users) user in
UserRowView(user: user)
.onTapGesture
self.selectedUserId = user.objectID
self.showUserEditor = true
.sheet(isPresented: $showUserEditor)
UserEditorView(userId: self.selectedUserId!)
如果您愿意,我可以发布编辑器和行,但它们似乎与问题无关,因为魔术应该发生在视图中。
【问题讨论】:
至少设置selectedUserId
之前设置showUserEditor
为true,换行。
或者使用 sheet
init 和 item
@vadian 我这样做了......这是显而易见的方式,我改变了它并没有回来检查。我将编辑问题以反映这一点。无论如何,据我了解,SwiftUI 重绘周期无关紧要。另外,请注意,当我调试sheet
代码时,即使在打印变量时 showUserEditor 也是错误的,所以我在那里感到困惑......也许我不清楚调试@State 变量时应该寻找什么。
@loremipsum - 找到你的意思:developer.apple.com/documentation/swiftui/view/… 我会试一试。
有一个.sheet
init,您可以在其中将项目作为参数传递。当项目的@State
不是nil
时,它会显示一个工作表developer.apple.com/documentation/swiftui/text/…
【参考方案1】:
所以,我仍然没有弄清楚为什么问题中发布的代码不起作用,使用来自 @loremipsum 的指针我通过使用另一个 .sheet()
方法得到了一个工作代码,该方法采用可选的 Item 和不是布尔标志。代码现在看起来像这样并且可以工作,但如果有人能解释为什么发布的代码不起作用,我会很感激。
struct UsersView: View
@Environment(\.managedObjectContext)
private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \User.nickname, ascending: true)],
animation: .default)
private var users: FetchedResults<User>
@State private var selectedUser : User? = nil
var body: some View
NavigationView
List
ForEach(users) user in
UserRowView(user: user)
.onTapGesture
self.selectedUser = user
.onDelete(perform: deleteItems)
.sheet(item: $selectedUser, onDismiss: nil) user in
UserEditorView(user: user)
【讨论】:
我可以输入答案。并且更好地解释它与在创建工作表时不可变的结构有关已经设置的变量并且由于您正在显示工作表而不会重新加载正文。【参考方案2】:struct == immutable
和 SwiftUI 决定 struct
何时初始化并重新加载
不建议使用依赖于 SwiftUI 在特定时间更新非包装变量的代码。您无法控制此过程。
要使您的第一个设置工作,您需要对变量使用 SwiftUI 包装器
.sheet(isPresented: $showUserEditor)
//struct == immutable SwiftUI wrappers load the entire struct when there are changes
//With your original setup this variable gets created/set when the body is loaded so the orginal value of nil is what is seen in the next View
UserEditorView1(userId: $selectedUserId)
struct UserEditorView1: View
//This is what you orginal View likely looks like it won't work because of the struct being immutable and SwiftUI controlling when the struct is reloaded
//let userId: NSManagedObjectID? <---- Depends on specific reload steps
//To make it work you would use a SwiftUI wrapper so the variable gets updated when SwiftUI descides to update it which is invisible to the user
@Binding var userId: NSManagedObjectID?
//This setup though now requres you to go fetch the object somehow and put it into the View so you can edit it.
//It is unnecessary though because SwiftUI provides the .sheet init with item where the item that is set gets passed directly vs waiting for the SwiftUi update no optionals
var body: some View
Text(userId?.description ?? "nil userId")
您的答案代码不起作用,因为您的参数是可选的并且Binding
不喜欢可选参数
struct UserEditorView2: View
//This is the setup that you posted in the Answer code and it doesn't work becaue of the ? Bindings do not like nil. You have to create wrappers to compensate for this
//But unecessary because all CoreData objects are ObservableObjects so you dont need Binding here the Binding is built-in the object for editing the variables
@Binding var user: User?
var body: some View
TextField("nickname", text: $user.nickname)
现在可以使用易于编辑的 CoreData 对象来处理代码
struct UsersView: View
@Environment(\.managedObjectContext)
private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \User.nickname, ascending: true)],
animation: .default)
private var users: FetchedResults<User>
//Your list view would use the CoreData object to trigger a sheet when the new value is available. When nil there will not be a sheet available for showing
@State private var selectedUser : User? = nil
var body: some View
NavigationView
List
ForEach(users) user in
UserRowView(user: user)
.onTapGesture
self.selectedUser = user
.sheet(item: $selectedUser, onDismiss: nil) user in //This gives you a non-optional user so you don't have to compensate for nil in the next View
UserEditorView3(user: user)
那么工作表中的视图将如下所示
struct UserEditorView3: View
//I mentioned the ObservedObject in my comment
@ObservedObject var user: User
var body: some View
//If your nickname is a String? you have to compensate for that optional but it is much simpler to do it from here
TextField("nickname", text: $user.nickname.bound)
//This comes from another very popular SO question (couldn't find it to quote it) that I could not find and is necessary when CoreData does not let you define a variable as non-optional and you want to use Binding for editing
extension Optional where Wrapped == String
var _bound: String?
get
return self
set
self = newValue
public var bound: String
get
//This just give you an empty String when the variable is nil
return _bound ?? ""
set
_bound = newValue.isEmpty ? nil : newValue
【讨论】:
以上是关于SwiftUI @State 变量未更新的主要内容,如果未能解决你的问题,请参考以下文章
在 SwiftUI 中使用 @State 属性包装器未更新视图
更新@State后未调用SwiftUI UIViewControllerRepresentable.updateUIViewController