How can I generate state variables from my database in Swift? - 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.

Related

Swift Variables and Enums in ForEach Loop for Bindings

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.

How can I create data structure with multiple values in Swift?

I'm learning Swift and I'm wondering how can I create a data structure with multiple values and pass descriptions values from UITableViewController to another viewController? I have tried like this
struct faculty {
var name = String()
var descriptions = (String)[]
}
let faculties = [name: "Faculties", description: ["Study1", "Study2"]]
I have successfully managed to list an array ["Test1", "Test2"] in tableView.
There are a couple of issues
An empty string array is [String]().
description is not equal to descriptions.
An instance must be created with Type(parameter1:parameter2:).
And structs are supposed to be named with starting capital letter.
struct Faculty {
var name = String()
var descriptions = [String]()
}
let faculties = [Faculty(name: "Faculties", descriptions: ["Study1", "Study2"])]
However default values are not needed. This is also valid
struct Faculty {
let name : String
var descriptions : [String]
}

Use an object to collect form data and set default value in Swift

I'm brand new in Swift development, and I can't for the life of me figure this out. All I want to do is use an object to collect a forms data, save it to Realm, and then send it to the server via API.
In every example I've found, people are creating a new State variable for each element in their form. This seems unpractical for forms with many fields, so I tried to just create an object with properties that match the form fields I need. If I don't try to set any default values, this works as I expect. But when I try to set some default values for the form in the init(), I get errors that I don't know how to resolve. Here's some partial code:
The object that will be used to collect the form data:
class RecordObject: ObservableObject {
var routeId: Int?
var typeId: Int?
var inDate: Date?
var outDate: Date?
var nextDate: Date?
// .... more properties
}
And what I want to do is in the View, set some default values in the init() that need some logic to derive the value:
In the View:
struct AddLauncherView: View {
var route: Route // this is passed from a previous view that lists all the routes
#StateObject var record: RecordObject = RecordObject() // this is where I want to store all the form data
init(){
_record.routeId = self.route.id // I get the error: Referencing property 'routeId' requires wrapped value of type 'RecordObject'
self.record.inDate = Date() // this gives the error: 'self' used before all stored properties are initialized
//self.record.nextDate = here I need to do some date math to figure out the next date
}
var body: some View {
Form{
DatePicker("In Date", selection: $record.inDate, displayedComponents: .date)
// .... more form elements
}
}
}
I know I could add default values in the RecordObject class, but there are some properties that will need some logic to assign the default value.
Can someone help out a Swift noob and give me some pointers for making this work? Do I really need to create a State var for each form field in the View?
If you did use a class (ObservableObject), you'd want the properties to be annotated with #Published. However, it's probably a better idea to use a struct with a #State variable that contains all of the various properties you need. That may look like this:
struct Record {
var routeId: Int?
var typeId: Int?
var inDate: Date?
var outDate: Date?
var nextDate: Date?
}
struct AddLauncherView: View {
var route: Route
#State var record: Record
init(route: Route) {
self.route = route
_record = State(initialValue: Record(routeId: route.id,
typeId: nil,
inDate: Date(),
outDate: nil,
nextDate: nil))
}
var body: some View {
Form{
DatePicker("In Date",
selection: Binding<Date>(get: {record.inDate ?? Date()},
//custom binding only necessary if inDate is Date? -- if you make it just Date, you can bind directly to $record.inDate
set: {record.inDate = $0}),
displayedComponents: .date)
// .... more form elements
}
}
}
You are using the ObservableObject incorrectly. You data model should be a struct, and then the class publishes values that are of the type of the struct, so:
struct Record: Identifiable {
// First, save yourself some grief later and make it conform to Identifiable
let id = UUID()
// The listed variables are probably non-optional in your data model.
// If they are optional, mark them as optional, otherwise
var routeId: Int
var typeId: Int
var inDate: Date
// These are more likely optional
var outDate: Date?
var nextDate: Date?
// .... more properties
}
class RecordObject: ObservableObject {
// Make the source of truth #Published so things are updated in your view
#Published var records: [Record] = []
// OR use an init
#Published var records: [Record]
init(records: [Records] {
self.records = records
// or call some function that imports/creates the records...
}
}

SwiftUI ForEach Binding compile time error looks like not for-each

I'm starting with SwiftUI and following WWDC videos I'm starting with #State and #Binding between two views. I got a display right, but don't get how to make back-forth read-write what was not include in WWDC videos.
I have model classes:
class Manufacturer {
let name: String
var models: [Model] = []
init(name: String, models: [Model]) {
self.name = name
self.models = models
}
}
class Model: Identifiable {
var name: String = ""
init(name: String) {
self.name = name
}
}
Then I have a drawing code to display that work as expected:
var body: some View {
VStack {
ForEach(manufacturer.models) { model in
Text(model.name).padding()
}
}.padding()
}
and I see this:
Canvas preview picture
But now I want to modify my code to allows editing this models displayed and save it to my model #Binding so I've change view to:
var body: some View {
VStack {
ForEach(self.$manufacturer.models) { item in
Text(item.name)
}
}.padding()
}
But getting and error in ForEach line:
Generic parameter 'ID' could not be inferred
What ID parameter? I'm clueless here... I thought Identifiable acting as identifier here.
My question is then:
I have one view (ContentView) that "holds" my datasource as #State variable. Then I'm passing this as #Binding to my ManufacturerView want to edit this in List with ForEach fill but cannot get for each binding working - how can I do that?
First, I'm assuming you have something like:
#ObservedObject var manufacturer: Manufacturer
otherwise you wouldn't have self.$manufacturer to begin with (which also requires Manufacturer to conform to ObservableObject).
self.$manufacturer.models is a type of Binding<[Model]>, and as such it's not a RandomAccessCollection, like self.manufacturer.models, which is one of the overloads that ForEach.init accepts.
And if you use ForEach(self.manufacturer.models) { item in ... }, then item isn't going to be a binding, which is what you'd need for, say, a TextField.
A way around that is to iterate over indices, and then bind to $manufacturer.models[index].name:
ForEach(manufacturer.indices) { index in
TextField("model name", self.$manufacturer.models[index].name)
}
In addition to that, I'd suggest you make Model (and possibly even Manufacturer) a value-type, since it appears to be just a storage of data:
struct Model: Identifiable {
var id: UUID = .init()
var name: String = ""
}
This isn't going to help with this problem, but it will eliminate possible issues with values not updating, since SwiftUI wouldn't detect a change.

How do I use an existing property in a property wrapper when self hasn't been initialized? (SwiftUI)

I have a struct with two variables inside property wrappers. One of the variables is supposed to be computed from the other. When I try to do this, I get the following error:
Cannot use instance member 'name' within property initializer; property initializers run before 'self' is available.
I tried assigning a temporary value to these variables, and then re-assigning them within a custom init() function, but that doesn't seem to work ether. I made a simplified version of the code to see if I could isolate the issue.
import SwiftUI
struct Person {
#State var name: String = ""
#State var nameTag: NameTag = NameTag(words: "")
init(name: String) {
// not changing name and nameTag
self.name = name
nameTag = NameTag(words: "Hi, my name is \(name).")
}
}
class NameTag {
var words: String
init(words: String) {
self.words = words
}
}
var me = Person(name: "Myself")
// still set to initial values
me.name
me.nameTag.words
I noticed that when I changed nameTag to an #ObservedObject, rather than #State, it was able to be re-assigned correctly. Although I don't believe I can change name to #ObservedObject. Could anyone tell me what I'm doing wrong?
To use property wrappers in initializers, you use the variable names with preceding underscores.
And with State, you use init(initialValue:).
struct Person {
#State var name: String
#State var nameTag: NameTag
init(name: String) {
_name = .init(initialValue: name)
_nameTag = .init( initialValue: .init(words: name) )
}
}
Here's what a #State property really looks like, as your tear down levels of syntactic sugar:
name
_name.wrappedValue
$name.wrappedValue
_name.projectedValue.wrappedValue
You can't use the underscore-name outside of the initial type definition.