I wish to have a UIButton
in a UIViewController
run a function in my ObservableObject
in my SwiftUI app. My needs are larger than this, so I've trimmed out things to this code:
UIViewController & Delegate
protocol TestVCDelegate {
func runTest()
}
class TestVC: UIViewController {
let btnTest = UIButton()
let lblTest = UILabel()
var delegate:TestVCDelegate! = nil
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.red
btnTest.translatesAutoresizingMaskIntoConstraints = false
lblTest.translatesAutoresizingMaskIntoConstraints = false
btnTest.backgroundColor = UIColor.green
btnTest.addTarget(self, action: #selector(runTest), for: .touchUpInside)
view.addSubview(btnTest)
btnTest.heightAnchor.constraint(equalToConstant: 100).isActive = true
btnTest.widthAnchor.constraint(equalToConstant: 100).isActive = true
btnTest.topAnchor.constraint(equalTo: view.topAnchor, constant: 100).isActive = true
btnTest.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 100).isActive = true
view.addSubview(lblTest)
lblTest.heightAnchor.constraint(equalToConstant: 100).isActive = true
lblTest.widthAnchor.constraint(equalToConstant: 100).isActive = true
lblTest.topAnchor.constraint(equalTo: btnTest.bottomAnchor, constant: 10).isActive = true
lblTest.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 100).isActive = true
}
@objc func runTest() {
delegate.runTest()
lblTest.text = "hello world!"
}
}
UIViewControllerRepresentable & Coordinator
struct Take2: UIViewControllerRepresentable {
@EnvironmentObject var model: Model
let testVC = TestVC()
func makeUIViewController(context: Context) -> TestVC {
testVC.delegate = context.coordinator
return testVC
}
func updateUIViewController(_ uiViewController: TestVC, context: Context) {
//
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, TestVCDelegate {
var parent: Take2
init(_ testViewController: Take2) {
parent = testViewController
}
func runTest() {
print("hello world")
}
}
}
Model
class Model : ObservableObject {
var objectWillChange = PassthroughSubject<Void, Never>()
@Published var lblText = "" {
willSet {
objectWillChange.send()
}
}
func runTest() {
print("yet again, hello world")
lblText = "hello world!"
}
}
Every time I try to execute runTest()
in my model I get this error:
Fatal error: Reading EnvironmentObject outside View.body
Now, this error happens whether I try to do this in Take2
, it's Coordinator
, or in my TestVC
. I (mostly) understand the error - if I should try to execute this in some View
via onAppear
it works. BUT - I thought a UIViewControllerRepresentable
is a View
to the SwiftUI
stack (even though it isn't some View
).
I've succeeded in exposing UIKit
properties (both get and set) to the SwiftUI stack. I also have UIKit
delegates (specifically, UIimagepickerController
) delegate successfully updating SwiftUI properties.
But how can I execute a function in an ObservableObject
from a UIKit UIButton
?
View.body
is required. Have you tried indirection - a wrapperView
having the environment object and passing intoTake2
aPassthroughSubject<Void, Never>
? This publisher you can use in the wrapper with.onReceive(publisher)
and call methods on the@EnvironmentObject
whileTake2
just sends on this publisher to signal the function call. I know it's a workaround but any other way is probably may be more complicated. Or you can use a@Published
setter since setter variables are allowed...... / willSet / didSet which can call more. Or can they? – FrederiqueView
- but so far I have something better -Notification
. (Doh!) I want to see if I can successfully apply the Publisher-Subscriber pattern fromCombine
(thus, the mistag) and I'll post my answer here for others within a day. – Persecution