A UIViewControllerRepresentable receives an image from a ViewController. This image should be made available to the SwiftUI view.
The ObservableObject:
class CurrentImage: ObservableObject {
var didChange = PassthroughSubject<Void, Never>()
@Published var photoTaken: UIImage? = nil {didSet{didChange.send()}}
}
UIViewControllerRepresentable:
struct CameraPreviewView: UIViewControllerRepresentable {
var currentImage: CurrentImage
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject {
let parent: CameraPreviewView
init(_ cameraPreviewController: CameraPreviewView) {
self.parent = cameraPreviewController
super.init()
// Notification receives a copy of the image taken from the ViewController:
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "imageDidChange"), object: nil, queue: OperationQueue.main){(notification) in
if let image = notification.userInfo?["image"] as? UIImage {
// *****ERROR: self.parent.videoStatus.photoTaken is not accessible
self.parent.currentImage.photoTaken = image
}
}
}
}
func makeUIViewController(context: UIViewControllerRepresentableContext<CameraPreviewView>) -> CameraViewController {
let cameraViewController = CameraViewController()
return cameraViewController
}
// View is not updating so we don't need this
func updateUIViewController(_ cameraViewController: CameraViewController, context: UIViewControllerRepresentableContext<CameraPreviewView>) {
}
}
The ObservableObject is 'not accessible' and so I can't set the image within it from the Coordinator. Am I overlooking something simple here?
UPDATE 2: Passing value to an ObservableObject still not working
I've struggled with this and tried a dozen combinations of approaches. But to simplify the issue, we have a Coordinator with a var representing an ObservableObject. In this case, I'm going to keep it simple and try to set the value of a string.
The Coordinator:
struct CameraPreviewView: UIViewControllerRepresentable {
var cameraPhoto = CurrentPhoto()
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject {
let parent: CameraPreviewView
init(_ cameraPreviewController: CameraPreviewView) {
self.parent = cameraPreviewController
super.init()
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "imageDidChange"), object: nil, queue: OperationQueue.main){(notification) in
print("notice received")
self.parent.cameraPhoto.testString = "This changed"
}
}
}
}...
The ObservableObject:
class CurrentPhoto: ObservableObject {
@Published var testString = "Testing string"
}
In order to test this, I also tried the following:
class CurrentPhoto: ObservableObject {
let objectWillChange = ObservableObjectPublisher()
var testString = "Testing String" {
willSet {
print("set string")
objectWillChange.send()
}
}
}
In the second example, the willSet method DOES get called. (It prints "set string") however the value remains "Testing String".
The ObservableObject IS being called from the Controller but the value does not set or change.
Therefore the view doesn't receive a new value:
struct ContentView: View {
@ObservedObject var cameraState = CurrentPhoto()
var body: some View {
VStack{
Text("test this \(cameraState.testString)")
...
NOTE: passing a value from the VIEW to the ObservableObject works, so the issue is somehow connected to how the Coordinator passes value to an ObservedObject.
The following works and passes a value from the View to the OO:
Button(action : {
self.cameraState.testString = "from view"
}, label : {
Text("Change string")
}
This doesn't entirely solve the problem of how to bind a Coordinator to an ObservableObject. However, this did achieve the intended goal, which was to return the image from the Coordinator to SwiftUI.
The view:
struct ContentView: View {
@State private var photoInView: Image?
@State var isRecording = true
var body: some View {
VStack{
photoInView?
.resizable()
.scaledToFit()
ZStack (alignment: .bottom) {
// Start up the CameraPreview and this will 2-way bind.
CameraPreviewView(isRecording: $isRecording, photoReceivedFromCoordinator: $photoInView)
....
And then in the UIViewRepresentable:
struct CameraPreviewView: UIViewControllerRepresentable {
@Binding var isRecording: Bool
@Binding var photoReceivedFromCoordinator: Image?
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject {
// Setting the coordinator to parent allows the coordinator to use the @Binding
let parent: CameraPreviewView
init(_ cameraPreviewController: CameraPreviewView) {
self.parent = cameraPreviewController
super.init()
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "imageDidChange"), object: nil, queue: OperationQueue.main){(notification) in
if let uiImage = notification.userInfo?["image"] as? UIImage {
// We need an Image not a UI Image, so first we convert it
let image = Image(uiImage: uiImage)
// Assign the value to the parent's binding value
self.parent.photoReceivedFromCoordinator = image
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With