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
}
}