SwiftUI ScrollViewProxy crash

I’ve hit a frustrating bug in SwiftUI scroll views since the release of iOS 16.

ScrollViewProxy

My situation is that I have a list of items. When the user adds a new item to the end of the list I want to scroll to and select that new item. Here’s my basic setup for the list:

struct ContentView: View {
  @EnvironmentObject private var itemStore: ItemStore
  @State private var selectedItemID: Item.ID?

  var body: some View {
    NavigationView {
      List(itemStore.items, selection: $selectedItemID) { item in
        NavigationLink(destination: ItemView(item: item),
                               tag: item.id,
                         selection: $selectedItemID) {
          Text(item.name)
        }
      }
      Text("Choose an item")
    }
  }
}

Note: I’m using the older navigation API for iOS 15 compatibility but I’ve seen the same problem with the newer iOS 16 only navigation APIs.

I add new items to the list with a toolbar button item:

.toolbar {
  ToolbarItem(placement: .primaryAction) {
    Button { itemStore.newItem() } 
      label: { Image(systemName: "plus") }
  }
}

I’ve covered programmatically scrolling with ScrollViewReader before. I’m following a similar approach here, embedding the list in a ScrollViewReader:

NavigationView {
  ScrollViewReader { proxy in
    List(...) {
    }
  }
}   

Then when the count of items increases I select and scroll to the identifier of the last item in the store:

List(...) {
}
.onChange(of: itemCount) { [itemCount] newCount in
  guard newCount > itemCount,
    let lastID = itemStore.lastItemID() else { return }
  withAnimation {
    selectedItemID = lastID
    proxy.scrollTo(lastID)   // iOS 16 crashes here
  }
}

I’ve tried a few variations and they all work fine on iOS 15 but crash in the scrollTo method of the scroll view proxy on iOS 16:

proxy.scrollTo(lastID) underlined in red. Thread 1: EXC_BREAKPOINT (code=1)

I should add, it doesn’t crash the first time but on adding the third item. I don’t have much of a workaround right now other than removing the functionality from iOS 16 and hoping Apple fixes it soon.

What Changed in iOS 16?

I don’t know what changed in iOS 16 to cause this crash. What we do know, from the release notes, is that the underlying implementation of list changed in iOS 16.

The implementation of list no longer uses UITableView. (81571203)

We can see that comparing the view debugger output for iOS 15 and 16:

View debugger showing UITableViewWrapperView on the left and UpdateCoalescingCollectionView on the right

On the left, iOS 15 is wrapping a UITableView. On the right, iOS 16 looks like it’s using a UICollectionView.

Potential Fix Identified

There’s some hope in that Apple did respond to my feedback report (FB11826112), identifying a potential fix for a future OS update.

FB11826112 Recent similar reports: Less than 10. Resolution: Potential fix identified for a future OS update

I’ll update this post if and when that happens.