rookie here, this may be a simple question but lets say I have a struct that looks like this:
struct User: Codable, Identifiable {
let id: String
let firstname: String
let lastname: String
let age: Int
}
This is the basic user model, but when i create a new user i basically need the same struct with no id:
struct UserCreate: Codable {
let firstname: String
let lastname: String
let age: Int
}
Seems quite verbose to create two structs. Is there a better pattern for this sort of thing?
Thanks
maybe this is what you want?
struct UserWithID: Codable, Identifiable {
let id: String
struct User: Codable {
let firstname: String
let lastname: String
let age: Int
}
}
You can set id as an optional and use other object in User
struct User: Codable, Identifiable {
let id: String?
let userCreate: UserCreate
}
struct UserCreate: Codable {
let firstname: String
let lastname: String
let age: Int
}
You can just set id as an optional and use this object
struct User: Codable, Identifiable {
let id: String?
let firstname: String
let lastname: String
let age: Int
}
Just give a default value for id.
let id = UUID().uuidString
Related
Can I show a sheet using a String?
Please let me know if there's a way around it.
#State var selectedContactInfo: String?
.sheet(item: $selectedContactInfo){ item in MessageView(recipient: selectedContactInfo!) }
I'm receiving this error:
Instance method 'sheet(item:onDismiss:content:)' requires that
'String' conform to 'Identifiable'
String could become Identifiable
Its id could be self because String is Hashable
extension String: Identifiable {
public var id: String {self}
}
I have a User object
#objc(User)
public class User: NSManagedObject {
#NSManaged public var firstname: String
#NSManaged public var lastname: String
#NSManaged public var country: String
#NSManaged public var friends: NSSet // of User objects
var full: String {
firstname + " " + lastname
}
var friendsArray: [User] {
friends.allObjects as? [User] ?? []
}
}
and at some point I want to map a large array of users (80k objects) to an array of View models
struct ItemViewModel: Hashable {
let id: UUID
let friendsName: [String]
}
Without lazy it takes a long time, so I have opted for the usag of lazy:
func prepareViewModel(users: [User]) -> [ItemViewModel] {
users.map { user in
let friendsName = user.friendsArray.lazy.filter{["en", "fr"].contains($0.country)}.map(\.full)
return ItemViewModel(id: UUID(), friendsName: friendsName)
}
}
But I get an error:
Cannot convert value of type 'LazyMapSequence<LazyFilterSequence<LazySequence<[User]>.Elements>.Elements, String>'
(aka 'LazyMapSequence<LazyFilterSequence<Array<User>>, String>') to expected argument type '[String]'
It makes sense because now the friends names array will be processed lazily later. I have tried to convert the view model struct to hold:
struct ItemViewModel: Hashable {
let id: UUID
let friendsName: LazyMapSequence<LazyFilterSequence<[User]>, String>
}
But now it's not Hashable is there a way to keep the auto-conformance to Hashable when using LazyMapSequence<LazyFilterSequence<[User]>, String> as type for ItemViewModel and any tips on how to improve performance of logic
I am trying to parse the below JSON response, which has multiple dynamic keys,
{
"Nagaland": {
"districtData": {
"Dimapur": {
"confirmed": 1,
"lastupdatedtime": "",
"delta": {
"confirmed": 0
}
}
}
},
"Meghalaya": {
"districtData": {
"East Khasi Hills": {
"confirmed": 1,
"lastupdatedtime": "",
"delta": {
"confirmed": 0
}
}
}
}
}
I have written my Codable struct like below,,
struct IndianStateListModel: Codable {
// MARK: Properties
let state: [String: StateData]
}
struct StateData: Codable {
// MARK: Properties
var districtData: Inner?
/// Mapping Key Enum
private enum CodingKeys: String, CodingKey {
case districtData
}
}
struct Inner: Codable {
// MARK: Properties
let districts: [String: DistrictData]
}
struct DistrictData: Codable {
// MARK: Properties
var confirmed: Int?
var lastupdatedtime: String?
var delta: DailyConfirmedData?
/// Mapping Key Enum
private enum CodingKeys: String, CodingKey {
case confirmed, lastupdatedtime, delta
}
}
struct DailyConfirmedData: Codable {
// MARK: Properties
var confirmed: Int?
/// Mapping Key Enum
private enum CodingKeys: String, CodingKey {
case confirmed
}
}
It's called as,
let summary = try JSONDecoder().decode(IndianStateListModel.self, from: data)
But its returning nil
P.S.: related question regarding decodable Swift Codable with dynamic keys
Any solution, would be great, Thanks in advance
The Codable models that you must use to parse the above JSON data should be like,
Models:
struct StateData: Codable {
var districtData: [String:DistrictData]?
}
struct DistrictData: Codable {
var confirmed: Int?
var lastupdatedtime: String?
var delta: DailyConfirmedData?
}
struct DailyConfirmedData: Codable {
var confirmed: Int?
}
Parsing:
let summary = try JSONDecoder().decode([String:StateData].self, from: data)
Note: There is no need to explicitly create enum CodingKeys if the JSON keys exactly match the properties of the Codable type.
The fundamental issue is that IndianStateListModel has a property called states. But no such key appears in your JSON. I’d suggest parsing it with singleValueContainer. E.g. perhaps:
struct States: Decodable {
typealias StateName = String
let states: [StateName: Districts]
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
states = try container.decode([StateName: Districts].self)
}
}
struct Districts: Decodable {
typealias DistrictName = String
var districts: [DistrictName: DistrictData]
enum CodingKeys: String, CodingKey {
case districts = "districtData"
}
}
struct DistrictData: Decodable {
var confirmed: Int
var lastupdatedtime: String
var delta: DailyConfirmedData
}
struct DailyConfirmedData: Decodable {
var confirmed: Int?
}
And
do {
let result = try JSONDecoder().decode(States.self, from: data)
print(result)
} catch {
print(error)
}
I'm relatively new to SwiftUI and time to time getting errors and solving them by searching over the internet but this time I could not find any solution to my problem and decided to ask for some help over here, stack overflow. I hope the code below helps you to find my issue.
Both my struct are Identifiable and I actually used ShoppingList struct in the same view to make a List of it with the same technique and it works without an error. But when I try to use ForEach for a variable of ShoppingList struct (which is also a struct and conforms to Identifiable protocol) I get this error "Type of expression is ambiguous without more context"
This is the view that I get my error:
struct ListDetailView: View {
#EnvironmentObject var session: SessionStore
var item: ShoppingList
#State private var isAddNewViewActive: Bool = false
var body: some View {
List {
Section(header: Text("Products")) {
ForEach(self.item.products, id: \.id) { product in <<<--- ERROR LINE
Text(product.name)
}
}
Section(header: Text("")) {
Button(action: { self.isAddNewViewActive.toggle() } ) {
Text("Click to add new product")
}
}
}
.listStyle(GroupedListStyle())
.navigationBarTitle(self.item.name)
.sheet(isPresented: $isAddNewViewActive) {
AddNewItemView(session: self.session, item: self.item, isViewActive: self.$isAddNewViewActive)
}
}
}
These are the structs that are in the code
struct ShoppingList: Identifiable, Equatable {
var id: UUID
var name: String
var coverPhoto: String
var products: [Product]
init(id: UUID = UUID(), name: String, coverPhoto: String = "cart", products: [Product] = [Product]()) {
self.id = id
self.name = name
self.coverPhoto = coverPhoto
self.products = products
}
mutating func addProduct(product: Product) {
products.append(product)
print(products)
}
}
struct Product: Identifiable, Equatable {
var id: UUID
var name: String
var brand: String
var imageURL: String
var links: [Int: String]
var description: String
init(id: UUID = UUID(), name: String, brand: String = "", imageURL: String = "", links: [Int: String] = [:], description: String = "") {
self.id = id
self.name = name
self.brand = brand
self.imageURL = imageURL
self.description = description
self.links = links
}
}
Thanks in advance to all StackOverflow Community.
i properly conform to the Equatable protocol
struct ShoppingList: Identifiable, Equatable {
static func == (lhs: ShoppingList, rhs: ShoppingList) -> Bool {
return lhs.id == rhs.id && rhs.id == lhs.id
}
var id: UUID()
...
init(name: String, brand: String = "", imageURL: String = "", links: [Int: String] = [:], description: String = "") {
...
}
}
no need to init UUID, UUID() will self generate
Apparently, there was an error in a completely unrelated part of the code snippet I posted here (sheet View that pops when I click the button on View that has error) and that was causing the error :/
The code I posted here works just fine.
So I have a user JSON structure that goes like this:
- results: {
meta: {}
users: []
},
- status:
I want to get the user so the User model I implement to get the JSON is like this:
struct Response: Decodable {
let results: Result
let status: Int
}
struct Result: Decodable {
let meta: Meta
let users: [User]
}
struct Meta: Decodable {
let total_data: Int
let total_page: Int
}
struct User: Decodable {
let avatar_url: String
let created_at: String
let updated_at: String
let email: String
let id: Int
let name: String
let username: String
}
It is working, but when I have another JSON that the structure is similar, let say like this
- results: {
meta: {}
rooms: []
},
- status:
And when I create Room model, with another struct Response on it, it will cause error because it is duplicated declaration.
Is it possible to reuse struct in Swift? Or is there any convenient way to do this?
Thanks
You could use generics.
struct Response<T: Decodable>: Decodable {
let results: Result<T>
let status: Int
}
struct Result<T: Decodable>: Decodable {
let meta: Meta
let objects: [T]
}
struct Meta: Decodable {
let total_data: Int
let total_page: Int
}
struct User: Decodable {
let avatar_url: String
let created_at: String
let updated_at: String
let email: String
let id: Int
let name: String
let username: String
}
let userResponse: Response<User>?
You can try to use generics in the following way:
struct Response<ResultType> : Decodable where ResultType : Decodable {
let results: ResultType
let status: Int
}
and then use that struct via:
struct Result: Decodable {
let something: String
}
let decoder = JSONDecoder()
let data = "{\"status\":123,\"results\":{\"something\":\"hi\"}}".data(using: .utf8)!
let value = try! decoder.decode(Response<Result>.self, from: data) // will be of type Response<Result>
You have one option is to reuse Meta and Status
If you use sm
For user You can replace name
struct UserResult: Decodable {
let meta: Meta
let users: [User]
}
struct UserResponse: Decodable {
let results: UserResult
let status: Int
}
and for Room
struct RoomResult: Decodable {
let meta: Meta
let users: [Room]
}
struct RoomResponse: Decodable {
let results: RoomResult
let status: Int
}
Depending on your needs, you can use generics to compose the whole struct.
Start with your data models
struct User: Decodable {
let avatar_url: String
let created_at: String
let updated_at: String
let email: String
let id: Int
let name: String
let username: String
}
struct Room: Decodable {
let id: Int
let name: String
}
The goal is to get nice composed types.
typealias UserResponse = Response<Result<User, Meta>>
typealias RoomResponse = Response<Result<Room, Meta>>
The generics types to build from
struct Response<ResultType: Decodable>: Decodable {
let results: ResultType
let status: Int
}
struct Result<ItemType: Decodable, MetaType: Decodable>: Decodable {
let meta: MetaType
let items: [ItemType]
}
Even Meta is a separate part of the composition.
struct Meta: Decodable {
let total_data: Int
let total_page: Int
}
Now lets say we need a custom Meta for the Room response
struct PagedMeta: Decodable {
let current_page: Int
let page_count: Int
}
Here is the new type
typealias RoomPagedResponse = Response<Result<Room, PagedMeta>>