1

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?

Dennis
  • 53
  • 4
  • `RoomPlanScan` seems to be `Codable`, so I'd suggest to use a Coder (like `JSONDecoder`/`JSONEncoder`, to transform it into `Data` and save it as such with your `NSCoder`, and do the reverse. – Larme Aug 24 '22 at 15:29
  • For your code, I guess `as? worldMap` is `as? ARWorldMap`, right? – Larme Aug 24 '22 at 15:30
  • In practice, it be something like: `let capturedRoomData = try? JSONEncoder().encode(capturedRoom); coder.encode(capturedRoomData, forKey: "capturedRoom")`, and `let captureRoomData = coder.decodeObject(forKey: "capturedRoom") as? Data; let captureRoom = try? JSONDecoder().decode(CaptureRoom.self, data: captureRoomData)` – Larme Aug 24 '22 at 15:32
  • Thanks for the clear explanation! This worked! :D – Dennis Aug 25 '22 at 08:58
  • You're right about the `as? ARWorldMap` part. I edited my question to correct it. – Dennis Aug 25 '22 at 08:59

1 Answers1

0

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.)

Larme
  • 24,190
  • 6
  • 51
  • 81