skip to Main Content

I’m having trouble displaying the day from a date. I have multiple dates in the format "2022-12-27T04:00:31.980". I want the "Picker" to be able to select the date after the day of the month. The problem arises when I have the same date but different time, because "Picker" shows me the day as many times as it appears with a different time.

This is my "Picker" code:

Picker(selection: $currentTab, label: Text("Wybierz dzień")) {
                    
                    let calendar = Calendar.current
                    ForEach(temperatureCall.chartDataEntries) { item in
                        let components = calendar.dateComponents([.day, .month, .year], from: item.lastupdated)
                        let day = components.day!
                        let month = components.month!
                        let year = components.year!
                        Text("(String(day)).(String(month)).(String(year))")
                            .tag(String(day))
                    }
                }

enter image description here

I would like each day to be unique and to be able to choose a day no matter how many times it is repeated with a different time.

let calendar = Calendar.current
let dates = Set(temperatureCall.chartDataEntries.map { calendar.startOfDay(for: $0.lastupdated) })

                Picker(selection: $currentTab, label: Text("Wybierz dzień")) {
                    ForEach(dates) { item in
                        let components = calendar.dateComponents([.day, .month, .year], from: item)
                        let day = components.day!
                        let month = components.month!
                        let year = components.year!

                        Text("(String(day)).(String(month)).(String(year))")
                            .tag(String(day))
                    }
                }

I did something like this, unfortunately I get an error:
Cannot convert value of type 'Set<Date>' to expected argument type 'Binding<C>'

3

Answers


  1. I would map to start of day and use Set to get unique values

    Set(dates.map { calendar.startOfDay(for: $0)})
    

    This could be done as a computed property in temperatureCall and then you can use this property in your ForEach instead

    var uniqueDates: [Date] {
        Set(dates.map { calendar.startOfDay(for: $0) }).sorted()
    }
    

    Another option is to use OrderedSet from the Swift Collections package which will not only keep only the unique values but also maintain the original order of the input array

    var uniqueDates: [Date] {
        var ordered = OrderedSet<Date>()
        dates.forEach {
            ordered.append(calendar.startOfDay(for: $0))
        }
        return ordered.elements
    }
    
    Login or Signup to reply.
  2. Starting from @JoakimDanielson answer, code that preserves the original order and has ≈O(1) performance would look something like this (It could be made shorter. I left it verbose for ease of reading.)

    var uniqueDates: [Date] {
        var result = [Date]()
        var uniqueDatesSet : Set<Date> = [] // Use a set to test for uniqueness
        let midnights = dates.map { Calendar.current.startOfDay(for: $0) }
        for date in midnights {
            if !uniqueDatesSet.contains(date) {
                uniqueDatesSet.insert(date)
                result.append(date)
            }
        }
        return result
    }
    

    Here is some working test code using the above, as a command line tool. (I wrote the test code to generate dates in random order so you can tell that the uniqueDates code preserves the original order of the array.)

    Note that calculating unique dates would take a non-trivial amount of time for a large array of dates, so using a computed property isn’t the best way to do this. It would be better to compute the unique dates the first time you ask for it after the source array of dates changes.

    import Foundation
    
    var dates: [Date] = []
    let secondsInDay = Double(24 * 60 * 60)
    
    for _ in 1...20 {
    let now = Date()
        let random = Double.random(in: (-secondsInDay * 5.0)...secondsInDay*5)
        dates.append(now.addingTimeInterval(random))
    }
    
    var uniqueDates: [Date] {
        var result = [Date]()
        var uniqueDatesSet : Set<Date> = [] // Use a set to test for uniqueness
        let midnights = dates.map { Calendar.current.startOfDay(for: $0) }
        for date in midnights {
            if !uniqueDatesSet.contains(date) {
                uniqueDatesSet.insert(date)
                result.append(date)
            }
        }
        return result
    }
    
    print("Dates:")
    dates.forEach { print(DateFormatter.localizedString(from:$0, dateStyle: .short, timeStyle: .none)) }
    print("Unique dates:")
    uniqueDates.forEach { print(DateFormatter.localizedString(from:$0, dateStyle: .short, timeStyle: .none)) }
    

    A test run of that outputs:

    Dates:
    1/12/23
    1/11/23
    1/9/23
    1/11/23
    1/4/23
    1/6/23
    1/7/23
    1/9/23
    1/12/23
    1/7/23
    1/3/23
    1/10/23
    1/8/23
    1/12/23
    1/6/23
    1/8/23
    1/6/23
    1/11/23
    1/4/23
    1/3/23
    Unique dates:
    1/12/23
    1/11/23
    1/9/23
    1/4/23
    1/6/23
    1/7/23
    1/3/23
    1/10/23
    1/8/23
    
    Login or Signup to reply.
  3. import SwiftUI
    import Algorithms
    
    struct ContentView: View {
        let dates = [Date(), Date(), Date().addingTimeInterval(500000)]
        
        @State var selection: Date?
        @Environment(.calendar) var calendar
        
        var uniqueDates: [Date] {
            dates.uniqued { date in
                calendar.startOfDay(for: date)
            }
        }
        
        var body: some View {
            if let date = selection {
                Text(date, style: .date)
            }else {
                Text("No Selection")
            }
            
            Picker(selection: $selection, label: Text("Date")) {
                Text("-")
                    .tag(nil as Date?)
                ForEach(uniqueDates, id: .self) { date in
                    Text(date, style: .date)
                        .tag(date as Date?)
                }
            }
            .pickerStyle(.wheel)
        }
    }
    

    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search