0

I don't know how to read a property that is in a struct from a class that is an Observable Object.

Context:

I'm trying to build an app which consists of 2 views:

  • a custom calendar;
  • a popup with a header 'Daily Joke', date formatted as 'MM-dd-yyyy' and a joke text that is fetched from Firebase using id. When the user clicks on a date in the calendar, the popup appears and shows the joke for a selected date.

The problem is that the 'currentDate' property (holds the value of the selected date) that I reference in the ObservableObject of the 'getJoke' class won't update when the user selects a different date. It always fetches the joke on today's date and not on the one the user has selected.

Here is the code of:

  • the custom calendar (selected date is held in the property 'currentDate')
import SwiftUI
import grpc

struct CustomDatePicker: View {
    
    @State var currentDate: Date
    @State var dailyJokePopUp = false

  //some code here
                        // When the user selects the date, currentDate property changes to the selected date
                        .onTapGesture {
                            currentDate = value.date
                        }

  //  Getting selected day for displaying in dailyJokePopUp
    func getCurrentDay()->String{
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "MM-dd-yyyy"
        let date = dateFormatter.string(from: currentDate)
        return date
    }

    
  • the class which is an @ObservableObject (I use it to add a listener to the Firebase to fetch the joke text by its id. Here I need to read 'currentDate' which is originally declared and changed in CustomDatePicker. I need to do it to check if 'currentDate' matches the id in Firebase (that way the joke text is fetched from Firebase on the selected date)).
    class getJoke : ObservableObject {
        
        @Published var data = [JokeX]()
        @Published var noData = false
        @Published var currentDate = Date()
        
    //some code here including adding SnapShotListener
                
                let callCDP = CustomDatePicker(currentDate: currentDate).getCurrentDay()
                if id == callCDP {
                    self.data.append(joke_text_imported)}

                }
            }

    }
    }
    
  • the popup (I call the result of the @ObservableObject to get the display the text fetched from Firebase)
import SwiftUI

struct dailyJokePopUp: View {
    
    @Binding var show: Bool
    @ObservedObject var Jokes = getJoke()    

    var currentDate: Date = Date()
    
 //some code here

  ForEach(self.Jokes.data){i in
  Text(i.joke_text)
  }

 //some code here
}

I can suspect something is wrong with how I declare properties. I've tried various wrappers (@Binding, @StateObject), but I got confused and it didn't work. Hope someone can be kind enough to help me solve the problem.

Gleb
  • 31
  • 7
  • Hi @Gleb. Welcome to SO. This is a little difficult to follow, since there's *so much* code here. In general, it's good to try to pare things down into a [mre]. It's also good to try to follow the Swift naming conventions (types start with capital letters, properties and variables start with lowercase letters) to make it easier for others to read your code. – jnpdx Apr 15 '22 at 19:31
  • hello @jnpdx, edited the question according to your suggestions – Gleb Apr 15 '22 at 19:41
  • Creating a `View` inside an `ObservableObject` like you've done with `let callCDP = CustomDatePicker(currentDate: currentDate).getCurrentDay()` doesn't work/make sense. Are you trying to get the *same instance* of `CustomDatePicker` that is actually displayed on the screen? – jnpdx Apr 15 '22 at 19:42
  • You are calling CustomDatePicker which is a View from your ObservableObject class, this is definitely not how it should be done. – Joakim Danielson Apr 15 '22 at 19:42
  • @jnpx with `let callCDP = CustomDatePicker(currentDate: currentDate).getCurrentDay()` and then `if id == callCDP` I try to reference the result of the function 'getCurrentDate()' which is a formatted date selected in CustomDatePIcker. It works, but 'id' returns the today's date and not the one the user has selected – Gleb Apr 15 '22 at 21:16
  • That's because you're creating a completely new instance of `CustomDatePicker` -- not the one that the user is interacting with. Looks like someone has left a solution already showing you how to address this by sharing state. – jnpdx Apr 15 '22 at 22:27
  • @jnpdx I tried it, but unfortunately it didn't work because in the Observable Object I still need to read 'currentDate' which is originally declared and changed in CustomDatePicker. I need to do it to check if 'id' matches it (that way the joke text is fetched from Firebase on the selected date). I have a separate struct for a popup called 'dailyJokePopup' and managed to read 'currentDate' that is in CustomDatePicker by using either Binding or simple var, but I don't know how to read it when being in the Observable Object. Could you tell me how to do it? – Gleb Apr 16 '22 at 18:47
  • The solution below seems like it would work perfectly. I read your comment on the answer and it was not clear to me why it doesn't work for your situation. It seems like you're attached to the idea of defining `currentData` inside `CustomDatePicker`, which is *not* where it should be defined. It should be owned by parent state. Again, the answer below shows everything you need. – jnpdx Apr 16 '22 at 19:06
  • @jnpdx I finally got it: I figured that to my knowledge it was impossible to call `CustomDatePicker` that was displayed on the screen from an `ObservableObject` class, so I moved the logic of calling `CustomDatePicker` (which I needed for checking if `currentDate` matches the id) out of the class to the dailyJokePopUp view. Your comment saying that I tried to create a completely new instance of `CustomDatePicker` finally sank into my mind, thanks a lot – Gleb Apr 16 '22 at 23:22

1 Answers1

0

ViewModel

class getJoke: ObservableObject {
  @Published var currentDate = Date()
}

View that can change passing data

struct CustomDatePicker: View {

  @Binding var currentDate: Date

  var body: some View{
      VStack {
          DatePicker(selection: $currentDate, displayedComponents: .date){
                Text("Select your date")
          }
          .datePickerStyle(.compact)
      }
  }
}

And put everything together

    struct ContentView: View {
    
    @StateObject var vm = getJoke()
    
    var body: some View {
        VStack(spacing: 40) {
            CustomDatePicker(currentDate: $vm.currentDate)
            Button {
                print(vm.currentDate)
            } label: {
                Text("Show selected date")
            }

        }
    }
}
Nizami
  • 728
  • 1
  • 6
  • 24
  • thanks for the solution - I tried it, but my example is a bit more complex, so it didn't work because: in the Observable Object I still need to read 'currentDate' which is originally declared and changed in CustomDatePicker to check if 'id' matches it. I have a separate struct for a popup called 'dailyJokePopup' and managed to read 'currentDate' from CustomDatePicker by using either @Binding or simple var, but I don't know how to read 'currentDate' that is in CustomDatePicker :( P.S. I've edited my question to make it more clear. – Gleb Apr 16 '22 at 18:33
  • I finally got it: I figured that to my knowledge it was impossible to call CustomDatePicker that was displayed on the screen from an ObservableObject class, so I moved the logic of calling CustomDatePicker (which I needed for checking if 'currentDate' matches the id) out of the class to the dailyJokePopUp view – Gleb Apr 16 '22 at 23:16
  • @Gleb good job! In my example I try to show the logic how data flows in SwiftUI. In this way you can bind and change any data from view and use this data in another part of app. It seems you have a bit misunderstanding conception and my advice to read about the MVVM architecture pattern. if my answer was useful for you can you mark up it like solved – Nizami Apr 17 '22 at 05:15