I have the following scenario:
a SwiftUI view (very simple) which I need to use as inputAccessoryView for a UIKit UITextField. By tapping some buttons in the SwiftUI view, I need to update the backgroundColor of the UITextField.
I approached in this way. When I becomeFirstResponder the textField, I initialize the SwiftUI view embedded in a UIHostingController. The UIHostingController's view, is a subview of a UIInput view, which I use as inputAccessoryView for the textfield. In order to detect taps on the SwiftUI view and then update the textField's backgroundColor, I'm using an ObservableObject which has a Published String property. This string is the "identifier" of the color I use to update the textField's background color.
So, I have the UIInputView. I'm passing it the ObservableObject and inject it into the SwiftUIView as ObservedObject. After the initialization of these things, I observe the changes of the Published String property by using a simple AnyCancellable. I'm doing this in the method which triggers the textField with the becomeFirstResponder(). Everything works fine, but there's a problem.
After the dismissing of the textField, the keyboard and the related UIInputView are gone. By opening the memory graph I don't see any instance of the UIInputView. But I see an instance of the ObservableObject and an instance of the SwiftUI view. I always have +1 in memory of the ObservableObject and the SwiftUI view for each becomeFirstResponder() and resignFirstResponder() I call on the textField. I'm struggling on this and I don't understand what I'm doing wrong. I paste some pieces of code...
func triggerTextField() {
let viewModel = MyViewModel(...)
let inputAccessoryView = MyInputView(viewModel: viewModel)
self.textField.inputAccessoryView = inputAccessoryView
self.textField.becomeFirstResponder()
cancellable = viewModel.$myColorString.sink { [weak self] newValue in
self?.textField.backgroundColor = UIColor(newValue)
}
}
class MyViewModel: ObservableObject {
Published var myValue = String()
...
}
class MyInputView: UIInputView {
init(viewModel: MyViewModel) {
super.init(frame: .zero, inputViewStyle: .keyboard)
let mySwiftUIView = MySwiftUIView(viewModel: viewModel!)
let hostingController = UIHostingController(rootView: mySwiftUIView)
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(hostingController.view)
NSLayoutConstraint.activate([
hostingController.view.topAnchor.constraint(equalTo: self.topAnchor),
hostingController.view.leadingAnchor.constraint(equalTo: self.leadingAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: self.trailingAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
}
}
struct MySwiftUIView: View {
ObservedObject var viewModel: MyViewModel
var body: some View {
HStack {
ForEach(viewModel.values, id: \.self) { value in
MyView(value: $viewModel.myValue)
.onTapGesture {
viewModel.myValue = value
}
}
}
.padding(12.0)
.frame(height: 56.0)
.frame(maxWidth: .infinity)
}
}
I'd like to destroy from memory both the ViewModel and the SwiftUIView when I resignFirstResponder() the textField. Actually in the memory graph I see an instance of the viewModel which seems to have a strong reference to the SwiftUIView (rootView of the UIHostingController) and the Published string ?! I don't know why... I also have an instance of the SwiftUIView. I tried to use the StateObject instead of the ObservedObject: nothing. I tried to move the initialization of the ViewModel in the viewDidLoad of the UIViewController in which I have the textField: nothing. Any idea or suggestion?