I need to ForEach an array of structs, and so they each must conform to the Identifiable protocol. But since these structs are decoded from fetched JSON's, they already have an id property--the id used in my database. Should I give them another UUID to satisfy the Identifiable protocol? If not, how do I use an existing property as an id?
struct Event: Codable, Identifiable {
let eventID: String
let description: String
let date: String
let location: String
let hostID: String
let hostName: String
// need?
let id = UUID()
}
Use a computed property to return an existing property as the ID:
struct Event: Codable, Identifiable {
let eventID: String
//...other properties
var id: String { eventID } //or whatever
}
Related
I am trying to store data after the app closes and one of the things I need to store is a variable that uses this struct
struct ToDoTasks: Identifiable, Hashable, Encodable, Decodable {
var id = UUID()
var task: String
var date: Date
}
The user can enter data where it is then stored here using the previous struct
#State var items:[ToDoTasks] = [ToDoTasks(task: "Test", date: Date())]
What would be the best way to save the items variable after the app closes?
I tried using #AppStorage but couldn’t get it to work.
In Apple's ScrumDinger sample they do the save in ScrumsView.swift like this:
#Environment(\.scenePhase) private var scenePhase
...
.onChange(of: scenePhase) { phase in
if phase == .inactive { saveAction() }
}
Are there any better syntax for generics? Belowed code gives me huge ugly generics definition in the beginning of view.
struct ViewUserSettings<TypeGif: ModelGifProtocol, TypeFont: ModelFontProtocol, TypeMagicText: ModelMagicTextProtocol, TypePreSetAnswer: ModelPreSetAnswerProtocol>: View where TypeGif: Hashable, TypeFont: Hashable, TypeMagicText: Hashable, TypePreSetAnswer: Hashable
I had to define Hashable inside generics otherwise compiler gives Error when I tried to use this Protocol in SwiftUI ForEach.
ModelGif.swift
// MARK: - Protocol
enum GifType: String, Codable {
case fooA = "fooA more"
case fooB = "fooB more"
case fooC = "fooC more"
// MARK: Properties
var fileName600px: String {
switch self {
case .fooA: return "fooAHQ"
case .fooB: return "fooBHQ"
case .fooC: return "fooCHQ"
}
}
var fileName80px: String {
switch self {
case .fooA: return "fooALQ"
case .fooB: return "fooBLQ"
case .fooC: return "fooCLQ"
}
}
}
protocol ModelGifProtocol: Codable {
var gif: GifType { get }
}
// MARK: - Module
struct ModelGif: ModelGifProtocol, Hashable {
// MARK: Properties
let gif: GifType
// MARK: Initialization
init(_ gif: GifType) {
self.gif = gif
}
// MARK: Static Properties
static var fooA = Self(.fooA)
static var fooB = Self(.fooB)
static var fooC = Self(.fooC)
}
ModelMagicText.swift
// MARK: - Protocol
protocol ModelMagicTextProtocol: Codable {
var id: UUID { get }
var context: String { get }
init(_ context: String, id: UUID) throws
init(_ context: String) throws
}
// MARK: - Module
struct ModelMagicText: ModelMagicTextProtocol, Hashable {
// MARK: Properties
let id: UUID
let context: String
// MARK: Initialization
init(_ context: String, id: UUID) throws {
guard context.count <= 30 else { throw ContextError.tooLong } // restricted at ViewAnimatingCircleText.modifyDynamicText()
self.context = context.uppercased()
self.id = id
}
init(_ context: String) throws {
try self.init(context, id: UUID())
}
init(_ model: ModelMagicTextProtocol) throws {
try self.init(model.context, id: model.id)
}
// MARK: Static Properties
static let defaultText = try! ModelMagicText("foooooooooooo")
}
// MARK: - Extension: ContextError
extension ModelMagicText {
enum ContextError: Error {
case tooLong
}
}
We can format that more readably using multiple lines. We can also move the Hashable constraints into the generic parameter list:
struct ViewUserSettings<
TypeGif: ModelGifProtocol & Hashable,
TypeFont: ModelFontProtocol & Hashable,
TypeMagicText: ModelMagicTextProtocol & Hashable,
TypePreSetAnswer: ModelPreSetAnswerProtocol & Hashable
>: View {
var gif: TypeGif
var font: TypeFont
var magicText: TypeMagicText
var preSetAnswer: TypePreSetAnswer
var body: some View { EmptyView() }
}
You only need a where clause when you have equality constraints, but your example code doesn't have any equality constraints.
If you change each of your model protocols to inherit from Hashable, you can eliminate the Hashable constraints entirely:
protocol ModelGifProtocol: Hashable { }
protocol ModelFontProtocol: Hashable { }
protocol ModelMagicTextProtocol: Hashable { }
protocol ModelPreSetAnswerProtocol: Hashable { }
struct ViewUserSettings<
TypeGif: ModelGifProtocol,
TypeFont: ModelFontProtocol,
TypeMagicText: ModelMagicTextProtocol,
TypePreSetAnswer: ModelPreSetAnswerProtocol
>: View {
...
There's also the question of whether you need such verbose names. Do you really need the Type and Model prefixes?
protocol GifProtocol: Hashable { }
protocol FontProtocol: Hashable { }
protocol MagicTextProtocol: Hashable { }
protocol PreSetAnswerProtocol: Hashable { }
struct ViewUserSettings<
Gif: GifProtocol,
Font: FontProtocol,
MagicText: MagicTextProtocol,
PreSetAnswer: PreSetAnswerProtocol
>: View {
var gif: Gif
var font: Font
var magicText: MagicText
var preSetAnswer: PreSetAnswer
var body: some View { EmptyView() }
}
Keep in mind that you can still refer to SwiftUI's Font type as SwiftUI.Font inside ViewUserSettings.
You didn't give any details regarding how your protocols are defined. Do you really need your own ModelFontProtocol? SwiftUI's Font is already Hashable, so if you can just use the concrete Font type, your code will be simpler. Maybe you can use concrete types in place of your other protocols too, but we can't say without seeing how they're defined.
If you're using the same set of type parameters and constraints for multiple views, there are ways to factor the common stuff out. You should edit your question to include more example code if that's the case.
I'm getting an error that Value type of screen has no member elements, but looking at my struct I can see it there. Is there a limit in how far my foreach can drill down to?
Here's where I'm getting the error
ForEach(remedydata?.remedy.content.screens.elements ?? []) { remedy in
If I remove elements I don't have any issues
My state is set as
#State var remedydata: Welcome? = nil
My struct looks like the below
// MARK: - Welcome
struct Welcome: Codable {
let remedy: Remedy
}
// MARK: - Remedy
struct Remedy: Codable {
let id, tenantID: String
let version: Int
let title, remedyClientID, createdAt, remedyDescription: String
let enabled: Bool
let trigger: Trigger
let requiredHostCapabilities: [RequiredHostCapability]
let localizedStrings: [LocalizedString]
let content: Content
let actor: String
enum CodingKeys: String, CodingKey {
case id
case tenantID = "tenantId"
case version, title
case remedyClientID = "remedyClientId"
case createdAt
case remedyDescription = "description"
case enabled, trigger, requiredHostCapabilities, localizedStrings, content, actor
}
}
// MARK: - Content
struct Content: Codable {
let serializationVersion: Int
let screens: [Screen]
}
// MARK: - Screen
struct Screen: Codable, Identifiable {
let id: Int
let title: String
let elements: [Element]
}
// MARK: - Element
struct Element: Codable, Identifiable {
let id = UUID()
let tag: Tag
let text: String
let action: Action?
You can't do remedydata?.remedy.content.screens.elements since remedydata?.remedy.content.screens is an Array. In the question, you have multiple Element inside multiple Screen, which means you have two options:
Display each Element for a specific Screen.
Display a specific Element for each Screen.
To "Display each Element for a specific Screen":
ForEach(remedydata?.remedy.content.screens.first?.elements ?? []) { element in
Text(element.text)
}
To "Display a specific Element for each Screen":
ForEach(remedydata?.remedy.content.screens ?? []) { screen in
Text(screen.elements.first?.text ?? "")
}
According to Apple's documentation regarding Picker in SwiftUI using an Enum, if the enum conforms to the Identifiable protocol in addition to CaseIterable, a picker iterating over all cases should update the bound variable natively.
I tested it, and it does not work as expected.
enum Flavor: String, CaseIterable, Identifiable {
case chocolate
case vanilla
case strawberry
var id: String { self.rawValue }
}
struct EnumView: View {
#State private var selectedFlavor = Flavor.chocolate
var body: some View {
VStack {
Picker("Flavor", selection: $selectedFlavor) {
ForEach(Flavor.allCases) { flavor in
Text(flavor.rawValue.capitalized)//.tag(flavor)
}
}
Text("Selected flavor: \(selectedFlavor.rawValue)")
}
}
}
However, if I pass a tag for each view, it works.
What's happening here? Is the Apple documentation wrong? The selectedFlavor variable expects a value of type Flavor, but the id used in the picker is actually a String.
Thanks.
For a Picker to work properly, its elements need to be identified.
Note that the selectedFlavor variable is of type Flavor. Which means the options in the Picker should be identified as Flavors (not Strings).
However, in your code your id is of type String:
var id: String { self.rawValue }
You can either:
provide a tag (of type Flavor):
Text(flavor.rawValue.capitalized)
.tag(flavor)
conform Flavor to Identifiable by providing a custom id of type Flavor:
var id: Flavor { self }
specify the id parameter (of type Flavor) explicitly in the ForEach:
ForEach(Flavor.allCases, id: \.self) { ... }
change the selectedFlavor to be a String:
#State private var selectedFlavor = Flavor.chocolate.rawValue
This is an addition to #pawello2222 already awesome answer.
Just to clarify that the solution to conform the enum to Identifiable using the below code works to correctly tag the UI picker without explicitly declaring it within the picker code:
var id: Flavor { self }
The official Apple documentation for the swiftUI picker says to id the enum using var id: String { self.rawValue } and does not work as expected using the rest of the example code.
However, getting an error message that says that my class 'Expenses' does not conform to protocol 'Decodable' & Type 'Expenses' does not conform to protocol 'Encodable'
import Foundation
class Expenses : ObservableObject, Codable {
#Published var items : [ExpenseItem] {
// Step 1 creat did set on publsihed var.
didSet {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(items) {
UserDefaults.standard.set(encoded, forKey: "Items")
}
}
}
init() {
if let items = UserDefaults.standard.data(forKey: "Items") {
let decoder = JSONDecoder(
if let decoded = try?
decoder.decode([ExpenseItem].self, from: items) {
self.items = decoded
return
}
}
self.items = []
}
}
my expense item is flagged as
struct ExpenseItem : Identifiable, Codable {
let id = UUID()
let name : String
let type : String
let amount : Int
}
Conformance to Encodable/Decodable is auto-synthesized when all stored properties conform to Encodable/Decodable, but using a property wrapper on a property means that now the property wrapper type needs to conform to Encodable/Decodable.
#Published property wrapper doesn't conform. It would have been nice to just implement conformance on the Published type itself, but unfortunately it doesn't expose the wrapped value, so without using reflection (I've seen suggestions online), I don't think it's possible.
You'd need to implement the conformance manually:
class Expenses : ObservableObject {
#Published var items : [ExpenseItem]
// ... rest of your code
}
extension Expense: Codable {
enum CodingKeys: CodingKey {
case items
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.items, forKey: .items)
}
required init(from decoder: Decoder) throws {
var container = try decoder.container(keyedBy: CodingKeys.self)
self.items = try container.decode([ExpenseItem].self, forKey: .items)
}
}