I have a macOS application with SwiftUI. I am saving a dictionary containing two custom classes with NSSavePanel. That's not a problem.
import SwiftUI
struct ContentView: View {
var body: some View {
...
}
private func savePanel() -> URL? {
let savePanel = NSSavePanel()
savePanel.allowedContentTypes = [.myCustomeFileType]
savePanel.canCreateDirectories = true
savePanel.isExtensionHidden = false
savePanel.title = "Saving..."
savePanel.message = "Please select a path where to save a file."
savePanel.nameFieldStringValue = "Untitled"
return savePanel.runModal() == .OK ? savePanel.url : nil
}
private func fileSaveAs() {
if let url = savePanel() {
let models = colorViewModel.frameModels
let borderModel = BorderModel(showBorder: true, colorIndex: 6, borderWeightIndex: 8)
let dict = ["FrameModelArray": models, "BorderModel": borderModel] as [String : Any]
NSKeyedArchiver.setClassName("FrameModel", for: FrameModel.self)
NSKeyedArchiver.setClassName("BorderModel", for: BorderModel.self)
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: dict, requiringSecureCoding: false)
try data.write(to: url, options: .atomic)
} catch {
print("Errrrrr \(error.localizedDescription)")
}
}
}
}
So my custom classes are FrameModel, BorderModel.
I can unarchive a saved file with a deprecated type method as follows.
private func fileOpen() {
if let url = openPanel() {
do {
NSKeyedUnarchiver.setClass(FrameModel.self, forClassName: "FrameModel")
NSKeyedUnarchiver.setClass(BorderModel.self, forClassName: "BorderModel")
let data = try Data(contentsOf: url)
if let someData = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) {
if let dict = someData as? [String : Any] {
if let frameModels = dict["FrameModelArray"] as? [FrameModel] {
print("[FrameModel] read...")
}
if let borderModel = dict["BorderModel"] as? BorderModel {
print("BorderModel read...")
}
}
}
} catch {
print("Errrrrr \(error.localizedDescription)")
}
}
}
If I use unarchivedObject(ofClasses:from:), I can't unarchive my file. What am I doing wrong? Thanks.
private func fileOpen() {
if let url = openPanel() {
do {
NSKeyedUnarchiver.setClass(FrameModel.self, forClassName: "FrameModel")
NSKeyedUnarchiver.setClass(BorderModel.self, forClassName: "BorderModel")
let data = try Data(contentsOf: url)
if let dictionary = try? NSKeyedUnarchiver.unarchivedObject(ofClasses: [FrameModel.self, BorderModel.self], from: data) as? NSDictionary {
print("Being read...")
} else {
print("Not read...")
}
} catch {
print("Errrrrr \(error.localizedDescription)")
}
}
}
Is there a reason you’re doing this with a keyed archive? Keyed archives are very Objective-C aligned, which makes them tricky to use from Swift. In Swift it’s usually much easier to lean into Codable
.
Anyway, with regards your secure coding problem, you need to list all of the classes you expect to see in the archive. So:
try NSKeyedUnarchiver.unarchivedObject(ofClasses: [
NSDictionary.self,
NSArray.self,
NSString.self,
FrameModel.self,
BorderModel.self,
], from: data)
Pasted in below is a small test project that successfully encodes and decodes something like your example.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
import Foundation
class BorderModel: NSObject, NSSecureCoding {
class var supportsSecureCoding: Bool { true }
var showBorder: Bool
init(showBorder: Bool) {
self.showBorder = showBorder
}
func encode(with coder: NSCoder) {
coder.encode(self.showBorder, forKey: "border")
}
required init?(coder: NSCoder) {
self.showBorder = coder.decodeBool(forKey: "border")
}
}
class FrameModel: NSObject, NSSecureCoding {
class var supportsSecureCoding: Bool { true }
var count: Int
init(count: Int) {
self.count = count
}
func encode(with coder: NSCoder) {
coder.encode(self.count, forKey: "count")
}
required init?(coder: NSCoder) {
self.count = coder.decodeInteger(forKey: "count")
}
}
func main() {
let models = [FrameModel(count: 1), FrameModel(count: 2)]
let borderModel = BorderModel(showBorder: true)
let dict = ["FrameModelArray": models, "BorderModel": borderModel] as [String : Any]
let data = try! NSKeyedArchiver.archivedData(withRootObject: dict, requiringSecureCoding: true)
let dict2 = try! NSKeyedUnarchiver.unarchivedObject(ofClasses: [
NSDictionary.self,
NSArray.self,
NSString.self,
FrameModel.self,
BorderModel.self,
], from: data)
print(dict2)
}
main()