Modern way of CMTime and CMTimeRange in plist using Swift

575 views Asked by At

What are the most 'modern' Swift ways to store objects like CMTime and CMTimeRange in plist? I have tried the following approaches. The dictionary object gets stored in plist.

  dictionary["timeRange"] =  NSValue(timeRange: timeRange)

and also,

  dictionary["timeRange"] = CMTimeRangeCopyAsDictionary(timeRange, allocator: kCFAllocatorDefault)

The problem with the first approach as someone pointed out is NSValue is more of Objective-C kind of stuff and it needs to be archived before it can be stored in a plist. The second approach is a dictionary based approach where timeRange is stored as CFDictionary but is compatible with plist without any need to archive. But it is CFDictionary that is even farther away from Swift!

So what's a modern way of making CMTimeRange property list serialisable?

1

There are 1 answers

12
Dávid Pásztor On BEST ANSWER

Use PropertyListEncoder/PropertyListDecoder with a Codable model type. CMTime and CMTimeRange are not Codable conformant by default, so you need to add the conformance yourself.

extension CMTime: Codable {
    enum CodingKeys: String, CodingKey {
        case value
        case timescale
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let value = try container.decode(CMTimeValue.self, forKey: .value)
        let timescale = try container.decode(CMTimeScale.self, forKey: .timescale)
        self.init(value: value, timescale: timescale)
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(value, forKey: .value)
        try container.encode(timescale, forKey: .timescale)
    }
}

extension CMTimeRange: Codable {
    enum CodingKeys: String, CodingKey {
        case start
        case duration
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let start = try container.decode(CMTime.self, forKey: .start)
        let duration = try container.decode(CMTime.self, forKey: .duration)
        self.init(start: start, duration: duration)
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(start, forKey: .start)
        try container.encode(duration, forKey: .duration)
    }
}

struct Model: Codable {
    let time: CMTime
    let timeRange: CMTimeRange
}

let model = Model(time: CMTime.invalid, timeRange: CMTimeRange(start: CMTime(), end: CMTime()))
do {
    let encodedData = try PropertyListEncoder().encode(model)
    let decodedData = try PropertyListDecoder().decode(Model.self, from: encodedData)
    print(decodedData)
} catch {
    print(error)
}