4

I have a simple View with a custom month selector. Each time you click chevron left or right,

Inside DateView I had two private vars: startDateOfMonth2: Date and endDateOfMonth2: Date. But they were only available inside DateView.

QUESTION

How to make those two variables available in other views?

I have tried to add them as @Published vars, but I am getting an error:

Property wrapper cannot be applied to a computed property

I have found just a few similar questions, but I cannot manage to use answers from them with my code.

import SwiftUI

class SelectedDate: ObservableObject {
    @Published var selectedMonth: Date = Date()
    
    @Published var startDateOfMonth2: Date {
        let components = Calendar.current.dateComponents([.year, .month], from: selectedMonth)
        let startOfMonth = Calendar.current.date(from: components)!
        return startOfMonth
    }

    @Published var endDateOfMonth2: Date {
        var components = Calendar.current.dateComponents([.year, .month], from: selectedMonth)
        components.month = (components.month ?? 0) + 1
        let endOfMonth = Calendar.current.date(from: components)!
        return endOfMonth
    }
}

struct DateView: View {
    @EnvironmentObject var selectedDate: SelectedDate
    
    static let dateFormat: DateFormatter = {
        let formatter = DateFormatter()
        formatter.setLocalizedDateFormatFromTemplate("yyyy MMMM")
        return formatter
    }()
    
    var body: some View {
        
        HStack {
            
            Image(systemName: "chevron.left")
                .frame(width: 50, height: 50)
                .contentShape(Rectangle())
                .onTapGesture {
                    self.changeMonthBy(-1)
            }
            
            Spacer()

            Text("\(selectedDate.selectedMonth, formatter: Self.dateFormat)")
            
            Spacer()
            
            Image(systemName: "chevron.right")
                .frame(width: 50, height: 50)
                .contentShape(Rectangle())
                .onTapGesture {
                    self.changeMonthBy(1)
            }
            
        }
        .padding(EdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5))
        .background(Color.yellow)
    }
    
    func changeMonthBy(_ months: Int) {
        if let selectedMonth = Calendar.current.date(byAdding: .month, value: months, to: selectedDate.selectedMonth) {
            self.selectedDate.selectedMonth = selectedMonth
        }
    }
}
mallow
  • 2,368
  • 2
  • 22
  • 63

1 Answers1

9

No need to declare your computed values as Published, as they are depending on a Published value. When the published value changed, they get recalculated.

class SelectedDate: ObservableObject {
    @Published var selectedMonth: Date = Date()
    
    var startDateOfMonth2: Date {
        let components = Calendar.current.dateComponents([.year, .month], from: self.selectedMonth)
        let startOfMonth = Calendar.current.date(from: components)!
        
        print(startOfMonth)
        return startOfMonth
    }

    var endDateOfMonth2: Date {
        var components = Calendar.current.dateComponents([.year, .month], from: self.selectedMonth)
        components.month = (components.month ?? 0) + 1
        let endOfMonth = Calendar.current.date(from: components)!

        print(endOfMonth)
        return endOfMonth
    }
}

When you print endDateOfMonth2 on click, you will see that it is changing.

davidev
  • 7,694
  • 5
  • 21
  • 56
  • Thank you, but I think that this way I still cannot access endDateOfMonth2 outside of class. I have just added `print("\(startDateOfMonth2)")` to .onTapGesture in my code and I am getting an error: `Use of unresolved identifier 'startDateOfMonth2'` – mallow Jul 05 '20 at 13:03
  • 1
    Worked for me with print(selectedDate.endDateOfMonth2) – davidev Jul 05 '20 at 13:05
  • Oh! Great. print(selectedDate.endDateOfMonth2) works and I can access it in another view. Thank you! :) So as far as I am using vars that depend on a @Published var there is no need to declare new published vars? I guess it makes the app use less memory, right? As in my case, I just need to store 1 published var in memory, not 3. Am I correct? – mallow Jul 05 '20 at 13:11
  • 1
    You can not change computed values itself. It is always computed. So the question is, when do you need to compute it again? It will computed if the Published value changes inside the class. As the computed value depends on that Published value, it will recalculated when the Published changes. So basically, the Published will change, the computed will recalculate and hence the Published changed, your view will reload and both values will be updated. – davidev Jul 05 '20 at 13:22
  • 1
    Cool. Thank you very much for this further explanation. In my case I only need the computed values to update based on the Published value. So it works perfectly with your solution :) – mallow Jul 05 '20 at 13:25