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
Sharing Image and Text to Facebook Messenger with Uiactivityviewcontroller Failing
How Does Uibutton Addtarget Self Work
Sirikit, How to Display Response for Start Workout Intent
Ambiguous Use of Registerclass with Swift
Increment Tab Bar Badge W/ Uialertaction Swift
How to Authorize Twitter with Swifter
Skaction Works in Didmovetoview But Doesn't Works in Function
iOS Swift Remove Uitableview Cell Separator Space
Uitableview with Uiviewrepresentable in Swiftui
Multiple Cells Selected on Scrolling [Reuse Cells Problem]
Lineargravityfield() Is Not Affecting Physics Bodies in the Scene Scenekit
Check the JSON Response Is Array or Int or String for a Key
Backgroundtimeremaining Returns (35791394 Mins)
Swiftui Card Flip Animation with Two Views, One of Which Is Embedded Within a Stack
Random Number from an Array Without Repeating the Same Number Twice in a Row