How to fix Call to main actor-isolated initializer 'init(frame:)' in a synchronous nonisolated context in Apple AVCam app? porting to swift 6

I am testing some swift 6 migrations in xcode 16.1 beta and I am trying to migrate the Apple CamAV app that was created in wwdc24 to swift 6 but I don't know how to fix this error in the init.

Errors:

Call to main actor-isolated initializer 'init(frame:)' in a synchronous nonisolated context; this is an error in the Swift 6 language mode

For all imageView properties.
Main actor-isolated property 'contentMode' can not be mutated from a nonisolated context; this is an error in the Swift 6 language mode

class PreviewView: UIView, PreviewTarget {

    init() {
        super.init(frame: .zero)
#if targetEnvironment(simulator)
        // The capture APIs require running on a real device. If running
        // in Simulator, display a static image to represent the video feed.
        let imageView = UIImageView(frame: UIScreen.main.bounds)
        imageView.image = UIImage(named: "video_mode")
        imageView.contentMode = .scaleAspectFill
        imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        addSubview(imageView)
#endif
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // Use the preview layer as the view's backing layer.
    override class var layerClass: AnyClass {
        AVCaptureVideoPreviewLayer.self
    }
    
    var previewLayer: AVCaptureVideoPreviewLayer {
        layer as! AVCaptureVideoPreviewLayer
    }
    
    nonisolated func setSession(_ session: AVCaptureSession) {
        // Connects the session with the preview layer, which allows the layer
        // to provide a live view of the captured content.
        Task { @MainActor in
            previewLayer.session = session
        }
    }
}
3 Likes

Came here while doing the same thing, this compilation error feels very weird even tho the reason is kinda clear (NSObject empty init is not mainActor-isolated) and reproducible for any CocoaView subclass.

Strict checks in swift6 with no ability to locally disable them gonna make me insane :clown_face:

I feel this meme on every level of my soul
photo_2024-09-29 8.56.43 PM

Tho this thing seems to compile :new_moon_with_face:

@MainActor
class PreviewView: UIView, CameraPreviewTarget {
  var onCaptureDevicePointTap: ((CGPoint) -> Void)?

  override class var layerClass: AnyClass {
    AVCaptureVideoPreviewLayer.self
  }

  var previewLayer: AVCaptureVideoPreviewLayer {
    layer as! AVCaptureVideoPreviewLayer
  }

  convenience init() {
    self.init()
  }

  @MainActor
  @_disfavoredOverload
  init(_ void: Void = ()) {
    super.init(frame: .zero)
    #if targetEnvironment(simulator)
      // The capture APIs require running on a real device. If running
      // in Simulator, display a static image to represent the video feed.
      let imageView = UIImageView(frame: UIScreen.main.bounds)
      imageView.image = UIImage.resource(.photoMode)
      imageView.contentMode = .scaleAspectFill
      imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
      addSubview(imageView)
    #endif

    addGestureRecognizer(
      UITapGestureRecognizer(
        target: self,
        action: #selector(handleTap)
      ))
  }

  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  nonisolated func setSession(_ session: AVCaptureSession) {
    let sendable = UncheckedSendable(session)
    Task { @MainActor in
      previewLayer.session = sendable.wrappedValue
    }
  }

  @objc private func handleTap(from recognizer: UITapGestureRecognizer) {
    let captureDevicePoint = previewLayer.captureDevicePointConverted(
      fromLayerPoint: recognizer.location(in: self)
    )

    onCaptureDevicePointTap?(captureDevicePoint)
  }
}

Upd:
Yes it's compiling, because it recoursively calls itself :clown_face:
Simply remove empty init and provide default value for init(frame:)

override init(frame: CGRect = .zero) {
  super.init(frame: frame)
  #if targetEnvironment(simulator)
  // The capture APIs require running on a real device. If running
  // in Simulator, display a static image to represent the video feed.
  let imageView = UIImageView(frame: UIScreen.main.bounds)
  imageView.image = UIImage.resource(.photoMode)
  imageView.contentMode = .scaleAspectFill
  imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
  addSubview(imageView)
  #endif

  addGestureRecognizer(UITapGestureRecognizer(
    target: self,
    action: #selector(handleTap)
  ))
}