Swift Variables and Enums in ForEach Loop for Bindings - swift

I have a ForEach loop in which I am displaying X amount of fields over and over again depending on user entry (i.e. they enter 5, it displays 5 iterations of these fields).
I need to be able to set the TextField, FocusState, and AccessibilityFocusState bindings for these fields dynamically. I know I have to conform the enum to CaseIterable but I can't separately define an infinite number of enum cases just in case a user enters a value outside of that in which I have configured enum cases.
I also realize that in my example I have only defined a single #State variable, but as this goes along with the question, how do you define X amount of variables when doing something like this?
Here's an example:
struct TestView: View {
#AccessibilityFocusState var accessFocus: AccessFocusField?
#FocusState var isFocused: Field?
#State private var field = ""
enum AccessFocusField: CaseIterable {
case fieldName
}
enum Field: CaseIterable {
case fieldName
}
var body: some View {
ForEach(1...5) { value in
TextField("Hello World!", $field[value])
.focused($isFocused, equals: .fieldName[value]
.accessibilityFocused($accessFocus, equals: .fieldName[value])
}
}
}

For ForEach I would create an identifiable object, named Field with all the properties that Textfield needs. And then declare an array of that object, and use that in ForEach.

Related

SwiftUI: What is the difference between var and let as a parameter in List's Row?

What is the difference between var and let as a parameter in List's Row?
Usually, if I don't change the landmark variable, the compiler will warn me, but there is no warning in that row. I wonder why.
struct LandmarkRow: View {
var landmark: Landmark
var body: some View {
Text(landmark.id)
}
}
struct LandmarkRow: View {
let landmark: Landmark
var body: some View {
Text(landmark.id)
}
}
This looks like a same result:
struct LandmarkList: View {
var body: some View {
List(landmarks, id: \.id) { landmark in
LandmarkRow(landmark: landmark)
}
}
}
var is a variable meaning it is mutable. You can reassign it a value as many time as you wish.
In LandmarkRow and LandmarkList, var body is a computed property that calculates (rather than stores) a value. And it's read-only. Computed properties can only be declared using var.
When you implement a custom view, you must implement a computed
body property to provide the content for your view. Return a view
that's composed of primitive views that SwiftUI provides, plus other composite views that you've already defined. https://developer.apple.com/documentation/swiftui/view/body-swift.property
let is used to declare a constant. You can assign it a value exactly once as you have done in LandmarkList. In other words, you can not reassign it a value.
List(landmarks, id: \.id) { landmark in
LandmarkRow(landmark: landmark)
}
Example
struct Example {
// variable that you can change
var name: String
// constant that can only be assigned a value once.
let fileExtension: String
// A computed property that is read-only
var filename: String {
return name + "." + fileExtension
}
}
var example = Example(name: "example", fileExtension: "swift")
You can change name from example to example_2 since it is a variable.
example.name = "example_2"
You can not change fileExtension from swift to js because fileExtension is a constant (let). If you do so, you will get the following error.
Cannot assign to property: 'fileExtension' is a 'let' constant
example.fileExtension = "js"
You can not change fileName because it is a read-only property. If you try to change, you will get this error.
Cannot assign to property: 'filename' is a get-only property
example.filename = "ex.js"
More info
What is the difference between `let` and `var` in swift?
https://docs.swift.org/swift-book/LanguageGuide/Properties.html
https://www.avanderlee.com/swift/computed-property/
The compiler warns you about local variables which are never been modified or being unused.
But you get never a warning about declared struct members / class properties. And a SwiftUI view is actually a regular struct.
However you get an error if you are going to modify a struct member / class property which is declared as constant.
In a SwiftUI View there is no difference except for semantics.
A struct is immutable therefore having a var vs a let is irrelevant.
let Is semantically correct
I am also new to SwiftUI. I only have backgound in C and C++. I am guessing that it has to do with the fact that you declared landmark but didn't initialized it (or in this case, default value). Here, it assumes that you will initialize landmark when you initialize a LandmarkRow. Getting back to the point, I think the compiler doesn't know if landmark changes or not untill it is run.
var => Variable
By defining var you inform the compiler that this variable will be changed in further execution of code.
var current_day = 6
let => Constant
By defining let you inform the compiler that this is a constant variable and its value stays the same.
Like
let earth_gravity = 9.8
Its just best practise to make unchanging variables constant.
There will be no difference in execution of output.

SwiftUI - using all but one values of Enum for different Pickers

In my app, I need to use a Picker in two different places. In one place, it's to set a property to one of three values: A, B, or C. But, in another place, I want to use the picker to filter a array of items to show either all items (no filter) or only the A, B, or C items.
So, my first attempt was to create an enum like so:
enum Category {
case all
case A
case B
case C
}
Using this, I can filter my list successfully. But, when I try to create a new item in the array, I don't want all to be an option. The user should only see a picker with A, B, and C.
I could try:
enum Category {
case A
case B
case C
}
but then how do I add the all option for the filter picker?
(To address some comments below, here are some more details on the specific thing I'm try to accomplish...
A tournament can be a men's, women's, or mixed tournament. On the screen where I list the tournaments, I want to be able to show all tournaments or just the men's, just the women's or just the mixed tournaments. So, I have a picker that spans the width of the iPhone. Looks and works great.
Obviously, when adding a new item to the list, I have to specify its category, but not "all". Also a picker.
So, in one case, I need three values, in the other case, I need the same three values with "All" added at the beginning.)
You should define your enum without the all case, because all is not a valid Category for an item. (This is a programming guideline known as “make illegal states unrepresentable”.)
enum Category: Hashable, CaseIterable {
case a
case b
case c
}
With that definition, the Picker for setting an item's property can look like this:
Picker("Category", selection: $category) {
ForEach(Category.allCases, id: \.self) { category in
Text(verbatim: "\(category)")
.tag(category)
}
}
Then you should recognize that your filter is optional. You can filter by item category, or you can perform no filtering. So your filter property should be declared optional:
#Binding var categoryFilter: Category?
The Picker for setting the filter then needs to be careful to use optional tags:
Picker("Category Filter", selection: $categoryFilter) {
Text("None")
.tag(Category?.none)
ForEach(Category.allCases, id: \.self) { category in
Text(verbatim: "\(category)")
.tag(Category?.some(category))
}
}
You can create enum in this way
enum Category {
case all
case A
case B
case C
static let categories: [Category] = [.A, .B, .C]
}
And then use the categories array when you need to choose among three.
You can make your enum CaseIterable and then you can use the allCases property when you want to display all enum values, and use a filter when you only want certain cases displayed.
NOTE: In my sample code, you'll need to replace selection: .constant(Category.a) with an #Binding
struct Test {
enum Category: String, CaseIterable {
case all = "All"
case a = "A"
case b = "B"
case c = "C"
}
struct TestView: View {
var body: some View {
VStack {
Picker("Picker Title", selection: .constant(Category.a)) {
ForEach(Category.allCases, id: \.self) { category in
Text(category.rawValue).tag(category)
}
}
Picker("Picker Title", selection: .constant(Category.a)) {
ForEach(Category.allCases.filter({ $0 != .all }), id: \.self) { category in
Text(category.rawValue).tag(category)
}
}
}
}
}
}
To answer your question if I understood you correctly.
Modify your enum to implement the CaseIterable take a look at it below.
enum Category:CaseIterable{
case all
case A
case B
case C
}
Filter out where your category matches the all case. Example below.
let preferredcategory = Category.allCases.filter{ return $0 != Category.all}
To test our result see the code below.
print(preferredcategory.count)
for catname in preferredcategory {
print("Hello, \(catname)!")
}
You can read more on this respective pages.
https://developer.apple.com/documentation/swift/caseiterable
https://developer.apple.com/documentation/swift/caseiterable/2994869-allcases
https://www.hackingwithswift.com/example-code/language/how-to-list-all-cases-in-an-enum-using-caseiterable
Thanks.
So you can use the preferredCategory variable to show the user your category for the one you do not want all to show. you can also use the same filter to show only the part of the enum that has all case as well.
Also, the good thing is you get to keep your enum and reuse it everywhere in your application without having to hardcode values. if you want to show only All you just have to filter out the remaining case.
Some people may want to downvote without a valid reasons but if you believe the approach is wrong feel free to edit or leave a comment on how to improve the solution.

Why are there parenthesis after the value of this variable in Swiftui?

Why are there parenthesis after the value of the entries variable in Swiftui? What does this mean?
func barChartItems() -> [ChartDataEntry] {
var entries = [ChartDataEntry]()
...
}
This is just a syntax to create an object of an empty typed array of ChartDataEntry type.
Creating an Empty Array
var someInts = [Int]() // an empty array of Int
Specifically the parenthesis following the declaration is the init() method call for the class or type. Because an Array type can be created without any init variables they are not needed to initialize the Array type, in-fact Swift best practice is not to add them if you aren't passing anything:
https://developer.apple.com/documentation/swift/array
In the case of a class, you always need the parenthesis to create an object instance from the class.
I built a small SwiftUI example with an #Observable class that has an multiple init() options. You can see how the init() parameters can change the properties of the object by calling the different initializers:
class MultipleInitOptions:ObservableObject {
#Published var name = "hard coded name"
init(){} // has no parameters, just use empty parenthesis
init(name:String) { // has parameters, pass in a name parameter inside parenthesis
self.name = name
}
}
struct TesterView: View {
#StateObject var hardCoded = MultipleInitOptions()
#StateObject var custom = MultipleInitOptions(name:"custom name")
var body: some View {
VStack {
Text("\(hardCoded.name)")
.padding()
Text("\(custom.name)")
.padding()
}
}
}

How can I generate state variables from my database in Swift?

My solution requires that I hard-code placeholder state vars field_1, field_2 that are then passed to my TextField dynamically via an array.
How can I achieve this more dynamically? I would like to have arbitrary Firestore documents from my database map to matching arbitrary TextFields. I use this to generate a settings screen that varies by user.
Here is my current solution, referenced above:
import SwiftUI
struct ParentView: View {
var data = [
["field": "Account Number", "title": "Account Number"],
["field": "Account Number", "title": "Account Number"]
]
#State var settings: [Dictionary<String,Any>] = []
var fields: [Binding<String>] = []
#State var field_1: String = "1"
#State var field_2: String = "2"
init(){
self.fields.append(self.$field_1)
self.fields.append(self.$field_2)
}
func getSettingsObjects() { for item in data { settings.append(item)}}
var body: some View {
VStack {
ForEach(self.$settings.wrappedValue.indices, id: \.self) { i in
TextField(self.settings[i]["title"] as! String, text: self.fields[i])
}
}
.padding(.all,20)
.navigationBarTitle("Settings")
.onAppear {
_ = self.getSettingsObjects()
}
}
}
To handle firebase fields, I like to set up a global struct. Then, every time instead of typing the string, you can user variable DatabaseField.field1 instead.
struct DatabaseField { //Titles for fields in database
static let field1 = "field_one_title"
static let field2 = "field_two_title"
}
Here, you can access the String by calling...
let field: String = DatabaseField.field1
Separately, you could also create a global enum to handle states, items, etc. with a rawValue of type String. I would use this if the State is going to work like a Bool in your viewController.
enum FieldState: String { //ViewController states can be used in functions
case field1 = "field_one_title"
case field2 = "field_two_title"
}
Here, you can set variables to this new type...
var currentState: FieldState = .field1
You can access the String value of this type with...
let currentField: String = currentState.rawValue
As you have realized, you should try to avoid hard-coding variable Strings and database field names whenever possible. I usually make a separate Swift File and store all of the global Structs or Enums for my project there. This way you can hard code it once and never have to touch it again. If you ever have to change the field title, you only need to change it within the Struct/Enum! Side note: your project may include several Structs and Enums. If this is the case, try not to make both a Struct/Enum for the same field/string or it might get confusing to decipher which is which.

How to bind an array and List if the array is a member of ObservableObject?

I want to create MyViewModel which gets data from network and then updates the arrray of results. MyView should subscribe to the $model.results and show List filled with the results.
Unfortunately I get an error about "Type of expression is ambiguous without more context".
How to properly use ForEach for this case?
import SwiftUI
import Combine
class MyViewModel: ObservableObject {
#Published var results: [String] = []
init() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.results = ["Hello", "World", "!!!"]
}
}
}
struct MyView: View {
#ObservedObject var model: MyViewModel
var body: some View {
VStack {
List {
ForEach($model.results) { text in
Text(text)
// ^--- Type of expression is ambiguous without more context
}
}
}
}
}
struct MyView_Previews: PreviewProvider {
static var previews: some View {
MyView(model: MyViewModel())
}
}
P.S. If I replace the model with #State var results: [String] all works fine, but I need have separate class MyViewModel: ObservableObject for my purposes
The fix
Change your ForEach block to
ForEach(model.results, id: \.self) { text in
Text(text)
}
Explanation
SwiftUI's error messages aren't doing you any favors here. The real error message (which you will see if you change Text(text) to Text(text as String) and remove the $ before model.results), is "Generic parameter 'ID' could not be inferred".
In other words, to use ForEach, the elements that you are iterating over need to be uniquely identified in one of two ways.
If the element is a struct or class, you can make it conform to the Identifiable protocol by adding a property var id: Hashable. You don't need the id parameter in this case.
The other option is to specifically tell ForEach what to use as a unique identifier using the id parameter. Update: It is up to you to guarentee that your collection does not have duplicate elements. If two elements have the same ID, any change made to one view (like an offset) will happen to both views.
In this case, we chose option 2 and told ForEach to use the String element itself as the identifier (\.self). We can do this since String conforms to the Hashable protocol.
What about the $?
Most views in SwiftUI only take your app's state and lay out their appearance based on it. In this example, the Text views simply take the information stored in the model and display it. But some views need to be able to reach back and modify your app's state in response to the user:
A Toggle needs to update a Bool value in response to a switch
A Slider needs to update a Double value in response to a slide
A TextField needs to update a String value in response to typing
The way we identify that there should be this two-way communication between app state and a view is by using a Binding<SomeType>. So a Toggle requires you to pass it a Binding<Bool>, a Slider requires a Binding<Double>, and a TextField requires a Binding<String>.
This is where the #State property wrapper (or #Published inside of an #ObservedObject) come in. That property wrapper "wraps" the value it contains in a Binding (along with some other stuff to guarantee SwiftUI knows to update the views when the value changes). If we need to get the value, we can simply refer to myVariable, but if we need the binding, we can use the shorthand $myVariable.
So, in this case, your original code contained ForEach($model.results). In other words, you were telling the compiler, "Iterate over this Binding<[String]>", but Binding is not a collection you can iterate over. Removing the $ says, "Iterate over this [String]," and Array is a collection you can iterate over.