Xcode报错:Instance method ‘setValue(forKey:to:)’ requires that ‘PhotosPickerItem’ conform to ‘PersistentModel’
Xcode报错:Instance method ‘setValue(forKey:to:)’ requires that ‘PhotosPickerItem’ conform to ‘PersistentModel’

Xcode报错:Instance method ‘setValue(forKey:to:)’ requires that ‘PhotosPickerItem’ conform to ‘PersistentModel’

在代码中尝试在SwiftData中存储PhotosPickerItem类型的值:

@Model
class SinglePhoto {
    var String = ""
    var photo: PhotosPickerItem?
    init(String: String = "", photo: PhotosPickerItem) {
        self.String = String
        self.photo = photo
    }
}

预览发现,Xcode报错:

/var/folders/1y/t7s8thm536j0hgvd3fhdp7pr0000gn/T/swift-generated-sources/@__swiftmacro_16hackingwithswift11SinglePhotoC5photo18_PersistedPropertyfMa_.swift:9:21 Instance method 'getValue(forKey:)' requires that 'PhotosPickerItem' conform to 'PersistentModel'

经排查发现,这是因为PhotosPickerItem 类型无法被 SwiftData 存储。SwiftData 要求模型属性必须是可序列化的(即可以持久化到存储中,例如 Core Data)。但是,PhotosPickerItem 是一个临时值类型,设计用于处理照片选择操作,并不是持久化数据的一部分。

解决方案

1、替换 PhotosPickerItem 为可持久化的数据类型

因为 PhotosPickerItem 本身不能被存储,可以改为存储照片的元数据,例如文件路径(String)、照片的二进制数据(Data),或者照片的标识符。

@Model
class SinglePhoto {
    var name: String = "" // 更有意义的属性名
    var photoData: Data? // 用于存储照片的二进制数据

    init(name: String = "", photoData: Data? = nil) {
        self.name = name
        self.photoData = photoData
    }
}

然后,在使用 PhotosPickerItem 时,加载照片数据并存储为 Data:

if let data = try? await photoPickerItem.loadTransferable(type: Data.self) {
    let singlePhoto = SinglePhoto(name: "Example Photo", photoData: data)
    photoGallery.photos.append(singlePhoto)
}

2、使用文件路径存储照片

如果照片文件较大,不建议直接存储 Data,可以将照片保存到磁盘,并存储文件路径:

@Model
class SinglePhoto {
    var name: String = ""
    var photoFilePath: String? // 存储照片文件路径

    init(name: String = "", photoFilePath: String? = nil) {
        self.name = name
        self.photoFilePath = photoFilePath
    }
}

在保存照片时,将其写入本地文件系统:

if let data = try? await photoPickerItem.loadTransferable(type: Data.self) {
    let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent("example.jpg")
    try? data.write(to: fileURL)
    let singlePhoto = SinglePhoto(name: "Example Photo", photoFilePath: fileURL.path)
    photoGallery.photos.append(singlePhoto)
}

在读取时,可以使用 photoFilePath 加载照片:

if let path = singlePhoto.photoFilePath, let imageData = try? Data(contentsOf: URL(fileURLWithPath: path)) {
    let uiImage = UIImage(data: imageData)
    // 使用 uiImage
}

需要注意的是,代码中使用的是temporaryDirectory临时目录,仅适用于临时测试,临时目录中的文件可能会在应用重新启动或系统回收资源时删除,不适合长期保存数据。

同时文件名称可以考虑UUID、时间戳、自定义计时器等形式(在结尾展开说明)。

3、考虑存储 iCloud 兼容的数据

如果需要同步到 iCloud,可以选择以下方式:

存储文件标识符(如 UUID)。

结合 CloudKit,存储照片文件到 iCloud 中,并在 SinglePhoto 中存储照片的标识符或 URL。

完整代码

import SwiftUI
import SwiftData
import Foundation
import PhotosUI

@Model
class PhotoGallery {
    var photos: [SinglePhoto] = []

    init(photos: [SinglePhoto] = []) {
        self.photos = photos
    }
}

@Model
class SinglePhoto {
    var name: String
    var photoData: Data? // 存储照片数据,或改为 photoFilePath: String? 来存储路径

    init(name: String = "", photoData: Data? = nil) {
        self.name = name
        self.photoData = photoData
    }
}

在使用时:

// 从 PhotosPickerItem 获取照片数据并存储
if let data = try? await photoPickerItem.loadTransferable(type: Data.self) {
    let singlePhoto = SinglePhoto(name: "New Photo", photoData: data)
    photoGallery.photos.append(singlePhoto)
}

总结

1、问题来源:PhotosPickerItem 是一个临时值类型,不能被 SwiftData 持久化。

2、解决方案:使用持久化的数据类型替代,例如 Data(照片数据)或 String(文件路径)。

3、更好的设计:对于大文件,推荐使用文件路径存储以节省内存;对于 iCloud,同步照片标识符或 URL。

扩展知识

区分多个文件名称

如果需要保存多个图片,文件名不能固定为 “example.jpg”,否则每次保存都会覆盖之前的文件。为了区分每个文件,可以使用以下方法生成唯一的文件名:

方法 1:使用 UUID

UUID 是一种常用的方式,用于生成唯一的字符串。你可以用它作为文件名,确保不会重复。

let uniqueFileName = UUID().uuidString + ".jpg"
let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent(uniqueFileName)
try? data.write(to: fileURL)

优点:保证文件名唯一。

缺点:文件名较长且不易读。

方法 2:基于时间戳

可以使用当前的时间戳(精确到秒或毫秒)作为文件名。

let timestamp = Date().timeIntervalSince1970
let uniqueFileName = "\(timestamp).jpg"
let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent(uniqueFileName)
try? data.write(to: fileURL)

优点:文件名可读且有一定规律。

缺点:如果多张图片同时保存(同一秒内),可能会有冲突。

方法 3:使用自定义计数器

可以维护一个计数器(如保存在内存或持久化存储中),每次保存时递增计数器以生成新文件名。

var imageCounter = UserDefaults.standard.integer(forKey: "imageCounter") // 从 UserDefaults 获取计数
imageCounter += 1
UserDefaults.standard.set(imageCounter, forKey: "imageCounter") // 更新计数器

let uniqueFileName = "image_\(imageCounter).jpg"
let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent(uniqueFileName)
try? data.write(to: fileURL)

优点:文件名简单且有序。

缺点:需要维护计数器状态。

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注