Search code examples
iosswiftuiswiftui-list

Crash when attempting to scroll using ScrollViewReader in a SwiftUI List


I am trying to scroll to a newly appended view in a SwiftUI List using ScrollViewReader but keep crashing with EXC_BAD_INSTRUCTION in scrollTo(_:) after adding a few items. I am using Xcode 14.0.1 and iOS 16.0 simulator.

Here is a minimal demo that exhibits the issue:

struct ContentView: View {

    @State var items = [Item]()
    @State var scrollItem: UUID? = nil
    
    var body: some View {
        NavigationView {
            ScrollViewReader { proxy in
                List {
                    ForEach(items) { item in
                        Text(item.id.uuidString)
                            .id(item.id)
                    }
                }
                .listStyle(.inset)
                .onChange(of: scrollItem) { newValue in
                    proxy.scrollTo(newValue)
                }
            }
            .navigationTitle("List Demo")
            .toolbar {
                Button("Add") {
                    addItem()
                }
            }
        }
    }

    func addItem() {
        items.append(Item())
        scrollItem = items.last?.id
    }
}

struct Item: Identifiable {
    let id = UUID()
}

I can get past the issue using a ScrollView instead of a List, but I would like to use the native swipe-to-delete functionality in the real project.


Solution

  • List is not supported well in ScrollViewReader. See this thread.

    This solution is ugly, but works. The bad thing is that list blinks when you add a new item. I used one of the ideas from the thread above.

    import SwiftUI
    
    struct ContentView: View {
    
        @State var items = [Item]()
        @State var scrollItem: UUID? = nil
        @State var isHidingList = false
    
        var body: some View {
            NavigationView {
                VStack(alignment: .leading) {
                    if isHidingList {
                        list.hidden()
                    } else {
                        list
                    }
                }
                .onChange(of: scrollItem) { _ in
                    DispatchQueue.main.async {
                        self.isHidingList = false
                    }
                }
                .navigationTitle("List Demo")
                .toolbar {
                    Button("Add") {
                        addItem()
                    }
                }
            }
        }
    
        var list: some View {
            ScrollViewReader { proxy in
                List {
                    ForEach(items) { item in
                        Text(item.id.uuidString)
                            .id(item.id)
                    }
                }
                .listStyle(.inset)
                .onChange(of: scrollItem) { newValue in
                    guard !isHidingList else { return }
                    proxy.scrollTo(newValue)
                }
                .onAppear() {
                    guard !isHidingList else { return }
                    proxy.scrollTo(scrollItem)
                }
            }
        }
    
        func addItem() {
            isHidingList = true
            items.append(Item())
            scrollItem = items.last?.id
        }
    
    }
    
    struct Item: Identifiable {
        let id = UUID()
    }