I have the following Model:
struct MyModel: Codable, Hashable {
let data: ASubModelData
let error: Bool?
let error_message: String?
let error_code: Int?
}
and I'm trying to create a variable in a view so I can then assign the values to it like this:
#State var myVar: MyModel? = MyModel()
but it's throwing an error:
Insert 'from: <#Decoder#>'
If I hit fix, it would be like:
#State var myVar: MyModel? = MyModel(from: Decoder)
and that's wrong, it also gives an error, how can I create a variable that is an empty instance of that model?
Your struct has no initializer other than the one for decoding. You have to add the initializer init():
init() {
data = ?
error = nil
error_message = nil
error_code = nil
}
However, since MyModel contains properties that are not optional, it would be probably better to initialize it with a nil:
#State var myVar: MyModel?
Related
I've added an Observable Object to my class DetailViewModel, but I am getting an error "Class 'DetailViewModel' has no initializers". Can anyone explain why?
import Foundation
import UIKit
#MainActor
class DetailViewModel: ObservableObject{
#Published var strMeal: String = ""
#Published var strInstructions: String
#Published var strIngredient: String
#Published var strMeasure: String
#Published var strMealThumb:URL?
private func loadMealDetails(idMeal: String) async {
do {
let mealDetailResponse = try await WebServiceRequest().loadData(url: Constants.Urls.getMealByIdUrl(strMeal)) { data in
return try? JSONDecoder().decode(MealDetailModel.self, from:data )
}
} catch {
print(error)
}
}
You have defined some properties (strInstructions, strIngredient, and strMeasure) that don't have initial values specified. Unlike structs, which get synthesized initializers (eg the compiler makes a initializer for us), with a class, we have to create an initializer ourselves (or give all of the properties default values).
With default values, it may look like:
#MainActor
class DetailViewModel: ObservableObject{
#Published var strMeal: String = ""
#Published var strInstructions: String = ""
#Published var strIngredient: String = ""
#Published var strMeasure: String = ""
#Published var strMealThumb:URL?
}
Or, with an initializer, it could be something like:
#MainActor
class DetailViewModel: ObservableObject{
#Published var strMeal: String = ""
#Published var strInstructions: String
#Published var strIngredient: String
#Published var strMeasure: String
#Published var strMealThumb:URL?
init(strInstructions: String, strIngredient: String, strMeasure: String) {
self.strInstructions = strInstructions
self.strIngredient = strIngredient
self.strMeasure = strMeasure
}
}
You may also want to accept values for strMeal and strMealThumb in your initializer -- it's up to you.
I am tinkering around with URLSession and was essentially building a super simple app to load a JSON file into ContentView sort of like a friends list from Facebook and wanted some clarity on not any errors I'm having, but rather, the internal workings of Swift's Codable protocol. Here are some code and explanations:
struct User: Identifiable, Codable {
struct Friend : Identifiable, Codable {
var name : String
var id : String
}
var id : String
var isActive: Bool
var name : String
var age: Int
var company : String
var address : String
var about : String
var registered : String
var friends : [Friend]
var checkIsActive: String {
return self.isActive ? "🟢" :"🔴"
}
}
So to summarize above, I have a User struct which contains a bunch of properties that conform to Codable.
class UsersArrayClass: ObservableObject {
#Published var userArray = [User]()
}
However, I have another class, UsersArrayClass which creates a #Published var userArray of User struct objects. This class conforms to the #ObservableObject protocol, but of course, when I try to make it conform to Codable, it does not like that likely because of the #Published property wrapper being applied on the array itself... This is what is essentially confusing me though if the User struct has Codable conformance, why doesn't userArray which contains User objects automatically conform to Codable as well?
I was thinking that maybe loading all of this up into a Core Data model would solve my problems, but I still can't move on unless I understand what I'm missing out on here, so thanks in advance for any input.
It is hacky, but we can via extension add Codable conformance to Published despite lacking access to Published internals.
extension Published: Codable where Value: Codable {
public func encode(to encoder: Encoder) throws {
guard
let storageValue =
Mirror(reflecting: self).descendant("storage")
.map(Mirror.init)?.children.first?.value,
let value =
storageValue as? Value
??
(storageValue as? Publisher).map(Mirror.init)?
.descendant("subject", "currentValue")
as? Value
else { fatalError("Failed to encode") }
try value.encode(to: encoder)
}
public init(from decoder: Decoder) throws {
self.init(initialValue: try .init(from: decoder))
}
}
Quick check:
class User: ObservableObject, Codable {
#Published var name = "Paul"
}
struct ContentView: View {
#ObservedObject var user = User()
var body: some View {
let data = try? JSONEncoder().encode(user)
let dataFromStr = """
{
"name": "Smith"
}
"""
.data(using: .utf8)
let decoded = try! JSONDecoder().decode(User.self, from: dataFromStr!)
return
VStack{
Text(verbatim: String(data: data!, encoding: .utf8) ?? "encoding failed")
Text(decoded.name)
}
}
}
/*
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 {}
I currently have
struct cellData {
var opened = Bool()
var title = String()
var iconName = String()
var sectionData = [Any]()
}
struct SectionData {
var subTitle: String
var iconName: String
}
And in another function I call:
let test = tableViewData[indexPath.section].sectionData[dataIndex]
print(test)
Which outputs:
SectionData(subTitle: "Terms", iconName: "")
How do I access the subTitle value because doing test.subTitle throws the following error:
Value of type 'Any' has no member 'subTitle'
This is because, in your line var sectionData = [Any](), you have defined the type as Any. So when you access it via tableViewData[indexPath.section], you get back the value as Any.
You should change var sectionData = [Any]() to var sectionData = [SectionData]()
Otherwise, once you get the value from tableViewData[indexPath.section], you can convert to SectionData and then access the value.
Change your sectionData array to an array of SectionData like so: var sectionData = [SectionData](). Once you do this, you'll able to access it by calling: tableViewData[indexPath.section].sectionData[dataIndex].subTitle
I have a protocol which is inherited from codable
protocol OrderItem:Codable {
var amount:Int{get set}
var isPaid:Bool{get set}
}
And a struct conform this protocol
struct ProductItem:OrderItem {
var amount = 0
var isPaid = false
var price = 0.0
}
However when I put this structure into a codable structure, I got errors
struct Order:Codable {
var id:String
var sn:String = ""
var items:[OrderItem] = []
var createdAt:Int64 = 0
var updatedAt:Int64 = 0
}
The errors are
Type 'Order' does not conform to protocol 'Encodable'
Type 'Order' does not conform to protocol 'Decodable'
But if I change items:[OrderItem] to items:[ProductItem] , everything works!
How can I fix this problem?
You cannot do that because a protocol only states what you must do. So when you conform your protocol X to Codable, it only means that any type that conforms to X must also conform to Codable but it won't provide the required implementation. You probably got confused because Codable does not require you to implement anything when all your types already are Codable. If Codable asked you to, say, implement a function called myFunction, your OrderItem would then lack the implementation of that function and the compiler would make you add it.
Here is what you can do instead:
struct Order<T: OrderItem>: Codable {
var id:String
var sn:String = ""
var items: [T] = []
var createdAt:Int64 = 0
var updatedAt:Int64 = 0
}
You now say that items is a generic type that conforms to OrderItem.
It's worth mentioning that if you have a property of an array and the type is a protocol: let arrayProtocol: [MyProtocol] and the array contains multiple types that all conform to MyProtocol, you will have to implement your own init(from decoder: Decoder) throws to get the values and func encode(to encoder: Encoder) throws to encode them.
So for example:
protocol MyProtocol {}
struct FirstType: MyProtocol {}
struct SecondType: MyProtocol {}
struct CustomObject: Codable {
let arrayProtocol: [MyProtocol]
enum CodingKeys: String, CodingKey {
case firstTypeKey
case secondTypeKey
}
}
so our decode will look like this:
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
// FirstType conforms to MyProtocol
let firstTypeArray = try values.decode([FirstType].self, forKey: .firstTypeKey)
// SecondType conforms to MyProtocol
let secondTypeArray = try values.decode([SecondType].self, forKey: .secondTypeKey)
// Our array is finally decoded
self.arrayProtocol: [MyProtocol] = firstTypeArray + secondTypeArray
}
and the same for encoded, we need to cast to the actual type before encoding:
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
let firstActualTypeArray = arrayProtocol.compactMap{$0 as? FirstType}
let secondActualTypeArray = arrayProtocol.compactMap{$0 as? SecondType}
try container.encode(firstActualTypeArray, forKey: .firstTypeKey)
try container.encode(secondActualTypeArray, forKey: .secondTypeKey)
}
Codable is a type alias for Encodable and Decodable.
Therefore if you're implementing it you need to implement the following two functions.
func encode(to: Encoder)
init(from: Decoder)
I have a following struct which defines a shoppingList.
struct ShoppingList {
var shoppingListId :NSNumber
var title :String
}
let shoppingList = ShoppingList(shoppingListId: NSNumber, title: String) // I don't want to assign shoppingListId
Now, the problem is that it auto-generates the constructors which takes in the shoppingListId as parameter. Since shoppingListId is an identity or unique key I don't want to pass in with the constructor. It will be set later by the ShoppingListService after inserting shoppingList into the database.
Is this a place where class will make more sense than structures?
UPDATE:
struct ShoppingList {
var shoppingListId :NSNumber
var title :String
init(title :String) {
self.title = title
// but now I have to assign the shoppingListId here unless I make it NSNumber? nullable
}
}
let shoppingList = ShoppingList(title: "Hello World")
UPDATE 3:
let shoppingList = ShoppingList()
// THE FOLLOWING CODE WILL NOT WORK SINCE IT REQUIRES THE shoppingList to // BE var instead of let
shoppingList.shoppingListId = NSNumber(int: results.intForColumn("shoppingListId"))
shoppingList.title = results.stringForColumn("title")
shoppingLists.append(shoppingList)
If you don't want the default constructor, then make one of your own. However, it is important to keep in mind that unless your property is an Optional, it has to be initialized to some value. In this case, you will have to give shoppingListId a value, or you will need to make it an Optional. Also, I don't see any reason why a class would be better than a struct for this scenario.
struct ShoppingList {
var shoppingListId: NSNumber
var title: String
init(title: String) {
self.title = title
shoppingListId = NSNumber(integer: 0)
}
}
let newList = ShoppingList(title: "Groceries")
Update:
Just saw you updated your question. If you are not able to pick a reasonable initial value for your shoppingListId, then make it an Optional like so:
struct ShoppingList {
var shoppingListId: NSNumber?
var title: String
init(title: String) {
self.title = title
}
}
let newList = ShoppingList(title: "Groceries")
Just realize shoppingListId will be nil till you set it to something, and everywhere you use it you should bind the value in an if let.