Reputation: 1006
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
}
}
Upvotes: 2
Views: 1923
Reputation: 257709
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
}
}
Upvotes: 1
Reputation: 434
The problem is that you are dealing with a struct. Changing a value inside a struct is semantically the same as assigning a new value to it. So the function would work when your ContentView struct is defined via var contentView = ContentView()
. If you use let contentView = ContentView()
than you get your error. The difference is that by assigning a new value to your image with the mutating function swift automatically creates a new Struct and assigns it to var contentView = ...
.
Another better approach would be if you would use a ViewModel like this:
import Combine
import UIKit
....
class ContentViewModel: ObservableObject {
var image: uiImage
@Published var classification: String
init(_ image: UIImage) {
self.image = image
}
func classifyImage() {
// classify your image and assign the result to the published var classification. This way your view will be automatically updated on a change
}
}
Then you could use the model in your View like this:
struct ContentView: View {
@ObservedObject private var viewModel = ContentViewModel(UIImage())
var body: some View {
Button(action: {
self.viewModel.classifyImage()
}) {
// Button text here
}
// blah blah
}
}
This is a much cleaner approach due to the fact that you are encapsulate your logic into a viewmodel and do not pollute the view with processing code.
Upvotes: 0