Search code examples
iosswiftswiftuiswiftui-listswiftui-navigationlink

How do I stop a view from refreshing each time I return from a detail view in SwiftUI?


I have a view which displays a list of posts. I have implemented infinite scrolling, and it is functioning properly. however, there is one small problem I am running into, and attempts to solve it have me going round in circles.

Main view

struct PostsHomeView: View {

    @EnvironmentObject var viewModel: ViewModel
    @State var dataInitiallyFetched = false

    var body: some View {
    
        NavigationView {
        
             VStack(alignment: .center, spacing: 0) {

                 if self.viewModel.posts.count > 0  {
                         
                     PostsListView(posts: self.viewModel.posts,
                                  isLoading: self.viewModel.canFetchMorePosts,
                                  onScrolledAtBottom: self.viewModel.fetchMorePosts
                     )

                 } else {

                     VStack {
                         
                         Text("You have no posts!")
                         
                     }
                 }
             }
             .onAppear() {

                 if !self.dataInitiallyFetched {

                     self.viewModel.fetchMostRecentPosts()

                     self.dataInitiallyFetched = true
                 }
             }
             .navigationBarTitle("Posts", displayMode: .inline)
       }
    }
}

List view

struct PostsListView: View {
    
    @EnvironmentObject var viewModel: ViewModel      
    let posts: [Post]
    let isLoading: Bool
    let onScrolledAtBottom: () -> Void

    var body: some View {
       List {

           postsList
           
           if isLoading {
               loadingIndicator
           }
       }
    }
       
    private var postsList: some View {
               
       ForEach(posts, id: \.self) { post in

           PostsCellView(post: post)
               .onAppear {
                         
                   if self.posts.last == post {

                           self.onScrolledAtBottom()
                   }
               }
       }
       .id(UUID())
    } 
}

Problem

Upon tapping one of the posts in the list, I am taken to a detail view. When I tap the nav bar's back button in order go back to the posts list, the whole view is reloaded and my post fetch methods are fired again.

In order to stop the fetch method that fetches most recent posts from firing, I have added a flag that I set to true after the initial load. This stops the fetch method that grabs the initial set of posts from firing when I go back and forth between the details view and posts home screen.

I have tried various things to stop the fetchMorePosts function from firing, but I keep going in circles. I added a guard statement to the top of the fetchMorePosts function in my view model. It checks to see if string is equal to "homeview", if not, then the fetch is not done. I set this string to "detailview" whenever the detail view is visited, then I reset it back to "homeview" in the guard statement.

guard self.lastView == "homeview" else {
    self.lastView = "homeview"
    return
}

This works to an extent, but I keep finding scenarios where it doesn't work as expected. There must be a straight-forward way to tell SwiftUI not to reload a view. The problem is the method sits in the onAppear closure which is vital for the infinite scrolling to work. I'm not using iOS 14 yet, so I can't use @StateObject.

Is there a way to tell SwiftUI not to fire onAppear everytime I return from a detail view?

Thanks in advance


Solution

  • The culprit was .id(UUID()). I removed it from my list and everything worked again.

    Thanks Asperi. Your help is much appreciated.