How to save a CapturedRoom using NSCoder

552 Views Asked by At

I'm trying to build an app that creates a floor plan of a room. I used ARWorldMap with ARPlaneAnchors for this but I recently discovered the Beta version of the RoomPlan API, which seems to lead to far better results.

However, I used te be able to just save an ARWorldMap using the NSCoding protocol, but this throws an error when I try to encode a CapturedRoom object: -[__SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x141c18110

My code for encoding the class containing the CapturedRoom:

import RoomPlan

class RoomPlanScan: NSObject, NSCoding {
    
    var capturedRoom: CapturedRoom
    var title: String
    var notes: String
    
    init(capturedRoom: CapturedRoom, title: String, notes: String) {
        self.capturedRoom = capturedRoom
        self.title = title
        self.notes = notes
    }
    
    required convenience init?(coder: NSCoder) {
        guard let capturedRoom = coder.decodeObject(forKey: "capturedRoom") as? CapturedRoom,
              let title = coder.decodeObject(forKey: "title") as? String,
              let notes = coder.decodeObject(forKey: "notes") as? String
        else { return nil }
        
        self.init(
            capturedRoom: capturedRoom,
            title: title,
            notes: notes
        )
    }
    
    func encode(with coder: NSCoder) {
        coder.encode(capturedRoom, forKey: "capturedRoom")
        coder.encode(title, forKey: "title")
        coder.encode(notes, forKey: "notes")
    }
    
}

To be clear, the following code does work:

import RoomPlan

class RoomPlanScan: NSObject, NSCoding {
    
    var worldMap: ARWorldMap
    var title: String
    var notes: String
    
    init(worldMap: ARWorldMap, title: String, notes: String) {
        self.worldMap = worldMap
        self.title = title
        self.notes = notes
    }
    
    required convenience init?(coder: NSCoder) {
        guard let capturedRoom = coder.decodeObject(forKey: "worldMap") as? ARWorldMap,
              let title = coder.decodeObject(forKey: "title") as? String,
              let notes = coder.decodeObject(forKey: "notes") as? String
        else { return nil }
        
        self.init(
            worldMap: worldMap,
            title: title,
            notes: notes
        )
    }
    
    func encode(with coder: NSCoder) {
        coder.encode(worldMap, forKey: "worldMap")
        coder.encode(title, forKey: "title")
        coder.encode(notes, forKey: "notes")
    }
    
}

I'm writing the object to a local file using NSKeyedArchiver so it would be nice if I could keep the same structure using NSCoder. How can I fix this and save a CapturedRoom?

1

There are 1 best solutions below

0
Larme On BEST ANSWER

The issue is about saving CaptureRoom. According to the doc, it's not NS(Secure)Coding compliant, but it conforms to Decodable, Encodable, and Sendable

So you can use an Encoder/Decoder, to do CaptureRoom <-> Data, you could use the bridge NSData/Data, since NSData is NS(Secure)Coding compliant.

So, it could be something like the following code. I'll use JSONEncoder/JSONDecoder as partial Encoder/Decoder because they are quite common.

  • Encoding:
let capturedRoomData = try! JSONEncoder().encode(capturedRoom) as NSData
coder.encode(capturedRoomData, forKey: "capturedRoom")
  • Decoding:
let captureRoomData = coder.decodeObject(forKey: "capturedRoom") as! Data
let captureRoom = try! JSONDecoder().decode(CaptureRoom.self, data: captureRoomData)

Side note:
I used force unwrap (use of !) to simplify the code logic, but of course, you can use do/try/catch, guard let, if let, etc.)