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?
CodePudding user response:
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
}
}
}
CodePudding user response:
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...
}
}