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:
- 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.
- 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
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)
}
}
}