这是 SwiftUI 的预期行为吗?我可能对 SwiftUI 还不够了解,但它看起来非常违反直觉,所以我想我应该问一下。
这是一个最小的例子:
@main
struct SwiftUI_exampleApp: App {
@State private var myString: String = "Initial value"
var body: some Scene {
WindowGroup {
VStack {
MySwiftUIView(myString: $myString)
Text("Click me!")
.onTapGesture {
myString = "Set from Text.onTapGesture"
}
}
}
}
}
struct MySwiftUIView: NSViewRepresentable {
@Binding var myString: String
func makeNSView(context: Context) -> MyNSView {
let myView = MyNSView(myString: $myString)
return myView
}
func updateNSView(_ nsView: MyNSView, context: Context) {
// this. If something like this line is not included, updateNSView is not called when myString changes
let _ = myString
nsView.setNeedsDisplay(NSRect(x: 0, y: 0, width: nsView.frame.width, height: nsView.frame.height))
}
}
class MyNSView: NSView {
@Binding var myString: String
init(myString: Binding<String>) {
self._myString = myString
super.init(frame: NSRect.zero)
}
override func draw(_ dirtyRect: NSRect) {
myString.draw(at: CGPoint(x: 0, y: 0))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
我的猜测是,SwiftUI“智能”地检测 updateNSView() 内部使用了哪些绑定变量,并且仅在其中一个变量发生更改时才调用它;然而,在像上面例子这样间接使用变量的情况下(即在 NSView.draw() 内部,在 nsView.setNeedsDisplay() 调用之后调用),它不够智能,无法正确检测,并且 updateNSView() 是跳过。
我有这个权利吗?那条
let _ = myString
线有点可怕,我想相信这不是最好/正确的方法。
我的猜测是,SwiftUI“智能”地检测内部使用了哪些绑定变量
,并且仅在其中一个变量发生更改时才调用它updateNSView()
这基本上是正确的。
这里的错误是你不应该在
@Binding
子类中有 NSView
。
class MyNSView: NSView {
var myString: String
init(myString: String) {
self.myString = myString
super.init(frame: NSRect.zero)
}
...
我假设您在
@Binding
中使用了 MyNSView
,因为您希望这将使 MyNSView.myString
与 MySwiftUIView.myString
“同步”。这是不正确的,因为 @Binding
在“SwiftUI 世界”之外不起作用。
保持这些同步正是
updateNSView
的目的。您应该在 MyNSView.myString
中更新 updateNSView
。
NSViewRepresentable
的实现应该如下所示:
struct MySwiftUIView: NSViewRepresentable {
@Binding var myString: String
func makeNSView(context: Context) -> MyNSView {
let myView = MyNSView(myString: $myString)
return myView
}
func updateNSView(_ nsView: MyNSView, context: Context) {
// Note this line:
nsView.myString = myString
// This is how you keep the myString in MyNSView synchronised with myString in MySwiftUIView
nsView.setNeedsDisplay(nsView.bounds)
}
}
如果您不打算更改
myString
中的 MySwiftUIView
,您甚至不需要 @Binding
中的 MySwiftUIView
。只需将 myString
声明为 let
常量即可。