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

如何让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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 13:35:27