This issue still alive. I couldn't find any solution on SwiftUI but there is an workaround which can change animation type and duration. This workaround work with introspect and UIKit. You can change simple math calculation or anchor padding according to your case.
Sample code
import SwiftUI
import UIKit
import SwiftUIIntrospect
class ViewModel {
var scrollView: UIScrollView?
let anchorPadding: CGFloat = 60
private let itemHeight: CGFloat = 108 <--- item height(100) + item bottom padding(8)
func scrollTo(id: CGFloat) {
UIView.animate(
withDuration: 4.0, <--- You can change animation duration.
delay: 0.0,
options: .curveEaseInOut, <--- You can change animation type.
animations: { [weak self] in
guard let self else { return }
scrollView?.contentOffset.y = (itemHeight * id) - anchorPadding <--- Simple math calculation.
}
)
}
}
struct ContentView: View {
private var viewModel = ViewModel()
var body: some View {
ZStack(alignment: .top) {
ScrollView {
VStack(spacing: 8) {
ForEach(0..<100) { i in
Rectangle()
.frame(width: 200, height: 100)
.foregroundColor(.green)
.overlay(Text("\(i)").foregroundColor(.white))
}
.frame(maxWidth: .infinity)
}
}
.introspect(.scrollView, on: .iOS(.v13, .v14, .v15, .v16, .v17, .v18)) { scrollView in
viewModel.scrollView = scrollView <--- Set current scroll view reference.
}
Rectangle()
.fill(.red)
.frame(maxWidth: .infinity)
.frame(height: 1)
.padding(.top, viewModel.anchorPadding)
.ignoresSafeArea()
.overlay(alignment: .topTrailing) {
Button("Scroll To") {
viewModel.scrollTo(id: 50) <--- Scroll to item which id is 50.
}
.padding(.trailing, 8)
}
}
}
}