.onAppear is calling when I navigated back to a view by clicking back button

Viewed 2089

I have two views written in swiftUI , say for example ViewA and ViewB. onAppear() of ViewA has an apiCall which calls when initially the view is loaded. I navigate to ViewB from ViewA using navigation link and on clicking back button in ViewB the onAppear() of ViewA is called.

• Is there any way to stop calling onAppear() while navigated back from a view

• I am looking swiftUI for something like 'ViewDidLoad' in UIKit given a sample of my code

struct ContentView: View {
    var body: some View {
        NavigationView{
            List(viewModel.list){ item in
               NavigationLink(
                destination: Text("Destination"),
                label: {
                    Text(item.name)
                })
            }
            .onAppear{
                viewModel.getListApiCall()
            }
     
        }
    }
}
2 Answers

Overview

  • SwiftUI is quite different from the way UIKit works. It would be best to watch the tutorials (links below) to understand how SwiftUI and Combine works.
  • SwiftUI is a declarative framework so the way we approach is quite different. It would be best not to look for a direct comparison to UIKit for equivalent functions.

Model:

  • Let the model do all the work of fetching and maintaining the data
  • Ensure that your model conforms to ObservableObject
  • When ever any @Published property changes, it would imply that the model has changed

View:

  • Just display the contents of the model
  • By using @ObservedObject / @EnvironmentObject SwiftUI would observe the model and ensure that the view states in sync with any changes made to the model
  • Notice that though the model fetches the data after 2 seconds, the view reacts to it and displays the updated data.

Model Code:

class Model: ObservableObject {
    
    @Published var list = [Item]()
    
    init() {
        fetchItems()
    }
    
    private func fetchItems() {
        
        //To simulate some Async API call
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
            self?.list = (1...10).map { Item(name: "name \($0)") }
        }
    }
}

struct Item: Identifiable {
    
    var name: String
    
    var id : String {
        name
    }
}

View Code:

import SwiftUI

struct ContentView: View {
    
    @ObservedObject var model: Model
    
    var body: some View {
        NavigationView{
            List(model.list){ item in
                NavigationLink(destination: Text("Destination")) {
                    Text(item.name)
                }
            }     
        }
    }
}

Reference:

SwiftUI

Combine

You could add a variable to check if the getListApiCall() has been invoked.

struct ContentView: View {
    @State var initHasRun = false
    var body: some View {
        NavigationView{
            List(viewModel.list){ item in
               NavigationLink(
                destination: Text("Destination"),
                label: {
                    Text(item.name)
                })
            }
            .onAppear{
                if !initHasRun {
                    viewModel.getListApiCall()
                    initHasRun=true
                }
            }
 
        }
    }
}
Related