I'm using MultipeerConnectivity to share SCNNodes position in a multiuser AR session.
When I archive (with NSKeyedArchiver.archivedData(withRootObject: someARNode, requiringSecureCoding: true) )
And unarchive (with if let node = try NSKeyedUnarchiver.unarchivedObject(ofClass:SCNNode.self, from: data) {)
Everything works fine, but now I'm trying to send a custom Object like this:
struct ARUser: Codable {
var idUser : String
var position: [Double]
}
When I try to unarchive the object received with the NSKeyedUnarchiver.unarchivedObject it let me error.
if let node = try NSKeyedUnarchiver.unarchivedObject(ofClass:ARUser.self, from: data) {...}
I get the syntax error: Incorrect argument label in call (have 'ofClass:from:', expected 'ofClasses:from:')
But if I change the function as suggested by the compiler:
if let node = try NSKeyedUnarchiver.unarchivedObject(ofClasses:[ARUser.self], from: data) {..}
I get the next syntax error: Cannot convert value of type 'ARUser.Type' to expected element type 'AnyObject.Type'
So, the question here is, what's the correct way to unarchive custom Objects?
Since here you use Codable
struct ARUser: Codable {
Then
do {
let dataToObject = try JSONDecoder().decode(ARUser.self,from:data)
let objectToData = try JSONEncoder().encode(dataToObject)
}
catch {
print(error)
}
NSKeyedUnarchiver is an old Objective-C stuff
Related
Thanks for your help. I need interaction with Toml files in my macOS Swift application. I am using the TOMLDecoder library to parse the Toml format. The library works by specifying a Swift struct type that conforms to Codable, and have the library create the object for us. From the docs:
struct Discography: Codable {
struct Album: Codable {
let name: String
struct Song: Codable {
let name: String
}
let songs: [Song]
}
let albums: [Album]
}
If we take a sample Toml file:
[[albums]]
name = "Born to Run"
[[albums.songs]]
name = "Jungleland"
[[albums.songs]]
name = "Meeting Across the River"
[[albums]]
name = "Born in the USA"
[[albums.songs]]
name = "Glory Days"
[[albums.songs]]
name = "Dancing in the Dark"
We can parse it with:
let tomlData = try? Data(contentsOf: URL(fileURLWithPath: "/path/to/file"))
let discography = try? TOMLDecoder().decode(Discography.self, from: tomlData)
Here comes my question. The library does not provide a way to reverse the process, so to serialize back the object, so I would like to write that on my own, and, possibly, I would like to achieve a solution in clean Swift, if I understand correctly, by the use of the T type, thus allowing any kind of Codable conforming object to be serializable. The decode function in the library is:
public func decode<T: Decodable>(_ type: T.Type, from text: String) throws -> T {
let topLevel: Any
do {
topLevel = try TOMLDeserializer.tomlTable(with: text)
} catch {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid TOML.", underlyingError: error))
}
let decoder = TOMLDecoderImpl(referencing: self, options: self.options)
guard let value = try decoder.unbox(topLevel, as: type) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value."))
}
return value
}
I have started to write my encode function like the following:
class TOMLEncoder: TOMLDecoder {
func encode<T>(sourceObject: T) -> String {
return "Sample serialized text..."
}
}
I really don't know how to proceed... from my very limited knowledge I should iterate somehow on the sourceObject properties and create the TOML file from the contents of those properties, but I am not sure if that is the correct approach and how to achieve it. Any help is greatly appreciated. Thanks
I have a struct array, for example:
struct Note {
let id: Int
let text: String
let timestamp: Int64
}
I want to send to the widget. As I searched, I can send an array through AppGroup with userDefaults, I need some tricks to send the struct array with it, but I need the its model on the widget side as well, which I don't have access to it.
Now, I want to know what is the best way to do that? convert it to Json and send via FileManager and encode it again on the widget side? Or use CoreData? or any other suggestion?
Thank you so much for your help in advance.
Assume you already set up your AppGroup; if not, check out this article Sharing data with a Widget
First, make your struct Codable:
struct Note: Codable {
let id: Int
let text: String
let timestamp: Int64
}
You could put your Struct file in a package and add it as your framework so you can use it in both your main app and your widget. Or you can add the file target membership on both your main app and your widget extension.
Then convert it to JSON, write to the File via FileManager, sample code: How to read files created by the app by iOS WidgetKit?
//Init your Note array
//Add your code here <-
let encoder = JSONEncoder()
do {
let jsonData = try encoder.encode(/*your note array*/)
// Write to the file system, you can follow the article above or make your own.
// Add your code here <-
WidgetCenter.shared.reloadTimelines(ofKind: "/*Your widget kind*/")
} catch {
Log.error("Save widget error: \(error.localizedDescription)")
}
Lastly, Decode in your widget timeline or a separate function:
if let jsonData = //Read your saved JSON file {
let decoder = JSONDecoder()
do {
let model = try decoder.decode([Note].self, from: jsonData)
// Do whatever you need to do with your model
} catch {
Log.error("Read widget error: \(error.localizedDescription)")
//Should display an error view
}
} else {
//Should display an error view
}
What I want to do:
I want to get an array from UserDefaults that I saved beforehand and append a custom object to it. Afterwards I want to encode it as a Data-type again and set this as the UserDefaults Key again.
My problem:
The encoding part is what is not working as intended for me.
It says: -[__SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x60000011a540
But I do not know how to fix this.
Below is my code for more context:
do {
let decoded = defaults.object(forKey: "ExArray") as! Data
var exo = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(decoded) as! [Exerc]
exo.append(datas[indexPath.row])
let enco = try NSKeyedArchiver.archivedData(withRootObject: exo, requiringSecureCoding: false) <- Here is the error
defaults.set(enco, forKey: "ExArray")
} catch {
print("Error encoding custom object NOSEARCHO")
}
This is how Exerc looks:
struct Exerc: Codable {
var title: String
var exID: String
}
Seems like you are not using the archiver features, so why don't you just use the codable?
do {
let key = "ExArray"
let decoded = defaults.data(forKey: key)!
var exo = try JSONDecoder().decode([Exerc].self, from: decoded)
exo.append(datas[indexPath.row])
let enco = try JSONEncoder().encode(exo)
defaults.set(enco, forKey: key)
} catch {
print("Error encoding/decoding custom object NOSEARCHO", error)
}
It just a simple refactored MVP of the original code, but you can even work a bit on this and make it human readable right in the plist file!
I am attempting to decode my JSON response data from type: AnyObject? back into something that can be printed out in the console / interacted with.
reading back the data, before decoding prints projectName.GameData
Here is the breakdown, data comes back from the response as type: Any? Because it sent up as
class GameData : Codable {
var isPlayerOneTurn: Bool!
var wasCreated: Bool!
var playerOne: String!
var playerTwo: String!
var board: [[Int]]!
init() {
}
}
The current error I am getting when attempting to decode is Cannot convert value of type 'GameData' to expected argument type 'Data'
code :
let decoder = JSONDecoder()
let dataTest = try? decoder.decode(GameData.self, from: data)
Am I missing a correct init() method on the GameData class?
UPDATE:
data was changed to type Data here: thank you #rmaddy for the comment pointing this out.
let data = data as? Data
let decoder = JSONDecoder()
let dataTest = try? decoder.decode(GameData.self, from: data!)
print("data: \(String(describing: dataTest))")
the print line still shows data: Optional(projectName.GameData)
What is wrong here still, not allowing me to view the values of the class GameData?
The print line mentioned in the question, was the value of the game object decode.. That was all XCode would print out - the name of the original object before decoding. Using dataTest.myValue worked when accessing data from the object.
I am parsing a response that needs to be transformed from a dictionary on the server (which is a legacy data format) - to simply an array of strings on the client side. Therefore I am wanting to decode the key called 'data' as a dictionary, so i can iterate through the keys and create an array of strings on the client side.
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
do {
let some_data_dictionary = try values.decode([String:Any].self, forKey: CodingKeys.data)
for (kind, values) in some_data_dictionary {
self.data_array.append(kind)
}
} catch {
print("we could not get 'data' as [String:Any] in legacy data \(error.localizedDescription)")
}
}
The error I am getting is: Ambiguous reference to member 'decode(_:forKey:)'
Looks like Swift 'Codable' cant support Any or use of [String:Any], so using this post here Swift 4 decodable nested json with random key attributes
I was able to make a struct for a class I wouldn't use called LegacyData, and then unpack the keys into an array of strings
do
{
let legacy_data = try values.decode([String:LegacyData].self, forKey: CodingKeys.data)
self.array = Array(legacy_data.keys)
}
catch
{
print("no legacy_data \(error) \n")
}