0

In the example below, there are 2 views linked through NavigationLink in the NavigationView: the root ContentView and the subordinate ListView.

    struct ContentView: View {
    
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    
    @State private var counter = 0
    
    var body: some View {
        
        NavigationView{
            
            VStack{
                
                Text("counter: \(counter)")
                    .onReceive(timer) { _ in
                        counter += 1
                    }
            
                NavigationLink(destination: ListView()) {
                    Text("List").padding()
                }
                
            }
            
        }
        
    }
}

struct ListView: View {
    
    let items = ["first", "second", "third"]
    
    init() {
        print(Date(), "init ListView")
    }
    
    var body: some View {
        
        List{
            
            ForEach(items, id: \.self) { item in
                NavigationLink(destination: ItemView(item: item)) {
                    Text(item).padding()
                }
            }
        }
        
    }
    
}

In the ContentView, the counter variable changes every second, which causes the ContentView to be redrawn and the ListView to be permanently initialized. The ListView is initialized and the "init ListView" displayed every second regardless of what is currently on the ContentView or ListView. From the point of view of the data model, the ListView does not depend in any way on counter and on the state of the ContentView.

In a real application, the situation is much more complicated and costly. ContentView displays a MapView whose state is constantly changing, and ListView displays a list of recorded routes. The ListView initialization contains some pretty expensive logic. Constantly initializing the ListView takes time, although it is really only required when switching to the ListView, and not every time the ContentView changes state.

What is the correct way to make the NavigationView work while getting rid of the ListView's dependency on the ContentView state change?

2

1 Answer 1

2

As lorem ipsum said, the initialization and updates of the content is part of how SwiftUI works and is to be expected. To overcome that, just create a new view that handle the timer, so that only your this last will be updated. Your ContentView should look more like :

struct ContentView: View {
    var body: some View {
        NavigationView{
            VStack{
                CounterView()
                NavigationLink(destination: ListView()) {
                    Text("List").padding()
                }
            }
        }
    }
}

And your CounterView :

struct CounterView: View {
    @State private var counter = 0
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

    var body: some View {
        Text("counter: \(counter)")
            .onReceive(timer) { _ in
                counter += 1
            }
    }
}

However, if you need to access your Timer variable through out your without necessarily update each single view, you might consider making a singletone accessible by calling simply "Timer.secondsUpdater".

extension Timer {
    static let secondsUpdater = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
}

Your CounterView would endup calling timer like this :

struct CounterView: View {
    @State private var counter = 0

    var body: some View {
        Text("counter: \(counter)")
            .onReceive(Timer.secondsUpdater) { _ in
                counter += 1
            }
    }
}

Hope it was helpful 🙂

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you so much I almost got to this decision myself, but your answer describes everything very clearly))

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.