如何让SwiftData存储与读取CGPoint、CGSize、CGRect等CG结构体?
SwiftData 存储 CGSize/CGRect/CGPoint 等 Core Graphics 结构体的解决方案
我尝试用SwiftData存储CGSize、CGRect、CGPoint这类Core Graphics基础结构体,但一直失败。CGVector和CGAffineTransform应该也存在同类问题,不过尚未测试。
核心问题:有没有简洁可靠的方法让SwiftData支持CG结构体的存储与读取?
当前问题分析
所有CG结构体都实现了Codable协议(遵循Encodable和Decodable),但它们采用的是无键编码(unkeyed encoding),直接在@Model类中使用这类属性会触发崩溃,报错信息为:
Fatal error: Composite Coder only supports Keyed Container
测试代码示例:
@Model class TestCGSize { // 运行时触发上述崩溃 var cgSize: CGSize = CGSize.zero init(cgSize: CGSize) { self.cgSize = cgSize } }
补充说明:这个错误描述并不完全准确——CGFloat同样采用无键编码,但能正常在SwiftData中使用。
已尝试的方案及问题
- 编写Codable扩展:无法覆盖CG结构体已有的Encodable/Decodable实现
- 编写子类:结构体不支持继承,此路不通
- 使用@propertyWrapper:触发编译器错误
Property wrapper cannot be applied to a computed property,推测SwiftData会将所有属性转为计算属性 - 使用didSet观察者:仅单向生效。SwiftData会在数据库中创建列并存储值,但读取数据时无法同步更新CG结构体属性,测试代码如下:
@Model class TestCGSize { @Transient var cgSize: CGSize = CGSize.zero { didSet { cgSizeWidth = cgSize.width cgSizeHeight = cgSize.height } } // 数据库中会创建列并存储数据,但读取时cgSize不会更新 var cgSizeWidth: Double = 0 { didSet { cgSize.width = cgSizeWidth } } var cgSizeHeight: Double = 0 { willSet { cgSize.height = newValue } } init(cgSizeWidth: Double, cgSizeHeight: Double) { self.cgSizeWidth = cgSizeWidth self.cgSizeHeight = cgSizeHeight self.cgSize.width = cgSizeWidth self.cgSize.height = cgSizeHeight } }
- 编写包装结构体:刚启动尝试,需要参考示例
可行解决方案:包装结构体适配键值编码
创建遵循Codable的包装结构体,将CG结构体的属性转为键值对形式编码,完美兼容SwiftData的要求。以CGSize为例:
1. 编写CGSize包装结构体
struct CGSizeWrapper: Codable { let size: CGSize // 自定义解码逻辑,从键值容器中读取宽高 init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let width = try container.decode(CGFloat.self, forKey: .width) let height = try container.decode(CGFloat.self, forKey: .height) size = CGSize(width: width, height: height) } // 自定义编码逻辑,将宽高写入键值容器 func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(size.width, forKey: .width) try container.encode(size.height, forKey: .height) } enum CodingKeys: String, CodingKey { case width, height } } // 扩展CGSize,便捷生成Wrapper extension CGSize { var wrapper: CGSizeWrapper { CGSizeWrapper(size: self) } }
2. 在@Model类中使用包装结构体
@Model class TestCGSize { var sizeWrapper: CGSizeWrapper // 提供便捷的CGSize访问属性,内部自动转换Wrapper var cgSize: CGSize { get { sizeWrapper.size } set { sizeWrapper = newValue.wrapper } } init(cgSize: CGSize) { self.sizeWrapper = cgSize.wrapper } }
3. 扩展到其他CG结构体
对于CGRect、CGPoint等其他CG结构体,只需按照相同逻辑编写对应的Wrapper即可:
- CGPointWrapper:包含x、y两个键值
- CGRectWrapper:包含x、y、width、height四个键值
这种方式既不会修改CG结构体的原有实现,又能让SwiftData正确处理存储与读取,是目前最可靠的解决方案。
内容的提问来源于stack exchange,提问作者HighFive
相关产品推荐
相关产品推荐

