I am using a LayzVStack embedded into a ScrollView. The list items are fetched from a core data store by using a @FetchResult or I tested it also with the new @Query command coming with SwiftData.
The list has one hundred items 1, 2, 3, ..., 100. The user scrolled the ScrollView so that items 50, 51, ... 60 are visible on screen.
Now new data will be fetched from the server and updates the CoreData or SwiftData model. When I add new items to the end of the list (e.g 101, 102, 103, ...) then the ScrollView is keeping its position. Opposite to this when I add new items to the top (0, -1, -2, -3, ...) then the ScrollView scrolls down.
Is there a way with the new SwiftData and SwiftUI ScrollView modifiers to update my list model without scrolling like with UIKit where you can query and set the scroll offset pixel wise?
The new scrollPosition modifier can help you. It associated a binding to an identifier with the scroll view. When changes to the scroll view occur like items being added or the scroll view changing its containing size, it will attempt to keep the currently scrolled item in the same relative position as before the change. The docs on the website have some incorrect information but here's an example to hopefully get you started.
struct ContentView: View {
@State var data: [String] = (0 ..< 25).map { String($0) }
@State var dataID: String?
var body: some View {
ScrollView {
VStack {
Text("Header")
LazyVStack {
ForEach(data, id: \.self) { item in
Color.red
.frame(width: 100, height: 100)
.overlay {
Text("\(item)")
.padding()
.background()
}
}
}
.scrollTargetLayout()
}
}
.scrollPosition(id: $dataID)
.safeAreaInset(edge: .bottom) {
Text("\(Text("Scrolled").bold()) \(dataIDText)")
Spacer()
Button {
dataID = data.first
} label: {
Label("Top", systemImage: "arrow.up")
}
Button {
dataID = data.last
} label: {
Label("Bottom", systemImage: "arrow.down")
}
Menu {
Button("Prepend") {
let next = String(data.count)
data.insert(next, at: 0)
}
Button("Append") {
let next = String(data.count)
data.append(next)
}
Button("Remove First") {
data.removeFirst()
}
Button("Remove Last") {
data.removeLast()
}
} label: {
Label("More", systemImage: "ellipsis.circle")
}
}
}
var dataIDText: String {
dataID.map(String.init(describing:)) ?? "None"
}
}