Swiftui - Scrollviewreader's Scrollto Does Not Scroll

SwiftUI - ScrollViewReader's scrollTo does not scroll

ScrollViewReader works only with:

  • Explicit use of ScrollView
  • List of identifiable collection

It does not work with List of Range<Int> unless you set its id explicitly.

Set id explicitly.

// List(0..<100, id: \.self)

struct ContentView: View {
var body: some View {
ScrollViewReader { proxy in
VStack {
Button("Jump to #50") {
proxy.scrollTo(5, anchor: .top)
}

List(0..<100, id: \.self) { i in
Text("Example \(i)")
.id(i)
}
}
}
}
}

// ForEach(0..<50000, id: \.self)

struct ContentView: View {
var body: some View {
ScrollView {
ScrollViewReader { proxy in
LazyVStack {
ForEach(0..<50000, id: \.self) { i in
Button("Jump to \(i+500)") {
proxy.scrollTo(i+500, anchor: .top)
}
Text("Example \(i)")
.id(i)
}
}
}
}
}
}

swiftui ScrollViewReader scrollTo not correct working

I think the issue you are having is that the 2 ForEach just don't seem to work well with the ScrollReader. I took a different approach and used an identifiable struct and a LazyVGrid. That allowed me to use one ForEach and id the individual squares with a UUID. I then used the same UUID in the scrollTo(). The only difficulty I ran into was that ScrollReader didn't know what to do with a bidirectional ScrollView, so I made a function that returned the UnitPoint and used that as an anchor in the scrollTo(). That seems to have done the trick and it works very reliably.

struct BiDirectionScrollTo: View {

let scrollItems: [ScrollItem] = Array(0..<100).map( { ScrollItem(name: $0.description) })

let columns = [
// Using 3 grid items forces there to be 3 columns
GridItem(.fixed(80)),
GridItem(.fixed(80)),
GridItem(.fixed(80)),
GridItem(.fixed(80)),
GridItem(.fixed(80)),
GridItem(.fixed(80)),
GridItem(.fixed(80)),
GridItem(.fixed(80)),
GridItem(.fixed(80)),
GridItem(.fixed(80))
]

init() {

}
var body: some View {
ScrollViewReader { reader in
ScrollView([.horizontal,.vertical], showsIndicators: false) {
LazyVGrid(columns: columns, spacing: 20) {
ForEach(scrollItems, id: \.id) { item in
Text("Item \(item.name)")
.foregroundColor(.white)
.frame(width: 80, height: 80)
.background(Color.red)
.id(item.id)
.onTapGesture {
withAnimation {
if let index = [0, 9, 55, 90, 99].randomElement() {
print(index)
reader.scrollTo(scrollItems[index].id, anchor: setUnitPoint(index))
}
}
}
}
}
}
}
}
private func setUnitPoint(_ index:Int) -> UnitPoint {
switch true {
case index % 10 < 2 && index / 10 < 2:
return .topLeading
case index % 10 >= 7 && index / 10 < 7:
return .topTrailing
case index % 10 < 2 && index / 10 >= 7:
return .bottomLeading
case index % 10 >= 2 && index / 10 >= 7:
return .bottomTrailing
default:
return .center
}
}
}

struct ScrollItem: Identifiable {
let id = UUID()
var name: String
}

SwiftUI ScrollViewReader scrollTo not working as expected

You need to give id to a view to scroll to, like:

ForEach(players, id: \.id) { player in
ExerciseTable(player: player)
.frame(width: size.width)
.id(player.id) // << here !!
}

ScrollTo in ScrollView not working as expected

In your ForEach, your are identifying each row by the \.id key-path. The id property in MessageTest is an optional, of type String! (but is really a String? to mean Optional<String>).

Because of this, you need to pass in the row to scroll to as a String? type, rather than just String.

Change the line to the following, by adding the cast:

scrollView.scrollTo("99" as String?, anchor: .bottom)

Link here to an answer about how String? and String! are both Optional<String>, but the ! version is just implicitly force-unwrapped.

Unable to scroll to the bottom of the list implemented with ScrollViewReader in SwiftUI

The scrollTo works by .id, so you have to specify identifier for each view explicitly, like (assuming that msgItem fits this needs, otherwise use corresponding id of your logic):

ForEach(chatItem.msgItems) { msgItem in
ChatView(data: msgItem).id(msgItem) // << here !!
}

See for example and details https://stackoverflow.com/a/60855853/12299030.

Scrollto with Scrollview doesn’t work the first time

Remove your VStacks as they are unnecessary and are interfering with the operation of the Scrollview. Essentially, everything you are getting out of the ForEach is a "cell". With the VStack's, you are putting EVERYTHING into 1 cell, and then expecting things to work. ScrollView is looking for the multiple cells, so give them to it.

struct ContentView: View {
@State var sp = [1,2,3,4,5]
var body: some View{
VStack{
Text("asdf")
.frame(height: 90)
ScrollView{
ScrollViewReader{scrollReader in
ForEach(sp,id:\.self){p in
Text("\(p)").frame(height: 50)
}
Button("add"){
sp.append((sp.last ?? 0) + 1)
}
Spacer(minLength: 60)
.id("bottom")
.frame(maxWidth: .infinity)
.border(Color.green)
.onChange(of: sp.count){_ in
scrollReader.scrollTo("bottom")
}
}
}
.border(Color.black)
}
}
}

Why is scrollTo working on Int and not Message custom object type?

Welcome to Stack Overflow! You are missing the simple fact that a message.id != message?.id. Your .id(message.id) is a non-optional UUID, however, groupViewModel.messages.last?.id is an optional. Since they don't match, you won't get the scroll. I will give you one better. First, you don't need to use message.id. You can simply compare the messages themselves as Identifiable conforms to Equatable. So, your code becomes:

        ScrollViewReader { scrollView in
ScrollView(.vertical) {
LazyVStack {
// You can also skip id: \.id as message is Identifiable
ForEach(groupViewModel.messages) { message in
MessageRowView(message: message)
.id(message)
}
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now()) {
if !groupViewModel.messages.isEmpty {
// Use if let to unwrap the optional
if let last = groupViewModel.messages.last {
// Use the unwrapped value here
scrollView.scrollTo(last)
}
}
}
}
}
}

If you haven't stated conformance of your struct to Identifiable, just do it. I am assuming you did, but you did not post that part of the code so we don't have a Minimal, Reproducible Example (MRE). Most questions will need an MRE to be answered, so make sure you understand what that is.

Edit:

In looking at your struct, try this:

struct Message: Codable, Equatable, Identifiable, Hashable {
let id = UUID()
let sender: FSUser
let message: String
let sendDate: Date
}

You can set the UUID immediately as it will never change. Also, not that it matters in the long run, but if you conform to Identifiable you get 'Equatable' conformance.



Related Topics



Leave a reply



Submit