1

How to get startDateOfMonth and endDateOfMonth based on selected date in SwiftUI?

I have found some answers for Swift (DateComponents), but couldn't make it work with SwiftUI.

Why I need this: I am going to use dynamic filters using predicate to filter all the data in the currently selected month (using custom control to switch months). But first I need to get the start and end dates per selected month.

EXAMPLE code:

ContentView.swift

import SwiftUI

struct ContentView: View {

    @State var currentDate = Date()

    // How to make startDateOfMonth and endDateOfMonth dependent on selected month?
    @State private var startDateOfMonth = "1st January"
    @State private var endDateOfMonth = "31st January"      

    var body: some View {

        VStack {
            DateView(date: $currentDate)

            Text("\(currentDate)")

            Text(startDateOfMonth)
            Text(endDateOfMonth)

        }

    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

DateView.swift

import SwiftUI

struct DateView: View {
    static let dateFormat: DateFormatter = {
        let formatter = DateFormatter()
        formatter.setLocalizedDateFormatFromTemplate("yyyy MMMM")
        return formatter
    }()

    @Binding var date : Date

    var body: some View {

        HStack {

            Image(systemName: "chevron.left")
                .padding()
                .onTapGesture {
                    print("Month -1")
                    self.changeDateBy(-1)
            }

            Spacer()

            Text("\(date, formatter: Self.dateFormat)")

            Spacer()

            Image(systemName: "chevron.right")
                .padding()
                .onTapGesture {
                    print("Month +1")
                    self.changeDateBy(1)
            }

        }
        .padding(EdgeInsets(top: 5, leading: 10, bottom: 5, trailing: 10))
        .background(Color.yellow)
    }

    func changeDateBy(_ months: Int) {
        if let date = Calendar.current.date(byAdding: .month, value: months, to: date) {
            self.date = date
        }
    }
}

struct DateView_Previews: PreviewProvider {

    struct BindingTestHolder: View {
        @State var testItem: Date = Date()
        var body: some View {
            DateView(date: $testItem)
        }
    }

    static var previews: some View {
        BindingTestHolder()
    }
}
mallow
  • 2,368
  • 2
  • 22
  • 63
  • 1
    It has nothing to do with swiftUI here. It's a question can be solved with `calendar` – E.Coms Jan 07 '20 at 04:46
  • Thanks for feedback, but as I wrote I have found some examples for Swift and I couldn't make it work with SwiftUI. I understand that this calendar is normal Swift, but I don't know how to use it with my SwiftUI code. – mallow Jan 07 '20 at 06:42

1 Answers1

1

I managed to solve it by the following implementation of ContentView

@State var currentDate = Date()

private var startDateOfMonth: String {
    let components = Calendar.current.dateComponents([.year, .month], from: currentDate)
    let startOfMonth = Calendar.current.date(from: components)!
    return format(date: startOfMonth)
}

private var endDateOfMonth: String {
    var components = Calendar.current.dateComponents([.year, .month], from: currentDate)
    components.month = (components.month ?? 0) + 1
    components.hour = (components.hour ?? 0) - 1
    let endOfMonth = Calendar.current.date(from: components)!
    return format(date: endOfMonth)
}

var body: some View {
    VStack {
        DateView(date: $currentDate)
        Text("\(currentDate)")
        Text(startDateOfMonth)
        Text(endDateOfMonth)
    }
}

private func format(date: Date) -> String {
    let dateFormatter = DateFormatter()
    dateFormatter.dateStyle = .medium
    return dateFormatter.string(from: date)
}

Because currentDate is changed by DateView through Binding the body computed property will be invoked thus startDateOfMonth and endDateOfMonth computed properties will return the updated values.

Gergely
  • 448
  • 3
  • 13
  • Thanks! In general, it works very good. I have one problem, though. I have commented out this Date->String function, because in fact I don't need to change it to String. And changed types of vars to Date. After this, I have noticed that endDateOfMonth was set to be last day at 11PM. Which means that 11:05PM for example would be excluded from filtering. So, I have changed `components.hour = (components.hour ?? 0) - 1` to be `components.second = (components.second ?? 0) - 1`. Is this correct? – mallow Jan 07 '20 at 13:43
  • Or... I have another idea. I could comment out this line: `components.hour = (components.hour ?? 0) - 1` completely. Which would make endDateOfMonth to be first minute of next month. And I would use this in predicate as date < endDateOfMonth AND date >= startDateOfMonth. Sounds good? :) – mallow Jan 07 '20 at 13:47
  • 1
    I think if you need it only for filtering and not for displaying then you can just remove that line, yes. – Gergely Jan 07 '20 at 13:57