View not refreshed when .onAppear runs SwiftUI

I made a little example replicating what's going on, if you run it, and click on

set future date
, and wit 5 seconds, you'll see that the box hasn't changed color, after that, click on
Go to view 2
and go back to view 1 and you'll see how the box color changes... that's what's happening in my code too:


import SwiftUI

struct ContentView: View {

    @State var past = Date()
    @State var futuredate = Date()
    
    
    var body: some View {
            
        NavigationView {
            VStack {
                NavigationLink(destination: DetailView())
                { Text("Go to view 2") }
                
                Button("set future date") {
                    self.futuredate = self.past.addingTimeInterval(5)
                }
                
                VStack {
                    if (past < futuredate) {
                        Button(action: {
                        }) {
                            Text("")
                        }
                        .padding()
                        .background(Color.blue)
                    } else {
                        Button(action: {
                        }) {
                            Text("")
                        }
                        .padding()
                        .background(Color.black)
                    }
                }
                
                .onAppear {
                    self.past = Date()
                }
            }
            
        }
    }

}


struct DetailView: View {
    
    @Environment(\.presentationMode) var presentationMode: Binding
    
    var body: some View {
       Text("View 2")
    }
}


Any help would be appreciated

Answered by OOPer in 419579022

It may be very hard.


You may start a timer to check if `futuredate` has come or not every second.

struct ContentView: View {
    
    @State var now = Date()
    @State var futuredate = Date()
    
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    
    var body: some View {
        
        NavigationView {
            VStack {
                NavigationLink(destination: DetailView()) {
                    Text("Go to view 2")
                }
                
                Button("set future date") {
                    self.futuredate = self.now.addingTimeInterval(5)
                }
                
                VStack {
                    if now < futuredate {
                        Button(action: {
                        }) {
                            Text("")
                        }
                        .padding()
                        .background(Color.blue)
                    } else {
                        Button(action: {
                        }) {
                            Text("")
                        }
                        .padding()
                        .background(Color.black)
                    }
                }
            }
        }
        .onReceive(timer) { _ in
            self.now = Date()
        }
    }
}

But the timer might be stopped when got into background, so you might need to re-invoke it when in foreground again.

I tried this exact code (XCode 11.3 Mojave and XCode 11.4.1 Catalina) and the box immediately changed color from black to blue.

Isn't it what you expect ?


When I go to second view and return, box is black again.


There is something curious in this code:

futuredate is initited to Date(), which is the time when view loads

Same for past

Then onAppear, you reste past to Date(). So at beginning, start, futuredate < past

Hence the blackbox


When you hit button, you set future after past.

Then, line 17, you set futureDate. We always have past < futuredate

No need to wait, VStack redraws with blue button

No, what I'm expecting is when the value changes, when future date becomes less than actual date, the UI change automatically without me having to go to second view and come back. Am I doing that the right way?


In my code it's for a button to change when it's passed due. The thing is that the user could quit the app or minimize it.

Am I doing that the right way?

Maybe no. As far as I read your code, it works as expected. When do you think future date becomes less than actual date ?


With your code, futuredate < date becomes true only when onAppear is triggered, first appearance or back from view 2.

Ok, I thought of a better way of explaining it. How can I make the UI update itself without the user having to interact with it?


For example: I have the user that clicks the button, Eat, and that records the current time and calculates future time when that is due. Since I can't put a function in the view (which is very annoying), I thought to check for time on .onAppear(), and if the next time the user is suppoused to eat is less than the current time, then change the value stored in DB to "overdue", if that value is overdue, display the button with another look.


So what I need is the UI to change when that value becomes overdue without me having to click a button, if it's still not clear let me know so I can think on a simpler way of explaining it.


It's imposible that for the UI to change the user has to interact with it everytime, is it?

How can I make the UI update itself without the user having to interact with it?

One thing is clear, your current code is not for your purpose.

True, agreed. Could you point me out to the simplest example possible for when the UI might change automatically without the user interaccion based on some logic happening in the future?

So create a new thread with async(after) where you will call for color change.

Note that the call itself to change color should be in the main thread.

There's no automatically in programming. You need to clarify what is some logic happening in the future.


For example, I can show you a code which updates UI 5 seconds after one button is tapped, but that may not be what you want.

Actually that might be exactly what I need, do you have such code? Also I might quit the app, is that 5 seconds going to count while the app is in the background?

Also I might quit the app, is that 5 seconds going to count while the app is in the background?

No. Once your app gets into the background, you do not have much control on it.

If that is your current requirement of your app, you may need to re-consider the spec of your app.

Yep, that is the current specs of the app. The user minimizes the app, locks the screen, when comes back, the button shoud change according to some variable (for example, the counter you mentioned).


What would be the best approach for this?

Accepted Answer

It may be very hard.


You may start a timer to check if `futuredate` has come or not every second.

struct ContentView: View {
    
    @State var now = Date()
    @State var futuredate = Date()
    
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    
    var body: some View {
        
        NavigationView {
            VStack {
                NavigationLink(destination: DetailView()) {
                    Text("Go to view 2")
                }
                
                Button("set future date") {
                    self.futuredate = self.now.addingTimeInterval(5)
                }
                
                VStack {
                    if now < futuredate {
                        Button(action: {
                        }) {
                            Text("")
                        }
                        .padding()
                        .background(Color.blue)
                    } else {
                        Button(action: {
                        }) {
                            Text("")
                        }
                        .padding()
                        .background(Color.black)
                    }
                }
            }
        }
        .onReceive(timer) { _ in
            self.now = Date()
        }
    }
}

But the timer might be stopped when got into background, so you might need to re-invoke it when in foreground again.

Actually that works very well. For this app that will do the trick, the next one I might have to dig deeper, but that there solves the current problem. Many thanks 🙂

Happy to hear this works for you. When you find some difficulty with the next one, you know you can start a new thread for it.

Good luck and good app.

View not refreshed when .onAppear runs SwiftUI
 
 
Q