AVCaptureDevice rotationCoordinator modifying CALayer on switching devices

I am trying to use AVCaptureDevice.rotationCoordinator API to observe angles for preview and capture and it seems there is an issue with the API when used with arbitrary CALayer (which is not a AVCaptureVideoPreviewLayer) and switching cameras.

Here is my setup. The below function is defined in an actor class called CameraManager that performs setup of rotationCoordinator.

 func updateRotationCoordinator(_ callback:@escaping @MainActor (CGFloat) -> Void) {
        guard let device = sessionConfiguration.activeVideoInput?.device, let displayLayer = displayLayer else { return }
        
        cancellables.removeAll()
        
        rotationCoordinator = AVCaptureDevice.RotationCoordinator(device: device, previewLayer: displayLayer)
        
        guard let coordinator = rotationCoordinator else { return }
        
        coordinator.publisher(for: \.videoRotationAngleForHorizonLevelPreview)
            .receive(on: DispatchQueue.main)
            .sink { degrees in
                let radians = degrees * .pi / 180
                MainActor.assumeIsolated {
                    callback(radians)
                }
                
            }
            .store(in: &cancellables)
    }

This works the very first time but when I switch cameras and call this function again, it throws a runtime error that view's layer is modified from a non-main thread. This happens at the very line where rotation coordinator is been recreated. It's not clear why initialising rotation coordinator should modify CALayer properties right in it's init method.

Modifying properties of a view's layer off the main thread is not allowed: view <MyApp.DisplayLayerView: 0x102ffaf40> with nearest ancestor view controller <_TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier__: 0x101f7fb80>; backtrace: 

(
	0   UIKitCore                           0x0000000194a977b4 575E5140-FA6A-37C2-B00B-A4EACEDFDA53 + 22509492
	1   UIKitCore                           0x000000019358594c 575E5140-FA6A-37C2-B00B-A4EACEDFDA53 + 416076
	2   QuartzCore                          0x00000001927f5bd8 D8E8E86D-85AC-3C90-B2E1-940235ECAA18 + 43992
	3   QuartzCore                          0x00000001927f5a4c D8E8E86D-85AC-3C90-B2E1-940235ECAA18 + 43596
	4   QuartzCore                          0x000000019283a41c D8E8E86D-85AC-3C90-B2E1-940235ECAA18 + 324636
	5   QuartzCore                          0x000000019283a0a8 D8E8E86D-85AC-3C90-B2E1-940235ECAA18 + 323752
	6   AVFCapture                          0x00000001af072a18 09192166-E0B6-346C-B1C2-7C95C3EFF7F7 + 420376
	7   MyApp.debug.dylib              0x0000000105fa3914 $s10MyApp15CapturePipelineC25updateRotationCoordinatoryyy12CoreGraphics7CGFloatVScMYccF + 972
	8   MyApp.debug.dylib              0x00000001063ade40 $s10MyApp11CameraModelC18switchVideoDevicesyyYaFTY3_ + 72
	9   MyApp.debug.dylib              0x0000000105fe3cbd $s10MyApp11ContentViewV4bodyQrvg7SwiftUI6VStackVyAE05TupleE0VyAE6HStackVyAIyAE6SpacerV_AE6ButtonVyAE0E0PAEE5frame5width6height9alignmentQr12CoreGraphics7CGFloatVSg_AyE9AlignmentVtFQOyAqEE11scaledToFitQryFQOyAqEE10imageScaleyQrAE5ImageV0Z0OFQOyA3__Qo__Qo__Qo_GtGG_AmKyAIyAKyAIyAqEE7paddingyQrAE4EdgeO3SetV_AYtFQOyAA07CaptureM0V_Qo__AOyAE4TextVGAmKyAIyA9__AqEEArstUQrAY_AYA_tFQOyAM_Qo_A9_tGGtGG_AmqEE10background_AUQrqd___A_tAePRd__lFQOyAqEEArstUQrAY_AYA_tFQOyA21__Qo__AqEEArstUQrAY_AYA_tFQOyAE06_ShapeE0VyAE9RectangleVAE5ColorVG_Qo_Qo_SgtGGtGGyXEfU0_A42_yXEfU_A10_yXEfU_yyScMYccfU_yyYacfU_TQ1_ + 1
	10  MyApp.debug.dylib              0x0000000105ff06d9 $s10MyApp11ContentViewV4bodyQrvg7SwiftUI6VStackVyAE05TupleE0VyAE6HStackVyAIyAE6SpacerV_AE6ButtonVyAE0E0PAEE5frame5width6height9alignmentQr12CoreGraphics7CGFloatVSg_AyE9AlignmentVtFQOyAqEE11scaledToFitQryFQOyAqEE10imageScaleyQrAE5ImageV0Z0OFQOyA3__Qo__Qo__Qo_GtGG_AmKyAIyAKyAIyAqEE7paddingyQrAE4EdgeO3SetV_AYtFQOyAA07CaptureM0V_Qo__AOyAE4TextVGAmKyAIyA9__AqEEArstUQrAY_AYA_tFQOyAM_Qo_A9_tGGtGG_AmqEE10background_AUQrqd___A_tAePRd__lFQOyAqEEArstUQrAY_AYA_tFQOyA21__Qo__AqEEArstUQrAY_AYA_tFQOyAE06_ShapeE0VyAE9RectangleVAE5ColorVG_Qo_Qo_SgtGGtGGyXEfU0_A42_yXEfU_A10_yXEfU_yyScMYccfU_yyYacfU_TATQ0_ + 1
	11  MyApp.debug.dylib              0x0000000105f9c595 $sxIeAgHr_xs5Error_pIegHrzo_s8SendableRzs5NeverORs_r0_lTRTQ0_ + 1
	12  MyApp.debug.dylib              0x0000000105f9fb3d $sxIeAgHr_xs5Error_pIegHrzo_s8SendableRzs5NeverORs_r0_lTRTATQ0_ + 1
	13  libswift_Concurrency.dylib          0x000000019c49fe39 E15CC6EE-9354-3CE5-AF91-F641CA8283E0 + 433721
)
Answered by DTS Engineer in 824252022

CALayers being shared with UIKit are sensitive to operations off the main thread. I don't think there's anything you can do about that. I suggest trying to move all of these operations to the main thread.

CALayers being shared with UIKit are sensitive to operations off the main thread. I don't think there's anything you can do about that. I suggest trying to move all of these operations to the main thread.

HI testinstadev, We're not sure if rotationCoordinator is an ivar or not. There's a possibility that the rotation coordinator is getting deallocated off the main thread, which could cause a call-out affecting your CALayer. Will you please file a report at feedbackassistant.apple.com and include a small project with the code that reproduces this problem? Thanks.

AVCaptureDevice rotationCoordinator modifying CALayer on switching devices
 
 
Q