SwiftUI Initialzier requires String conform to Identifiable - swift

I am attempting writing some SwiftUI code for the very first time and am running into an issue while trying to make a simple table view. In this code teams is an array of String, but the List is giving me the following error: Initializer 'init(_:rowContent:)' requires that 'String' conform to 'Identifiable'
var body: some View {
let teams = getTeams()
Text("Hello there")
List(teams) { team in
}
}
Anyone have an idea what it means?

The argument of List is required to be unique, that's the purpose of the Identifiable protocol
You can do
List(teams, id: \.self) { team in
but then you are responsible to ensure that the strings are unique. If not you will get unexpected behavior.
Better is to use a custom struct with an unique id conforming to Identifiable

The Apple preferred way to do this is to add a String extension:
extension String: Identifiable {
public typealias ID = Int
public var id: Int {
return hash
}
}
See this code sample for reference:
https://github.com/apple/cloudkit-sample-queries/blob/main/Queries/ContentView.swift

I'm assuming that getTeams() returns a [String] (a.k.a. an Array<String>).
The issue, as the error suggests, are the strings aren't identifiable. But not just in the "they don't conform to the Identifiable protocol" sense, but in the broader sense: if two strings are "foo", one is indistinguishable from the other; they're identical.
This is unacceptable for a List view. Imagine you had a list that contains "foo" entries, and the users dragged to rearrange one of them. How would List be able to tell them apart, to know which of the two should move?
To get around this, you need to use an alternate source of identity to allow the List to distinguish all of its entries.
You should watch the "Demystify SwiftUI" talk from this year's WWDC. It goes into lots of detail on exactly this topic.

If your trying to go over a 'List' of items of type 'Struct'
Change the Struct to use 'Identifiable':
my Struct:
struct Post : Identifiable{
let id = UUID()
let title: String
}
my List :
let posts = [
Post( title: "hellow"),
Post( title: "I am"),
Post( title: "doing"),
Post( title: "nothing")
]
then you can go over your List:
List(posts){post in
Text(post.title
}

Related

SwiftUI: How could I use polymorphism to make a model class create its own View

I am trying to build a small App in which you can create tests with questions using SwifUI, but finding how to make everything works out is getting hard for a newbie like me. The app would show a list of questions in a main scrollable view and these questions could be of different types such as true or false, text, multiple choice, etc… and could be active or not.
I thought it would be great that all different types of questions adopted the same protocol. This protocol would also define a function or a computed property in charge of display its on view using the values store in the different attributes. However, the problem comes up when trying to modify any of this parameters interacting with that View. Let's say I want to add a toggle button that active or reactive the question, modifying one of the values of that question. With the different solutions I implemented, I didn't get the view being rebuild/updated.
I tried several things to accomplish this, like wrapping those properties that are supposed to update their values with #State or #Binding. I also tried to turn those properties into ObservableObjects, adding new classes that adopt the ObservableObject protocol, but it does not work. The only thing that seems to work is, for any type of Question, create a view, with an observable ViewModel. Later, in the view where I display all the question, I have to create a Switch with all the different possibilities.
What I don't like about this solutions is that if I wanted to add a new type of question, I would have to modify this main view to include an extra case for this new type of question, what is against the Open-Closed principle.
Do you have any suggestion guys to assign this responsibility to any question class instead of to the main view?
Thank you in advance :)
Usually you don't want a model to know about the view. That's backwards. But yet we often want to hide the concrete selection logic behind a single call that does different things depending on the data provided (similar to polymorphism via function overloading).
The key idea is the use of the Factory Pattern combined with the Visitor Pattern to keep in harmony with the Open-Closed Principle.
When we do this kind of thing with regular objects, we often use a factory method to return the proper subclass for the input data. Inside that factory method usually sits a switch statement. The factory interface lets us honor the Open-Closed principle so that ContentView doesn't change when we add new question types. Chances are, what follows is probably very similar to what you had yourself with the view model approach.
In SwiftUI, the best approach at a factory-style view would be to create a QuestionView that then knows how to create the correct concrete view for each question object. I hope that default + fatalError() makes you consider how an enum might be useful here.
struct QuestionView: View {
let question: Question
var body: some View {
switch question {
case let q as TextQuestion:
TextQuestionView(question: q)
case let q as DoubleQuestion:
DoubleQuestionView(question: q)
default:
fatalError("Unknown question type")
}
}
}
Then in your main view it would be polymorphic, reacting dynamically to the actual Question instances. You'd use it something like this:
struct ContentView: View {
#State private(set) var questions: [Question]
var body: some View {
NavigationView {
Form {
ForEach(questions, id: \.key) { question in
QuestionView(question: question)
}
Button("Submit") {
let answers = Answers()
for question in questions {
question.record(answers: answers)
}
print(answers)
}
}
.navigationTitle("Questions")
}
}
}
A reasonable way to extract the answers is to use the Visitor Pattern in a way similar in structure to Encodable's encode(to encoder: Encoder). We expect each specialty view to communicate with its specific Question object, and then expect the Question object to contain an implementation of func record(answers: Answers). When it's time, loop through questions and tell them to record their answers. (Note that we can add various Answers implementations without changing the Question subclasses, in keeping with the Open-Closed principle).
The Question objects are like view models, and they are ObservableObjects. You can see how they record their answers when asked.
For this to work, they cannot be protocols with associated types. That just kills using them in an array.
class TextQuestion: Question, ObservableObject {
#Published var answer = ""
override func record(answers: Answers) {
answers.addAnswer(key: key, value: answer)
}
}
class MeasurementQuestion: Question, ObservableObject {
let unit: String
#Published var answer = 0.0
init(key: String, question: String, unit: String) {
self.unit = unit
super.init(key: key, question: question)
}
override func record(answers: Answers) {
answers.addAnswer(key: key, value: answer)
}
}
Then each individual question subtype view will watch its own Question instance:
struct TextQuestionView: View {
#ObservedObject private(set) var question: TextQuestion
var body: some View {
Section(question.question) {
TextField("Answer", text: $question.answer)
}
}
}
struct MeasurementQuestionView: View {
#ObservedObject private(set) var question: MeasurementQuestion
var body: some View {
Section(question.question) {
HStack {
TextField("Answer", value: $question.answer, format: .number)
Text(question.unit)
}
}
}
}
You can simply add your list of questions to the preview and see how it works:
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(questions: Example.questions)
}
}
struct Example {
static let questions: [Question] = [
TextQuestion(
key: "name",
question: "What...is your name?"
),
TextQuestion(
key: "quest",
question: "What...is your quest?"
),
[
TextQuestion(
key: "assyria",
question: "What...is the capital of Assyria?"
),
TextQuestion(
key: "color",
question: "What...is your favorite colour?"
),
MeasurementQuestion(
key: "swallow",
question: "What is the air-speed velocity of an unladen swallow?",
unit: "kph"
)
].randomElement()!
]
}
I'm not sure I like this implementation for anything beyond a toy project—I prefer stronger layer separations. This does, however, setup a polymorphic view that adjusts to data implementation types. The key idea is the use of the Factory Pattern combined with the Visitor Pattern.

