SwiftUI - struct embedded in struct with UUID() - swift

Using SwiftUI, I have a embedded struct in a struct like
struct Order Identifiable {
var id = UUID()
var includeSoap: Bool = false
var includeTowel : Bool = false
var quantity : Int
}
struct CompletedOrder Identifiable {
// var id = UUID() or var id = order.id
var order : Order
var summary : String
var purchaseDate : String
}
For later use I need to use the Identifiable keyword.
How do I avoid the extra UUID()? I can try to copy the Order.id to the CompletedOrder.id but not sure if it is a bug in XCode but I got weird errors there.
Anyway, what is the best way to do this? Do I need in both structs 'Identifiable' or is there a kind of inheritaence? What are the pro and cons about this? Again I got weird results in XCode. But I hope its me ;)
Thank you

return the order.id it self:
struct CompletedOrder: Identifiable {
var id: UUID { order.id }
var order: Order
var summary: String
var purchaseDate: String
}

Related

How do I use polymorhism for lists in swiftUI?

I want to add two different types, Pig and Frog, to the same array. Normally this would work well as long as both conform to the same protocol. However, in swiftUI if you want to use that array for a List then those types also need to conform to Identifiable. Like so:
protocol Animal: Identifiable {
var id: String { get }
var name: String { get }
var count: Int { get }
var image: String { get }
}
struct Pig: Animal {
var id: String = UUID().uuidString
var name = "Pig"
var count = 2
var image = "🐷"
}
struct Frog: Animal {
var id: String = UUID().uuidString
var name = "Frog"
var count = 3
var image = "🐸"
}
struct ContentView: View {
var animals: [Animal] = [Pig(), Frog()]
var body: some View {
List(animals) { animal in
Row(animals: animal)
}
}
}
This gives me the error Protocol 'Animal' can only be used as a generic constraint because it has Self or associated type requirements.
So my questions are why is this a problem and how do I get around it?
I experimented a bit and tried using an associated type like this:
protocol ConstructAnimal: View where AnimalType: Animal {
associatedtype AnimalType
var animals: [AnimalType] { get set }
}
But this still only lets me have one type in the array at a time.

Assigning a fixed value to a variable in a struct

I have a struct that is used when parsing JSON data. I want one of the fields, name, to be a fixed name, see below...
struct QuestionConfiguration: Codable, DisplayOrderable {
var name: String? = "QuestionConfiguration"
var isRequired: Bool
var displayOrder: Int
var title: String = ""
var questions: [Question]
}
Each time, when I then try and access a QuestionConfiguration object, name is nil.
I have tried using an init(), with parameters and without.
I have tried String? and String.
Does anyone know how I can achieve this so that name is the same for each object, without having to pass it to the object?
Simply by changing this line - var name: String? = "QuestionConfiguration" to this - let name = "QuestionConfiguration"
Full code -
struct QuestionConfiguration: Codable, DisplayOrderable {
let name = "QuestionConfiguration"
var isRequired: Bool
var displayOrder: Int
var title: String = ""
var questions: [Question]
}
Note - If property is fixed then you should not call it as variable b'coz variable means it may change

Using SwiftUI ForEach to iterate over [any Protocol] where said protocol is Identifiable

