Home > Software design >  Bind @State to optional @Binding in the same struct SwiftUI
Bind @State to optional @Binding in the same struct SwiftUI

Time:03-12

I am creating a View that may or may not receive a binding:


import SwiftUI

struct AssignmentEditMenu: View {
    
    @EnvironmentObject var planner: Planner
    @Environment(\.dismiss) var dismiss
    
    var isEditing // set to true if assignment is not nil
    var assignment: Binding<Assignment>?
        
    @State var newAssignment = assignment ?? Assignment()
    // if assignment is not nil, create a copy of it and set newAssignment equal to the copy
    // if assignment is nil, initialize a new struct and set newAssignment equal to it
    
    var body: some View {
        
        NavigationView {
            Form {
                nameSection
                dateSection
            }
            .navigationTitle(Text("New Assignment"))
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button(isEditing ? "Add" : "Done") {
                        isEditing ? (assignment = newAssignment) : planner.addAssignment(newAssignment)
                        dismiss()
                    }
                }
                ToolbarItem(placement: .navigationBarLeading) {
                    Button("Cancel") {
                        dismiss()
                    }
                }
            }
        }
    }
    
    var nameSection: some View {
        Section {
            TextField("Name", text: $newAssignment.name)
            Picker("Course", selection: $newAssignment.course) {
                ForEach(planner.courses) { course in
                    Text("\(course.name)").tag(course)
                }
                
            }
        }
    }
    
    var dateSection: some View {
        Section {
            DatePicker(
                selection: $newAssignment.assignedDate,
                displayedComponents: [.date, .hourAndMinute]
            ) {
                Text("Assigned:")
                    .lineLimit(1)
                    .allowsTightening(true)
            }
            DatePicker(
                selection: $newAssignment.dueDate
            ) {
                Text("Due:")
                    .lineLimit(1)
                    .allowsTightening(true)
            }
        }
    }
}

What I am trying to do is if a binding to an Assignment is passed in here, then the View is in Edit Mode. newAssignment will be set to the same values as the assignment binding but not reference it at all. It will collect changes and when complete, set that assignment binding equal to itself, thus saving the changes. The reason for using newAssignment is so that if the user cancels (this view will be a sheet) changes are discarded since newAssignment is a @State var.

When no Assignment binding is passed in the View is in Create mode. newAssignment collects the changes and is added to the store via planner.

Assignment implementation:


struct Assignment: Identifiable, Equatable, Hashable  {
    var name: String
    var assignedDate: Date
    var dueDate: Date
    var course: Course
    var percentCompleted: Double
    let id: UUID
    
    init(
        name: String = "",
        assignedDate: Date = Date(),
        dueDate: Date = Calendar.current.nextDate(after: Date(), matching: .init(hour: 0, minute: 0), matchingPolicy: .strict)!,
        course: Course = Course()
    ) {
        self.name = name
        self.assignedDate = assignedDate
        self.dueDate = dueDate
        self.course = course
        self.id = UUID()
        self.percentCompleted = 0.0
    }
}

struct Course: Identifiable, Equatable, Hashable {
    var name: String
    let id: UUID
    
    init(_ name: String = "Course") {
        self.name = name
        self.id = UUID()
    }
}

CodePudding user response:

Add a custom init. With that, newAssignment var is also not needed.

struct AssignmentEditMenu: View {
    
    @EnvironmentObject var planner: Planner
    @Environment(\.dismiss) var dismiss
    
    var isEditing // set to true if assignment is not nil

   var assignment: Binding<Assignment>
    
    init(a: Binding<Assignment> = .constant(Assignment())) {
        assignment = a 
    }

.....

}
  • Related