SwiftUI: how to make network request when value of binding changes?

Viewed 550

I'm trying to update my chart view when I swipe to another page in PagerView. When I swipe currentIndex changes. But I don't understand how to get notified when currentIndex changes. Here is my code:

struct MainView: View {

    let network = Network()

    @State private var currentIndex: Int = 0
    @State private var sources: [Source] = []

    var body: some View {
        return ZStack {
            ...
            VStack {
                Text("Температура")
                    .defaultFont(font: .system(size: 30), weight: .regular)

                PagerView(pagesCount: self.sources.count, currentIndex: self.$currentIndex) {
                    ForEach(self.sources, id: \.self) { t in
                        ...
                    }
                }

                if !sources.isEmpty {
                    ChartView(sourceId: $sources[currentIndex].id)
                } else {
                    Spacer()
                }
            }
        }
    }

}

PagerView binds to currentIndex so when I swipe a page currentIndex changes. ChartView has a method loadData and I want to call it when I swipe a page to load new chart depends on sources[currentIndex].id. Here is code of ChartView:

struct ChartView: View {

    @Binding var sourceId: String {
        didSet {
            loadData()
        }
    }
    @State private var points: [TemperaturePoint] = []

    var body: some View {
        GeometryReader { proxy in
            ChartView.makePath(by: self.points, with: proxy)
                .stroke(Color.white, lineWidth: 2)
        }.onAppear(perform: loadData)
    }

    func loadData() {
        network.getPoints(sourceId: sourceId) { response in
            switch response {
            case .result(let array):
                self.points = TemperaturePoint.smooth(points: array.results)

            case .error(let error):
                print(error)
            }
        }
    }

}

So question is how to make network calling when Binding variable changes? Or maybe I mistake and need to use another way to code this.

1 Answers

When bound state is changed only body of view with @Binding is called, ie. actually only refresh happens, and only of part dependent of bound state, so even .onAppear is not called.

Taking about into account and the fact that entire ChartView depends on new identifier, the solution would be to make force refresh view as whole, so .onAppear called again, and loaded new data.

Here is code. Tested with Xcode 11.4 / iOS 13.4 (with some simplified replicated version).

struct ChartView: View {

    @Binding var sourceId: String   // << just binding
    @State private var points: [TemperaturePoint] = []

    var body: some View {
        GeometryReader { proxy in
            ChartView.makePath(by: self.points, with: proxy)
                .stroke(Color.white, lineWidth: 2)
        }
        .id(sourceId)                    // << here !!
        .onAppear(perform: loadData)
    }

    func loadData() {
        network.getPoints(sourceId: sourceId) { response in
            switch response {
            case .result(let array):
                self.points = TemperaturePoint.smooth(points: array.results)

            case .error(let error):
                print(error)
            }
        }
    }
}
Related