Optional dates and how to handle them

Hello,

I am still working on my first project and one thing is making me crazy: optional dates. The task seems easy, the user should be able to set two optional dates (from & to).
To my knowledge, SwiftUI does not provide the functionality to delete the date of a DatePicker, thus I use a Toggle to enable/disable the date. The problem is the handling with the data:

  1. A @State is used by the DatePicker and I have to sync the user input and the data. This is performed by the onChange method of the Toggle (maybe I have to add it for the DatePicker) and works. But this does not work the other way around. For example, when a date is loaded from an URL or from the UserDefaults, I have no access to set the Toggle in SwiftUI. What is a good way to go?

Ideas:
a) Put additional variables in the Note class and ignore these by de/encoder. But how can I handle the optional date, when the DatePicker does not accept these?
b) Add a new class which handles the additional variables (States), but how does the access work between note-, new class and SwiftUI?
c) Extend the Note.loadPassFromAPI method to forward the response.result (success/error), call this method in SwiftUI and handle the Toggle - sounds completely wrong.

  1. How do I force the encoder to load the optional dateString and convert it to a date? I can choose the type for every variable for the decoding, but I think the encoder inherit the type from the variable. Thus a error is thrown, that the type does not fit.

I added a rough example for the fromDate and would be very happy if somebody could give me a hint. I am sure there are better ways than my ideas :slight_smile:

ContentView.swift:

struct ContentView: View {
    @State private var dateFromToggle = false
    @State private var dateFromPicker = Date()
    @ObservedObject var note: Note
    
    var body: some View {
        HStack {
            Toggle("", isOn: $dateFromToggle)
                .labelsHidden()
                .padding()
                .onChange(of: dateFromToggle, perform: {(value) in
                    if !value {
                        note.dateFrom = nil
                    } else {
                        note.dateFrom = dateFromPicker
                    }
                })
            DatePicker("Valid from", selection: $dateFromPicker, displayedComponents: .date)
                .disabled(!dateFromToggle)
        }
    }
}

Note.swift:

import Foundation
import Alamofire

enum CodingPassKeys: CodingKey {
    case dateFrom
}

class Note: Codable, Identifiable, ObservableObject {
    
    @Published var dateFrom: Date? = nil
    
    internal init() {
        self.dateFrom = nil
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingPassKeys.self)
        dateFrom = nil
        let from = try container.decodeIfPresent(String.self, forKey: .dateFrom)
        if from != nil {
            dateFrom = Note.getDateFormatterJson().date(from: from!)
        }
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingPassKeys.self)
        try container.encode(dateFrom, forKey: .dateFrom)
    }
    
    static func getDateFormatterJson() -> DateFormatter {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
        return dateFormatter
    }
    
    func loadPassFromAPI(id: String) {
        guard let url = URL(string: "www.testdomain.de/\(id)") else {
            return
        }
        
        // response = {"dateTo":"2021-12-12 23:59:59"}
        AF.request(url)
            .validate()
            .responseDecodable(of: Note.self) { response in
                switch response.result {
                    case .success(let p):
                        self.dateFrom = p.dateFrom
                }
            }
    }
}

Notes.swift:

import Foundation

final class Notes {
    static let notesKey  = "Notes"
    
    @Published var notes: [Note] {
        didSet {
            save()
        }
    }

    init() {
        if let data = UserDefaults.standard.data(forKey: Notes.notesKey) {
            do {
                let decoder = JSONDecoder()
                decoder.dateDecodingStrategy = .formatted(Note.getDateFormatterJson())
                let decoded = try decoder.decode(Array<Note>.self, from: data)
               
                self.notes = decoded
            } catch let error {
                print(error)
            }
        }
        self.notes = []
    }
    
    private func save() {
        let encoder = JSONEncoder()
        encoder.dateEncodingStrategy = .formatted(Note.getDateFormatterJson())
        if let encoded = try? encoder.encode(self.notes) {
            UserDefaults.standard.set(encoded, forKey: Notes.notesKey)
        }
    }
}

Solved with onChange modifier, which sets the toggles and dates to the right status. If somebody has a better idea if optional dates, please let me know.

This topic was automatically closed after 166 days. New replies are no longer allowed.