So there seems to be a retain cycle when injecting a Binding that is a published property from an ObservableObject into UIViewControllerRepresentable.
It seems if you create a view inside another view in and that second view has an ObservableObject and injects it's published property into the UIViewControllerRepresentable and is then used in the coordinator, the ObservableObject is never released when the original view is refreshed.
Also it looks like the Binding gets completely broken and the UIViewControllerRepresentable no longer works
When looking at it, it makes sense that Coordinator(self) is bad, but Apple's own documentation says to do it this way. Am I missing something?
Here is a quick example:
struct ContentView: View {
@State var resetView: Bool = true
var body: some View {
VStack {
OtherView()
Text("\(resetView ? 1 : 0)")
// This button just changes the state to refresh the view
// Also after this is pressed the UIViewControllerRepresentable no longer works
Button(action: {resetView.toggle()}, label: {
Text("Refresh")
})
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct OtherView: View {
@ObservedObject var viewModel = OtherViewModel()
var body: some View {
VStack {
Text("Value: \(viewModel.value)")
Wrapper(value: $viewModel.value).frame(width: 100, height: 50, alignment: .center)
}
}
}
class OtherViewModel: ObservableObject {
@Published var value: Int
deinit {
print("OtherViewModel deinit") // this never gets called
}
init() {
self.value = 0
}
}
struct Wrapper: UIViewControllerRepresentable {
@Binding var value: Int
class Coordinator: NSObject, ViewControllerDelegate {
var parent: Wrapper
init(_ parent: Wrapper) {
self.parent = parent
}
func buttonTapped() {
// After the original view is refreshed this will no longer work
parent.value += 1
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> ViewController {
let vc = ViewController()
vc.delegate = context.coordinator
return vc
}
func updateUIViewController(_ uiViewController: ViewController, context: Context) {}
}
protocol ViewControllerDelegate: class {
func buttonTapped()
}
class ViewController: UIViewController {
weak var delegate: ViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 20))
button.setTitle("increment", for: .normal)
button.setTitleColor(UIColor.blue, for: .normal)
button.addTarget(self, action: #selector(self.buttonTapped), for: .touchUpInside)
self.view.addSubview(button)
}
@objc func buttonTapped(sender : UIButton) {
delegate?.buttonTapped()
}
}