Reputation: 31
I'm trying to make my view scroll to the bottom of the item list every time a new item is added. This is my code, can someone help please?
ScrollView(.vertical, showsIndicators: false) {
ScrollViewReader{ value in
VStack() {
ForEach(0..<self.count, id:\.self) {
Text("item \($0)")
}
}.onChange(of: self.data.sampleCount, perform: value.scrollTo(-1))
}
}
Upvotes: 3
Views: 6855
Reputation: 1228
Here is my example/solution.
var scrollView : some View {
ScrollView(.vertical) {
ScrollViewReader { scrollView in
ForEach(self.messagesStore.messages) { msg in
VStack {
OutgoingPlainMessageView() {
Text(msg.message).padding(.all, 20)
.foregroundColor(Color.textColorPrimary)
.background(Color.colorPrimary)
}.listRowBackground(Color.backgroundColorList)
Spacer()
}
// 1. First important thing is to use .id
//as identity of the view
.id(msg.id)
.padding(.leading, 10).padding(.trailing, 10)
}
// 2. Second important thing is that we are going to implement //onChange closure for scrollTarget change,
//and scroll to last message id
.onChange(of: scrollTarget) { target in
withAnimation {
scrollView.scrollTo(target, anchor: .bottom)
}
}
// 3. Third important thing is that we are going to implement //onChange closure for keyboardHeight change, and scroll to same //scrollTarget to bottom.
.onChange(of: keyboardHeight){ target in
if(nil != scrollTarget){
withAnimation {
scrollView.scrollTo(scrollTarget, anchor: .bottom)
}
}
}
//4. Last thing is to add onReceive clojure, that will add an action to perform when this ScrollView detects data emitted by the given publisher, in our case messages array.
// This is the place where our scrollTarget is updating.
.onReceive(self.messagesStore.$messages) { messages in
scrollView.scrollTo(messages.last!.id, anchor: .bottom)
self.scrollTarget = messages.last!.id
}
}
}
}
Also take a look at my article on medium.
https://mehobega.medium.com/auto-scrollview-using-swiftui-and-combine-framework-b3e40bb1a99f
Upvotes: 1
Reputation: 117
You can add an EmptyView item in the bottom of list
ScrollView(.vertical, showsIndicators: false) {
ScrollViewReader{ value in
VStack() {
ForEach(0..<self.count, id:\.self) {
Text("item \($0)")
}
Spacer()
.id("anID") //You can use any type of value
}.onChange(of: self.data.sampleCount, perform: { count in
value.scrollTo("anID")
})
}
Upvotes: 5
Reputation: 258345
We need to give id
for views which in ScrollView
we want to scroll to.
Here is a demo of solution. Tested with Xcode 12.1 / iOS 14.1
struct DemoView: View {
@State private var data = [String]()
var body: some View {
VStack {
Button("Add") { data.append("Data \(data.count + 1)")}
.padding().border(Color.blue)
ScrollView(.vertical, showsIndicators: false) {
ScrollViewReader{ sr in
VStack {
ForEach(self.data.indices, id:\.self) {
Text("item \($0)")
.padding().id($0) // << give id
}
}.onChange(of: self.data.count) { count in
withAnimation {
sr.scrollTo(count - 1) // << scroll to view with id
}
}
}
}
}
}
}
Upvotes: 4