NEW: Subscribe to Hacking with Swift+ and accelerate your learning! >>

List reset when navigating back from detail screen

Forums > SwiftUI

According to this post by Paul https://www.hackingwithswift.com/articles/210/how-to-fix-slow-list-updates-in-swiftui

I have issue when I navigate back to list from detail screen I'm back on top of the list, not on the position where I was navigating to detail screen. And I need to use above tip because sorting my list with 500+ rows was very slow.

for example I tapped 20th row and when back it should show this row on screen, but I see first 5-6 (because of row height)

Can you recommend any solution for this?

   

Unfortunately i cant see any way around it at this time. It would be something to think about. The problem is that the body property of the view your list is in is being invoked when you come back from your detail view so its pretty much refreshing back to the originla state of the list.

It would be intereating if there was a way to stop this. Thinking outside the box, if there was a way to capture the state of the view and then load that state instead of the original state of the view might work but im not sure how to go about this. I know closures capture the variables they reference so using this might be able to do it. Something to think about anyways

Dave

   

@pd95  

After reviewing my messy code, I edited the answer again to use a ViewModel to encapsulate everything related to the list and detail views state.

I think, setting the list .id() always is a bad idea, because the list will be reset whenever the state of the main view is modified. But in the most cases, the state modified in the main view is not related to sorting of the list... so we lose the lists scrolling state without a good reason.

I've tested with the code below an approach, where I set a state variable fakeID when I've influenced the lists sorting. Based on this boolean, the .id() assigns the list a random ID which makes it render really quickly. In all the other cases, the lists .id() is set to nil which ensures, that the list (and therefore it's scrolling state) is not reset whenever some "normal" state change happens.

In belows example code, I have a main view with a list of 601 items (=Strings), shown in a List with for each row a NavigationLink to a DetailView. The detail views is "fiddling" with the main view's state (e.g. to make the rows background green and/or shuffle/sort the items array).

I don't think this is a beautiful solution, there are certainly guys here who are able to make it better, but I think it is a good compromise to make List performance acceptable when it has heavily changed (like in shuffle or sort), but ensure that List state is kept when "regular" changes happen.

Please give it a try.

class ViewModel: ObservableObject, CustomStringConvertible {
    @Published var list: [String] = (0...600).map { "Line \($0)"}
    @Published var makeGreen = false

    var fakeID = false             // set to true, to make List() load quickly

    func shuffleList() {
        list.shuffle()
        fakeID = true
    }

    func sortList() {
        list.sort()
        fakeID = true
    }

    // getListID returns a UUID only once!
    func getListID() -> UUID? {
        let id = fakeID ? UUID() : nil
        fakeID = false
        return id
    }

    var description: String {
        "ViewModel(list: \(list.prefix(3)), fakeID=\(fakeID), makeGreen=\(makeGreen))"
    }
}

struct ContentView: View {
    @ObservedObject var model = ViewModel()

    var body: some View {
        print("ContentView body executed: model=\(self.model)")
        return NavigationView {
            VStack {
                Button("Shuffle", action: model.shuffleList)
                Button("Sort", action: model.sortList)

                List(model.list, id: \.self) { item in
                    NavigationLink(destination: DetailView(item: item, model: self.model)) {
                        Text(item)
                            .background(self.model.makeGreen ? Color.green : Color.clear)
                    }
                }
                .id(model.getListID())  // getListID returns a UUID only once!
                .onAppear() {
                    print("ContentView onAppear: model=\(self.model)")
                }
                .onDisappear() {
                    print("ContentView onDisappear: model=\(self.model)")
                }
            }
            .navigationBarTitle("Main")
        }
    }
}

struct DetailView: View {
    let item: String

    @ObservedObject var model: ViewModel

    var body: some View {
        print("DetailView body executed: model=\(self.model)")
        return VStack {
            Text(item)
                .font(.largeTitle)

            Toggle(isOn: $model.makeGreen) {
                Text("Green background")
            }

            Button("Shuffle now", action: model.shuffleList)
                .padding()

            Button("Sort now", action: model.shuffleList)
                .padding()

            Spacer()
        }
        .onAppear() {
            print("DetailView onAppear: model=\(self.model)")
        }
        .onDisappear() {
            print("DetailView onDisappear: model=\(self.model)")
        }
        .padding()
        .navigationBarTitle("Detail view")
    }
}

   

Just had to fix one line

@Published var list: [String] = (0...600).map { "Line \(String(format: "%03d", $0))" }

Makes it 001, 022 for easier sorting.

   

thanks a lot for this suggestions. I will test it in my use case.

   

Hacking with Swift is sponsored by NSSpain

SPONSORED Announcing NSSpain 2020: Remote Edition! An online, continuous conference for iOS developers. We’ll start on Thursday and finish on Friday, with talks, activities, and lots of fun for 36 hours, non-stop. Sound good? Join us!

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

Not logged in

Log in
 

Link copied to your pasteboard.