TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

SOLVED: SwiftData: How do I create an Add view with an optional date

Forums > Swift

Using SwiftData I have a very basic model with an optional date:

import SwiftData

@Model
class Item {
    var name: String
    var date: Date? = nil

    init(name: String, date: Date) {
        self.name = name
        self.date = date
    }
}

I'm working on the Add view to insert data into that model and no matter what I've tried so far, I get a variety of different errors around the date field. I'm fairly certain I have to hide the date picker, since I don't think I can have it displayed with an empty value. But even then, since I'm binding data to the field, I get this error: Cannot convert value of type 'Binding<Date?>' to expected argument type 'Binding<Date>'

    @State private var newItem = Item(name: "", date: Date())

    var body: some View {

        List{
            Section("Add item"){
                TextField("Enter Name", text: $newItem.name)

                HStack{
                    Text("Select Date")
                    Spacer()
                    Image(systemName: "calendar.badge.plus")
                    if let date = $newItem.date {
                        DatePicker("Date", selection: date)
                    }
                }

I've tried a variety of alternates in the @State declaration around date with nil but those generate their own errors that 'nil' cannot be assigned to type 'Date.Type'

It seems like it's impossible to have a date field that is optional but I've seen other apps pull it off, I simply have no idea how. Any suggestions?

3      

The initializer in your Item class requires a Date as a parameter, rather than a Date?. If you change that, does that help with your problem?

3      

I think it does help, because things are starting to look right but I'm not quite all the way there yet.

I updated the init in the model

@Model
class Item {
    var name: String
    var date: Date? = nil

    init(name: String, date: Date?) {
        self.name = name
        self.date = date
    }
}

And the State to align (I think): @State private var newItem = Item(name: "", date: Date?.none)

But I'm still getting the binding error at the date picker, which I haven't changed: Cannot convert value of type 'Binding<Date?>' to expected argument type 'Binding<Date>'

3      

The reason for error is the DatePicker has init() that require a Binding Date not an optional a bit like your before you change it.

init(
    selection: Binding<Date>,
    displayedComponents: DatePicker<Label>.Components = [.hourAndMinute, .date],
    @ViewBuilder label: () -> Label
)

A few questions for you

  1. Do you really need it to be optional?, (Remember that optionals in CoreData/SwiftData are different then in Swift)
  2. How do you tell if a new item should have a date or not?

I have done a little hack to make it work. Change you init slighty to

class Item {
    var name: String
    var date: Date?

    init(name: String, date: Date? = nil) {
        self.name = name
        self.date = date
    }
}

This will give Item(name: ) and `Item(name: , date: ) in autocomplete

Add two properites to hold the new infomation

@State private var name = ""
@State private var date = Date.now

Add a property to say if a date is you be used

@State private var showingDatePicker = false

Then you can use it in the List

Section("Add item"){
  TextField("Enter Name", text: $name)

  if showingDatePicker {
      HStack{
          Image(systemName: "calendar.badge.plus")

          DatePicker("Date", selection: $date, displayedComponents: .date)
      }
  } else {
      Button {
          withAnimation {
              showingDatePicker.toggle()
          }
      } label: {
          Label("Select Date", systemImage: "calendar.badge.plus")
      }
  }
}

I have added a property for testing

@State private var items = [Item]()

Now add a funtion to change to a Item

func save() {
    let item: Item

    if showingDatePicker {
        item = Item(name: name, date: date)
    } else {
        item = Item(name: name)
    }

    // Add to array but you could save to SwiftData
    items.append(item)
}

For ease I added a Button to List view to "save" a new item but you can do what you want

Button("Add", action: save)

Lastly added a ForEach to show the items array

Section {
    ForEach(items) { item in
        HStack {
            Text(item.name)
            Spacer()
            if let date = item.date {
                Text(date, style: .date)
            }
        }
    }
}

I would serious consider seeing if I could just use non optional Date as you will also have to unwrap it every time you use it!

3      

This is the complete demo project

struct ContentView: View {
    // Added to make list work for testing
    @State private var items = [Item]()

    // Temporary properties to enter data
    @State private var name = ""
    @State private var date = Date.now

    // A property to show DataPicker if date is required
    @State private var showingDatePicker = false

    var body: some View {
        List{
            Section("Add item"){
                TextField("Enter Name", text: $name)

                if showingDatePicker {
                    HStack{
                        Image(systemName: "calendar.badge.plus")

                        DatePicker("Date", selection: $date, displayedComponents: .date)
                    }
                } else {
                    Button {
                        withAnimation {
                            showingDatePicker.toggle()
                        }
                    } label: {
                        Label("Select Date", systemImage: "calendar.badge.plus")
                    }
                }

                Button("Add", action: save)
            }

            // List to show that you adding Items with and with Date
            Section {
                ForEach(items) { item in
                    HStack {
                        Text(item.name)
                        Spacer()
                        if let date = item.date {
                            Text(date, style: .date)
                        }
                    }
                }
            }
        }
    }

    func save() {
        let item: Item

        if showingDatePicker {
            item = Item(name: name, date: date)
        } else {
            item = Item(name: name)
        }

        // Add to array but you could save to SwiftData
        items.append(item)
    }
}

#Preview {
    ContentView()
}

class Item: Identifiable {
    var id = UUID() // <- Added to make list work for testing
    var name: String
    var date: Date?

    init(name: String, date: Date? = nil) {
        self.name = name
        self.date = date
    }
}

6      

Thank you so much! Unfortunatley, I do really need it to be an optional Date for other parts of the app to work correctly. This is unbelievably helpful.

3      

Hacking with Swift is sponsored by Blaze.

SPONSORED Still waiting on your CI build? Speed it up ~3x with Blaze - change one line, pay less, keep your existing GitHub workflows. First 25 HWS readers to use code HACKING at checkout get 50% off the first year. Try it now for free!

Reserve your spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.