SwiftUI Button操作关闭中的Swift错误:“不能在不可变值上使用变异成员:'self'是不可变的”

时间:2020-08-13 03:55:04

标签: swift swiftui coreml

以下是我的Swift应用程序中ContentView的缩写。错误Cannot use mutating member on immutable value: 'self' is immutable出现在我的Button动作关闭内的第self.classifyImage(self.image)行上。如何将image设置为可变的?还是有更好的方法来完成我要完成的工作?本质上,我想通过这里的UIImage函数在ContentView中传递classifyImage变量,由Vision CoreML模型处理。

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
    }
}

2 个答案:

答案 0 :(得分:1)

您不能从自身内部将View更改为struct(因此,不能创建任何延迟的创建,更改功能等)。如果您需要在某个地方更改image,请直接将其指定为状态。

此处是固定的(可编译)代码部分。经过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
    }
}

答案 1 :(得分:0)

问题是您正在处理结构。在结构上更改值在语义上与为其分配新值相同。因此,当通过var contentView = ContentView()定义ContentView结构时,该功能将起作用。如果使用let contentView = ContentView(),则会出现错误。区别在于,通过使用mutating功能swift为图像分配新值会自动创建新的Struct并将其分配给var contentView = ...

另一种更好的方法是,如果您使用这样的ViewModel:

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
    }
}

然后您可以像这样在视图中使用模型:

struct ContentView: View {
    @ObservedObject private var viewModel = ContentViewModel(UIImage())

    var body: some View {
        Button(action: {
            self.viewModel.classifyImage()
        }) {
            // Button text here
        }
        // blah blah
    }
}

由于您将逻辑封装到视图模型中,并且不会用处理代码污染视图,因此这是一种更清洁的方法。