There are other answers on SO about this issue but none seen to solve the problem because in my case I am using a NSViewRepresentable
cocoa view, that is not exactly a SwiftUI view.
I have this code:
struct MyObject: Hashable, Identifiable {
let id: UUID = UUID()
var name, brand: String
init(name: String = "", brand: String = "") {
self.name = varName
self.brand = brand
}
}
final class Model:ObservableObject {
@Published var objects = (0..<3).compactMap {_ in
return MyObject()
}
}
struct ContentView: View {
private let columns = [GridItem(.flexible()), GridItem(.flexible())]
@StateObject private var model = Model()
var body: some View {
VStack{
LazyVGrid(
columns: columns,
alignment: .center,
spacing: 10,
pinnedViews: []
) {
ForEach($model.objects, id:\.id) {$object in
MyTextField($initObject)
}
}
}
.padding(20)
}
}
struct MyTextField: View {
@Binding private var initObject:InitObject
init(_ initObject:Binding<InitObject>) {
_initObject = initObject
}
var body: some View {
AppKitTextField(property: $initObject.name,
placeholder: "name",
fontName:"Avenir-Medium",
fontSize:18) { text in
}
MyAppKitTextField(property: $initObject.type,
placeholder: "type",
fontName:"Avenir-Medium",
fontSize:18)
}
}
The problem is that MyAppKitTextField
is a AppKit NSTextField
NSViewRepresentable
, like this:
public struct AppKitTextField: NSViewRepresentable {
public typealias NSViewType = NSTextField
@Binding var property: String
public init(property: Binding<String>) {
self._property = property
}
public class Coordinator: NSObject, NSTextFieldDelegate {
@Binding private var text: String
init(text: Binding<String>) {
self._text = text
}
}
public func makeNSView(context: NSViewRepresentableContext<AppKitTextField>) -> NSTextField {
let textField = NSTextField()
textField.delegate = context.coordinator
textField.stringValue = property
return textField
}
public func makeCoordinator() -> AppKitTextField.Coordinator {
return Coordinator(text: $property)
}
public func updateNSView(_ nsView: NSTextField, context: Context) {
nsView.stringValue = property
}
}
}
My problem is the line
@Binding var property: String
When I try to use this view I see this error:
Accessing StateObject's object without being installed on a View. This will create a new instance each time.
My first guess is @Binding
property wrapper shouldn't be used in the Coordinator
class since it's not a View
where @Binding
is designed to be used. Instead, you can either use Binding<String>
or just a closure since you only need to set anyway.
My second guess is this mistake:
public func makeCoordinator() -> AppKitTextField.Coordinator {
return Coordinator(text: $property)
}
It should be:
public func makeCoordinator() -> AppKitTextField.Coordinator {
return Coordinator() // make is only called once so there is no point in giving it an old version of the binding.
}
public func updateNSView(_ nsView: NSTextField, context: Context) {
context.coordinator.textDidChange = nil // just to prevent endless updates depending on how your coordinator is designed
nsView.stringValue = property
// must also use the new version of the binding
context.coordinator.textDidChange = { text
self.property = text
}
}
FYI Binding is just a pair of get/set closures, there is no need to give the Binding to the Coordinator because it doesn't need the get, thats the reason for the set only textDidChange
closure which you should invoke from your delegate or action handler.