In a ViewModel I have:
public var models: [any Tabbable]
And Tabbable starts with
public protocol Tabbable: Identifiable {
associatedtype Id
var id: Id { get }
/// ...
}
In Swift in my ViewModel I can use the Array.forEach to:
models.forEach { _ in
print("Test")
}
But in SwiftUI I can't use ForEach to:
ForEach(viewModel.models) { _ in
Text("Test")
}
Due to:
Type 'any Tabbable' cannot conform to 'Identifiable'
Any suggestions?
This is with Swift 5.7. Do I need to go back to making something like AnyTabbable?
Thanks in advance.
Consider this example in the form of a Playground:
import UIKit
import SwiftUI
public protocol Tabbable: Identifiable {
associatedtype Id
var id: Id { get }
var name : String { get }
}
struct TabbableA : Tabbable{
typealias Id = Int
var id : Id = 3
var name = "TabbableA"
}
struct TabbableB : Tabbable {
typealias Id = UUID
var id : Id = UUID()
var name = "TabbableB"
}
struct ViewModel {
public var models: [any Tabbable]
}
let tabbableA = TabbableA()
let tabbableB = TabbableB()
let models: [any Tabbable] = [tabbableA, tabbableB]
struct ContentView {
#State var viewModel : ViewModel = ViewModel(models: models)
var body : some View {
ForEach(viewModel.models) { model in
Text(model.name)
}
}
}
In this case, we have the type TabbableA where each instance has an id property that is an integer (for the sake of the sample they only use "3" but it's the type that is significant). In TabbableB each instance's id is a UUID. Then I create an array where one item is an instance of TabableA and another is an instance of TabbableB. The array is of type [any Tabbable].
Then I try to use ForEach on the array. But some elements in the array use Int ids and some use UUID ids. The system doesn't know what type to use to uniquely identify views. Ints and UUIDs can't be directly compared to one another to determine equality. While each item that went into the array is Tabbable, and therefore conforms to Identifiable, the elements coming out of the array, each of which is of type any Tabbable, do not conform to Identifiable. So the system rejects my code.
You can provide a KeyPath to the id (or any unique variable): parameter which specifies how to retrieve the ID in ForEach. It is because all items in ForEach must be unique. You can create a structure, which conforms to your protocol. At that moment it should work. The second option is to remove Identifiable from the protocol and then you can use that directly.
public protocol Tabbable {
var id: String { get }
/// ...
}
public var models: [Tabbable]
ForEach(viewModel.models, id: \.id) { _ in
Text("Test")
}
or
public protocol TabbableType: Identifiable {
associatedtype Id
var id: Id { get }
/// ...
}
struct Tabbable: TabbableType {
var id: String { get }
/// ...
}
public var models: [Tabbable]
ForEach(viewModel.models) { _ in
Text("Test")
}

Struct's id of type UUID changes everytime i try to access it [duplicate]

This question already has answers here:
SwiftUI: data identifier stability and uniqueness
(2 answers)
Closed 1 year ago.
I have a struct which is parsed from JSON, but contains another struct Article that must be identifiable. It looks like this:
import Foundation
struct TopHeadlines: Codable {
var totalArticles: Int
var articles: [Article]
struct Article: Codable {
var title: String
var description: String
var url: String
var image: String
var publishedAt: String
var source: Source
}
struct Source: Codable {
var name: String
var url: String
}
var json: Data? {
return try? JSONEncoder().encode(self)
}
}
extension TopHeadlines.Article: Identifiable {
var id: UUID { return UUID() }
}
I need UUID generated to access image from newImages dictionary:
List(viewModel.articles, id: \.id) { article in
HStack {
OptionalImage(uiImage: viewModel.newsImages[article.id])
Text(article.id.uuidString)
Text(article.id.uuidString)
Text(article.id.uuidString)
}
}
but three text views print three different UUIDs:
CC83B8AE-61B1-4A7D-A8A4-1B1E98C27CE7
545C1D28-F098-48A3-8C3C-A98BB54F9751
39B8383C-A2D8-46B0-BA51-1B861AF09762
How should I create ID for Article struct so it wouldnt be re-generated everytime?
You've declared id as a computed property, so by definition a new UUID instance will be returned every time you access the property.
You need to make the property a stored immutable property to ensure that the UUID never changes.
You also need to manually declare a CodingKey conformant enum and omit the id key from its cases to tell the compiler that id should not be decoded from the JSON.
struct TopHeadlines: Codable {
var totalArticles: Int
var articles: [Article]
struct Article: Codable, Identifiable {
var title: String
var description: String
var url: String
var image: String
var publishedAt: String
var source: Source
let id = UUID()
private enum CodingKeys: String, CodingKey {
case title, description, url, image, publishedAt, source
}
}
struct Source: Codable {
var name: String
var url: String
}
var json: Data? {
return try? JSONEncoder().encode(self)
}
}
The id property in your extension is a computed property, so a new UUID (UUID()) is generated on each call.
Since you can't have stored properties in an extension, try adding it directly to the Article struct, like this:
struct Article: Codable, Identifiable {
var id = UUID()
var title: String
var description: String
var url: String
var image: String
var publishedAt: String
var source: Source
}
This only generates a UUID when the struct is initialized.
extensions cannot store properties, that's why you've implemented id as computed property, and each new UUID() is unique
the most obvious solution is to move it to move it to your struct
struct Article: Codable {
let uuid = UUID()
// ...
}
Declared like this it won't require value both in time of decoding or creating new object.
But if you can't edit this struct(which is probably why you're using an extension), you can do the following: extend your struct with Hashable, then you can access object hashValue which is calculated based on all properties so this value will be the same only for two objects with same values in all properties, which is usually fits good for an unique identifier
extension Article: Hashable {
}
Usage
Text("\(article.hashValue)")

Environmentobject keep track of arrays variables

I'm pretty new to xCode so this could have an obvious answer.
I've just learned about environment objects and created the following one:
import SwiftUI
class Data: ObservableObject {
#Published var types = [Type]()
#Published var customers = [Customer]()
#Published var templates = [SubscriptionTemplate]()
#Published var subscriptions = [Subscription]()
#Published var giftCodes = [Giftcode]()
}
As you can see the object contains an array of objects. One of these is a customer array. The customer object looks like this:
import SwiftUI
class Customer: Identifiable, Codable{
var id: Int
var firstname: String
var lastname: String
var address: String
var plz: Int
var place: String
var credit: Int
init(id: Int, firstname: String, lastname: String, address: String, plz: Int, place: String, credit: Int) {
self.id = id
self.firstname = firstname
self.lastname = lastname
self.address = address
self.plz = plz
self.place = place
self.credit = credit
}
}
extension Customer: Equatable {
static func == (lhs: Customer, rhs: Customer) -> Bool {
return lhs.id == rhs.id
}
}
In my project, I implemented an API call to update the customer. This works like a charm, but after updating, I also want to fetch the customer objects with the following method:
API().fetchCustomers { (customers) in
self.data.customers = customers
}
After updating an object this doesn't work. The environment object doesn't update, but after creating a new object or fetching the data initial, it works.
What is the difference between the update and the create / fetch?
Make Customer a value type (ie. struct):
struct Customer: Identifiable, Codable{
var id: Int
// ... other code