Search code examples
swiftswiftuicoreml

Swift error in SwiftUI Button action closure: "Cannot use mutating member on immutable value: 'self' is immutable"


Following is the shortened version of the ContentView in my Swift app. The error Cannot use mutating member on immutable value: 'self' is immutable shows up on line self.classifyImage(self.image) inside my Button action closure. How do I set image to be mutable? Or is there a better way to do what I'm trying to accomplish? Essentially I want to pass the UIImage var in my ContentView to be processed by my Vision CoreML model via the classifyImage function I have here.

struct ContentView: View {
    @State private var image = UIImage()

    private lazy var classificationRequest: VNCoreMLRequest = {
        do {
          let model = try VNCoreMLModel(for: SqueezeNet().model)
        
          let request = VNCoreMLRequest(model: model) { request, _ in
              if let classifications =
                request.results as? [VNClassificationObservation] {
                print("Classification results: \(classifications)")
              }
          }
          request.imageCropAndScaleOption = .centerCrop
          return request
        } catch {
          fatalError("Failed to load Vision ML model: \(error)")
        }
    }()

    private mutating func classifyImage(_ image: UIImage) {
        guard let orientation = CGImagePropertyOrientation(
          rawValue: UInt32(image.imageOrientation.rawValue)) else {
          return
        }

        guard let ciImage = CIImage(image: image) else {
          fatalError("Unable to create \(CIImage.self) from \(image).")
        }

        DispatchQueue.global(qos: .userInitiated).async {
          let handler =
            VNImageRequestHandler(ciImage: ciImage, orientation: orientation)
          do {
              try handler.perform([self.classificationRequest])
          } catch {
            print("Failed to perform classification.\n\(error.localizedDescription)")
          }
        }
    }

    var body: some View {
        Button(action: {
            self.classifyImage(self.image).   // <-- error
        }) {
            // Button text here
        }
        // blah blah
    }
}

Solution

  • You cannot mutate View from within itself as struct (so no lazy creations, no mutating func, etc). If you need to change image somewhere, then assign to it directly as to state.

    Here is fixed (compilable) part of code. Tested with Xcode 12.

    struct ContentView: View {
        @State private var image = UIImage()
    
        private let classificationRequest: VNCoreMLRequest = {
            do {
              let model = try VNCoreMLModel(for: SqueezeNet().model)
    
              let request = VNCoreMLRequest(model: model) { request, _ in
                  if let classifications =
                    request.results as? [VNClassificationObservation] {
                    print("Classification results: \(classifications)")
                  }
              }
              request.imageCropAndScaleOption = .centerCrop
              return request
            } catch {
              fatalError("Failed to load Vision ML model: \(error)")
            }
        }()
    
        private func classifyImage(_ image: UIImage) {
            guard let orientation = CGImagePropertyOrientation(
              rawValue: UInt32(image.imageOrientation.rawValue)) else {
              return
            }
    
            guard let ciImage = CIImage(image: image) else {
              fatalError("Unable to create \(CIImage.self) from \(image).")
            }
    
            DispatchQueue.global(qos: .userInitiated).async {
              let handler =
                VNImageRequestHandler(ciImage: ciImage, orientation: orientation)
              do {
                  try handler.perform([self.classificationRequest])
              } catch {
                print("Failed to perform classification.\n\(error.localizedDescription)")
              }
            }
        }
    
        var body: some View {
            Button(action: {
                self.classifyImage(self.image)   // <-- error
            }) {
                // Button text here
            }
            // blah blah
        }
    }