You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift 6中调用PHPhotoLibrary.performChanges保存图片崩溃问题

解决Swift 6中使用PHPhotoLibrary保存图片时出现的IOSurfaceClientSetSurfaceNotify failed e00002c7错误

问题描述

原本在Swift 5中正常运行的相册保存代码,切换到Swift 6后点击保存按钮时触发错误提示:

IOSurfaceClientSetSurfaceNotify failed e00002c7

原代码如下:

import SwiftUI
import Photos

struct ContentView: View {
    var body: some View {
        VStack {
            Color.blue
                .frame(width: 100, height: 100)

            Button("Save") {
                if let imageData = ImageRenderer(content: self.body).uiImage?.pngData() {
                    saveImageToPhotoLibrary(imageData: imageData)
                }
            }
        }
    }

    private func saveImageToPhotoLibrary(imageData: Data) {
        Task {
            let status = await PHPhotoLibrary.requestAuthorization(for: .addOnly)
            guard status == .authorized else { return }

            try await PHPhotoLibrary.shared().performChanges {
                let request = PHAssetCreationRequest.forAsset()
                request.addResource(with: .photo, data: imageData, options: nil)
            }
        }
    }
}

问题原因

这个错误源于Swift 6对并发和UI线程访问的严格控制——代码中直接将self.body传入ImageRenderer,而body是View的计算属性,在Swift 6的并发环境下,跨线程访问UI相关资源会触发底层IOSurface的异常。

解决方案

方案1:单独提取目标渲染视图

将需要保存的视图部分单独抽离,避免直接引用整个View的body:

import SwiftUI
import Photos

struct ContentView: View {
    // 单独定义要保存的目标视图
    private let saveTargetView: some View = Color.blue
        .frame(width: 100, height: 100)
    
    var body: some View {
        VStack {
            saveTargetView

            Button("Save") {
                // 渲染单独的目标视图而非整个body
                if let imageData = ImageRenderer(content: saveTargetView).uiImage?.pngData() {
                    saveImageToPhotoLibrary(imageData: imageData)
                }
            }
        }
    }

    private func saveImageToPhotoLibrary(imageData: Data) {
        Task {
            let status = await PHPhotoLibrary.requestAuthorization(for: .addOnly)
            guard status == .authorized else { return }

            try await PHPhotoLibrary.shared().performChanges {
                let request = PHAssetCreationRequest.forAsset()
                request.addResource(with: .photo, data: imageData, options: nil)
            }
        }
    }
}

方案2:强制渲染操作在主线程执行

通过自定义ImageRenderer的渲染闭包,确保UI绘制操作在主线程完成:

import SwiftUI
import Photos

struct ContentView: View {
    var body: some View {
        VStack {
            Color.blue
                .frame(width: 100, height: 100)

            Button("Save") {
                let renderer = ImageRenderer(content: self.body)
                // 指定渲染闭包,强制在主线程执行绘制
                renderer.renderer = .init { size, context in
                    DispatchQueue.main.sync {
                        self.body.draw(in: CGRect(origin: .zero, size: size), with: context)
                    }
                }
                if let imageData = renderer.uiImage?.pngData() {
                    saveImageToPhotoLibrary(imageData: imageData)
                }
            }
        }
    }

    private func saveImageToPhotoLibrary(imageData: Data) {
        Task {
            let status = await PHPhotoLibrary.requestAuthorization(for: .addOnly)
            guard status == .authorized else { return }

            try await PHPhotoLibrary.shared().performChanges {
                let request = PHAssetCreationRequest.forAsset()
                request.addResource(with: .photo, data: imageData, options: nil)
            }
        }
    }
}

补充说明

Swift 6强化了UI线程的访问限制,任何涉及UI绘制、资源访问的操作都必须在主线程执行。直接在非主线程访问View的body会导致底层IOSurface资源的冲突,通过上述两种方式确保UI渲染操作的线程安全性,即可解决该错误。

内容的提问来源于stack exchange,提问作者YRUsoDiao

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 04:42:35