swiftuiuiimageviewurlsessionmjpeg

@Published UIImage property not updating in SwiftUI


I want to show a MJPEG stream in a UIImageView.

I have implemented the stream loading and send the images to a CameraViewModel via a delegate.

final class CameraViewModel: ObservableObject {
    
    lazy var cameraService = CameraService(delegate: self)
    
    @Published var cameraImage = UIImage(systemName: "cloud.heavyrain.fill")
    
    func play() {
        cameraService.play()
    }
    
    func stop() {
        cameraService.stop()
    }
}

extension CameraViewModel: CameraServiceDelegateProtocol {
    func frame(image: UIImage) {
        cameraImage = image
        print("updated image \(self.cameraImage!)")
    } 
}

The delegate function gets called with every new image as expected and I'm assigning it to @Published var cameraImage.

On the View side the @Published property is used to populate a UIImageView, but the image is not updating.


struct CameraView: View {
    @Binding var showCamera: Bool
    @ObservedObject var cameraViewModel: CameraViewModel = CameraViewModel()
    var body: some View {
        VStack {
            Image(uiImage: cameraViewModel.cameraImage!).aspectRatio(contentMode: .fit).frame(height: 200)
            
            Button("Close") {
                cameraViewModel.stop()
                showCamera.toggle()
            }.onAppear() {
                cameraViewModel.play()
            }
        }
    }
}

The Image(....) in the VStack is not updating and I can't seem to figure out whats wrong.

For reference, here is the CameraService:

protocol CameraServiceDelegateProtocol {
    func frame(image: UIImage) -> Void
}

protocol CameraServiceProtocol {
    var rosServiceDelegate: CameraServiceDelegateProtocol { get set }
}

class CameraService: NSObject, ObservableObject {
    var cameraServiceDelegate: CameraServiceDelegateProtocol
    let realUrl = URL(string: "http://192.168.45.100:8080/stream?topic=/image_raw")
    var dataTask: URLSessionDataTask?
    var receivedData: NSMutableData = NSMutableData()
    var session: URLSession?
    
    init(delegate: CameraServiceDelegateProtocol) {
        cameraServiceDelegate = delegate
    }
    
    func play() {
        session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)
        dataTask = session?.dataTask(with: realUrl!)
        dataTask?.resume()
    }
    
    func stop() {
        dataTask?.cancel()
    }
}

extension CameraService: URLSessionDataDelegate {
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
        if self.receivedData.length > 0,
            let receivedImage = UIImage(data: self.receivedData as Data) {
                
            DispatchQueue.main.async {
                self.cameraServiceDelegate.frame(image: receivedImage)
            }
                
            self.receivedData = NSMutableData()
        }
            
        completionHandler(URLSession.ResponseDisposition.allow) //.Cancel,If you want to stop the download
            
    }
        
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        self.receivedData.append(data)
    }
}


Solution

  • In CameraView replace

    @ObservedObject var cameraViewModel 
    

    with

    @StateObject var cameraViewModel