How to Conform an Observableobject to the Codable Protocols

How to conform an ObservableObject to the Codable protocols?

Add the init() and encode() methods to your class:

required init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)

id = try values.decode(Int.self, forKey: .id)
name = try values.decode(String.self, forKey: .name)
ingredients = try values.decode([Ingredient].self, forKey: .ingredients)
numberOfPeople = try values.decode(Int.self, forKey: .numberOfPeople)
}

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(name, forKey: .name)
try container.encode(ingredients, forKey: .ingredients)
try container.encode(numberOfPeople, forKey: .numberOfPeople)
}

How to conform a custom object to Codable

Other than not conforming the Skill and SkillButton classes to Codable, the underlying problem you have is down to the @published property wrapper, as this stops the auto-synthesis of Codable conformance.

Rather than writing out the solution, can I refer you to this Hacking with Swift link: https://www.hackingwithswift.com/books/ios-swiftui/adding-codable-conformance-for-published-properties

You will also have to conform Color to Codable yourself as (unless it's changed in recent updates?) it isn't Codable out of the box.

How can I make my model conform to codable and save it locally on every change?

I like very much pawello2222's detailed response. If you continue to develop on iOS, you will eventually need to learn CoreData.

That said, if you don't have time to do that for your current project, personally, I would recommend using RealmSwift and creating Realm Parent, Child and Puppet objects. It would take you an hour to learn how to install and use RealmSwift.

Here's a start:

import RealmSwift

class Puppet: Object {
@objc dynamic var id = UUID()
@objc dynamic var name: String = ""
}

But if you want to save a small number of objects you could use UserDefaults - none of your objects are using unsupported types, so just declare them as codable and let the compiler do the conversion automatically.

Conforming class to codable protocol in swift4

It is quite straightforward. /p>

Codable conformance at @Published array of a codable-conformed Struct

/*
Cannot automatically synthesize 'Encodable' because 'Published<[User]>'
does not conform to 'Encodable' @Published var userArray = [User]()
*/

// Published declaration
@propertyWrapper struct Published<Value> { ... }

Published don't conform to Codable or any common protocol in Foundation currently

Trying to make Published conform to Codeable resulting error below:

/*
Implementation of 'Decodable' cannot be
automatically synthesized in an extension in a different file to the type
*/
extension Published: Codable where Value: Codable {}

Type 'Model' does not conform to protocol 'Decodable'/Encodable

A @Published property with type, let's say, String, are of type Published<String>. And apparently, that type is not Codable.

You can solve this by writing custom encode and decode functions. That's not difficult; it's only some extra lines of code. Please see the documentation on Codable for some examples.

Here is an example for your case:

class TimeModel: Codable, Identifiable, ObservableObject {
@Published var id: UUID = UUID()
@Published var nome : String
@Published var time : String

func aggiornaUI() {
DispatchQueue.main.async {
self.objectWillChange.send()
}
}

init(nome: String, time: String) {
self.nome = nome
self.time = time
}

enum CodingKeys: String, CodingKey {
case id
case nome
case time
}

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(nome, forKey: .nome)
try container.encode(time, forKey: .time)
}

required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(UUID.self, forKey: .id)
nome = try container.decode(String.self, forKey: .nome)
time = try container.decode(String.self, forKey: .time)
}
}

This should work, but I can't really test it because I don't know the rest of your code.

Protocol type cannot conform to protocol because only concrete types can conform to protocols

Rather than protocols use generics.

Declare a simple function

func decodeStickers<T : Decodable>(from data : Data) throws -> T
{
return try JSONDecoder().decode(T.self, from: data)
}

T can be a single object as well as an array.


In your structs drop the Sticker protocol. You can also delete Equatable because it's getting synthesized in structs.

public struct StickerString : Codable {
let fontName: String
let character: String
}

public struct StickerBitmap : Codable {
let imageName: String
}

To decode one of the sticker types annotate the type

let imageStickers = """
[{"imageName":"Foo"},{"imageName":"Bar"}]
"""
let stickerData = Data(imageStickers.utf8)

let recentStickers : [StickerBitmap] = try! decodeStickers(from: stickerData)
print(recentStickers.first?.imageName)

and

let stringSticker = """
{"fontName":"Times","character":"}
"""
let stickerData = Data(stringSticker.utf8)

let sticker : StickerString = try! decodeStickers(from: stickerData)
print(sticker.character)

To decode an array of StickerString and StickerBitmap types declare a wrapper enum with associated values

enum Sticker: Codable {

case string(StickerString)
case image(StickerBitmap)

init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
let stringData = try container.decode(StickerString.self)
self = .string(stringData)
} catch DecodingError.keyNotFound {
let imageData = try container.decode(StickerBitmap.self)
self = .image(imageData)
}
}

func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let string) : try container.encode(string)
case .image(let image) : try container.encode(image)
}
}
}

Then you can decode

let stickers = """
[{"imageName":"Foo"},{"imageName":"Bar"}, {"fontName":"Times","character":"}]
"""

let stickerData = Data(stickers.utf8)
let recentStickers = try! JSONDecoder().decode([Sticker].self, from: stickerData)
print(recentStickers)

In a table view just switch on the enum

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let sticker = stickers[indexPath.row]
switch sticker {
case .string(let stringSticker):
let cell = tableView.dequeueReusableCell(withCellIdentifier: "StringStickerCell", for: indexPath) as! StringStickerCell
// update UI
return cell
case .image(let imageSticker):
let cell = tableView.dequeueReusableCell(withCellIdentifier: "ImageStickerCell", for: indexPath) as! ImageStickerCell
// update UI
return cell
}
}


Related Topics



Leave a reply



Submit