SwiftUI ForEach Bindable List errors

I would appreciate help with SwiftUI bindable lists.
I read the following article and tried it on my app but I'm getting errors.
https://www.swiftbysundell.com/articles/bindable-swiftui-list-elements/
First, the following View including the non-bindable ordinary ForEach list works fine without any errors
#ObservedObject var notificationsViewModel = NotificationsViewModel.shared
//NotificationsViewModel does a API call and puts the fetched data in the Notifications Model
var body: some View {
VStack {
ForEach(notificationsViewModel.notifications?.notificationsDetails ?? [NotificationsDetail]()) { notificationsDetail in
---additional code here--- }
}
Model below:
struct Notifications: Codable, Identifiable {
let id = UUID()
let numberOfNotifications: Int
var notificationsDetails: [NotificationsDetail]
enum CodingKeys: String, CodingKey {
case numberOfNotifications = "number_of_notifications"
case notificationsDetails = "notifications"
}
}
struct NotificationsDetail: Codable, Identifiable, Equatable {
let id: Int
let notificationsCategoriesId: Int
let questionsUsersName: String?
enum CodingKeys: String, CodingKey {
case id = "notifications_id"
case notificationsCategoriesId = "notifications_categories_id"
case questionsUsersName = "questions_users_name"
}
}
When I try to change this ForEach to a bindable one, I start getting multiple errors.
ForEach($notificationsViewModel.notifications?.notificationsDetails ?? [NotificationsDetail]()) { $notificationsDetail in
---additional code here using $notificationsDetail---}
When I try to fix some of the errors such as "remove ?", I get a new error saying I need to add the ?.
When I delete the default value ?? NotificationsDetail, I still get errors
The Xcode build version is iOS15.
Does anyone know how to fix this? Thanks in advance.
ForEach($notificationsViewModel.notifications?.notificationsDetails ?? [NotificationsDetail]()
Is confusing the type system, because one side of the ?? is a binding to an array of values, and the other side is an array of values. There's also an optional in your key path to make things more complicated.
Try to rearrange the NotificationsViewModel type so that it just surfaces a non-optional array instead of having all this optional mess at the view level. Is it really meaningful to have an optional notification property, or can an empty one be used instead? Do you need the separate notifications struct? Are you just modelling your data types directly from API responses? Perhaps you can make changes to your model types to make them easier to work with?

Understanding list identifiers in SwiftUI

Apple tutorial:
Lists work with identifiable data. You can make your data identifiable in one of two ways: by passing along with your data a key path to a property that uniquely identifies each element, or by making your data type conform to the Identifiable protocol.
I am curious what the implications are for lists that show items by design that are semantically "equal", creating duplicate rows that should behave in the same manner (i.e. both be deleted on removal by id). For instance:
List(["a", "b", "b", "c", "c"], id: \.self) { str in
Text(str)
}
I think I saw some sources saying that each row must be uniquely identified. Is that really true or should it be identifi-able?
The code above doesn't seem to crash and works fine -- is it actually fine?
The code above doesn't seem to crash and works fine -- is it actually
fine?
Yes! Definitely, List and ForEach needs their item's be in some way identifiable. What does it means for us? That means we should provide unique items to List or ForEach! But you would say wait a moment I did not make my array or items unique, and instead of that I made even duplicate items as well, So why it does worked?
Well I would say you absolutely made all your items unique, in this way that only 1 of them exist not 2 or . . ., you would say how? the answer is in id: \.self when you use id: \.self term you are literally saying and conforming to Integer Indexing ID system from SwiftUI, with using id: \.self SwiftUI understand that you are not going show a way for SwiftUI to understand which item is which, then it start to work by itself, and given an undercover id to your items.
So you can even delete the part of: id: \.self from your code, if you can show a way to SwiftUI to make your items Identifiable, the way that SwiftUI works in this case is awesome, when you remove id: \.self it starts analysing the type of item in your List/ForEach, then automatically goes to search identifiable protocol in that type, So that means your CustomType also should conform identifiable.
Let me gave you an example that we are going delete id: \.self and even making more duplicate and also deleting items, all would be identifiable.
import SwiftUI
struct ContentView: View {
#State private var array: Array<String> = ["a", "a", "a", "b", "b", "b", "c", "c", "c"]
var body: some View {
List(array) { item in
Text(item)
}
Button("remove last item") {
if array.count > 0 { array.remove(at: array.count - 1) }
}
.foregroundColor(Color.red)
.font(Font.body.weight(Font.Weight.bold))
}
}
extension String: Identifiable {
public var id: UUID {
get {
return UUID()
}
}
}

Protocol conforming to type with associated value

I've got the following snippet:
protocol MyProtocol: Identifiable where ID == UUID {
var id: UUID { get }
}
var test: [MyProtocol] = []
Protocol 'MyProtocol' can only be used as a generic constraint because it has Self or associated type requirements
Why doesn't this work? Shouldn't the where ID == UUID remove the ambiguity the error is concerned with? Am I missing something here?
I think this question is similar to this one: Usage of protocols as array types and function parameters in swift
However, I would have assumed that adding where ID == UUID should fix the problem? Why is that not the case?
Thanks!
Edit
So, this problem has occurred while experimenting with SwiftUI and struct data models. I've always used classes for any kind of data model but it seems like SwiftUI wants to get you to use structs as often as possible (I still don't see how that's realistically possible but that's why I'm experimenting with it).
In this particular case, I tried to have a manager that contains structs that all conform to MyProtocol. For example:
protocol MyProtocol: Identifiable where ID == UUID {
var id: UUID { get }
}
struct A: MyProtocol { // First data model
var id: UUID = UUID()
}
struct B: MyProtocol { // Second data model
var id: UUID = UUID()
}
class DataManager: ObservableObject {
var myData: [MyProtocol]
}
...
I don't actually have to declare Identifiable on MyProtocol but I thought it would be nicer and cleaner.
Because this is not a current feature of Swift. Once there is an associated type, there is always an associated type. It doesn't go away just because you constrain it. And once it has an associated type, it is not concrete.
There is no way to "inherit" protocols this way. What you mean is:
protocol MyProtocol {
var id: UUID { get }
}
And then you can attach Identifiable to structs that require it:
struct X: MyProtocol, Identifiable {
var id: UUID
}
(note that no where clause is required.)
There is no Swift feature today that allows you to say "types that conform to X implicitly conform to Y." There is also no Swift feature today that allows for an Array of "things that conform to Identifiable with ID==UUID." (That's called a generalized existential, and it's not currently available.)
Most likely you should go back to your calling code and explore why you require this. If you post the code that iterates over test and specifically requires the Identifiable conformance, then we may be able to help you find a design that doesn't require that.

Cannot find a way to make a joined query result encodable in Vapor 3

I want to display the result of a join query (which debug shows is working) in a leaf template:
persons.get("/persons")
{ request ->Future<View> in
return Title.query(on:request).join(\Person.titleId, to:\Title.id)
.alsoDecode(Person.self).all().flatMap(to: View.self) { pers in
let context = APContext(title: "Demo", personnel: pers)
let leaf = try request.make(LeafRenderer.self)
return leaf.render("persons", context)
}
}
I haven't been able to find a way of passing the context directly as an array (like in Vapor 2). So, I am trying to use the codable structure:
struct APContext: Encodable {
let title: String
let personnel: [(Title,Person)]
}
The documentation is out-of-date as it suggests creating the personnel property as EncodableStream will work, but this has been removed. If the struct above is used it gives a compilation error:
Type 'APContext' does not conform to protocol 'Encodable'
If I simplify the query so the the definition becomes:
let personnel = [Person]
Then it works.
So, I suppose my question boils down to: how do I make [(Title,Person)] encodable?
Thanks to Leo's suggestion, I have got a solution:
struct Personnel: Encodable {
let title: Title
let person: Person
}
And include the following in the route:
var enc: [Personnel] = []
for per in pers {
enc.append(Personnel(title:per.1,person:per.0))
}
enc can now be passed to the leaf template happily. The only thing to watch for is that the tags must include the field name from the personnel structure, e.g. per.person.surname.