If you have a SwiftUI List with that allows single selection, you can change the selection by clicking the list (presumably this makes it the key responder) and then using the arrow keys. If that selection reaches the end of the visible area, it will scroll the whole list to keep the selection visible.
However, if the selection object is updated in some other way (e.g. using a button), the list will not be scrolled.
Is there any way to force the list to scroll to the new selection when set programmatically?
Example app:
import SwiftUI
struct ContentView: View {
@State var selection: Int? = 0
func changeSelection(_ by: Int) {
switch self.selection {
case .none:
self.selection = 0
case .some(let sel):
self.selection = max(min(sel + by, 20), 0)
}
}
var body: some View {
HStack {
List((0...20), selection: $selection) {
Text(String($0))
}
VStack {
Button(action: { self.changeSelection(-1) }) {
Text("Move Up")
}
Button(action: { self.changeSelection(1) }) {
Text("Move Down")
}
}
}
}
}
If you want to programmatically make SwiftUI's List move to show a specific row, you should embed it inside a ScrollViewReader . This provides a scrollTo() method on its proxy that can move to any row inside the list, just by providing its ID and optionally also an anchor.
A view that arranges its children in a line that grows vertically, creating items only as needed.
I tried several solutions, one of them I'm using in my project (I need horizontal paging for 3 lists). And here are my observations:
scrollToRowAtIndexPath
method (like in this answer).As I wrote, here is my example, which, of course, requires refinement. First of all ScrollView needs to be inside GeometryReader and you can understand the real size of content. The second thing is that you need to control your gestures, which might be difficult. And the last one: you need to calculate current offset of ScrollViews's content and it could be other than in my code (remember, I tried to give you example):
struct ScrollListView: View {
@State var selection: Int?
@State private var offset: CGFloat = 0
@State private var isGestureActive: Bool = false
func changeSelection(_ by: Int) {
switch self.selection {
case .none:
self.selection = 0
case .some(let sel):
self.selection = max(min(sel + by, 30), 0)
}
}
var body: some View {
HStack {
GeometryReader { geometry in
VStack {
ScrollView(.vertical, showsIndicators: false) {
ForEach(0...29, id: \.self) { line in
ListRow(line: line, selection: self.$selection)
.frame(height: 20)
}
}
.content.offset(y: self.isGestureActive ? self.offset : geometry.size.height / 4 - CGFloat((self.selection ?? 0) * 20))
.gesture(DragGesture()
.onChanged({ value in
self.isGestureActive = true
self.offset = value.translation.width + -geometry.size.width * CGFloat(self.selection ?? 1)
})
.onEnded({ value in
DispatchQueue.main.async { self.isGestureActive = false }
}))
}
}
VStack {
Button(action: { self.changeSelection(-1) }) {
Text("Move Up")
}
Spacer()
Button(action: { self.changeSelection(1) }) {
Text("Move Down")
}
}
}
}
}
of course you need to create your own "list row":
struct ListRow: View {
@State var line: Int
@Binding var selection: Int?
var body: some View {
HStack(alignment: .center, spacing: 2){
Image(systemName: line == self.selection ? "checkmark.square" : "square")
.padding(.horizontal, 3)
Text(String(line))
Spacer()
}
.onTapGesture {
self.selection = self.selection == self.line ? nil : self.line
}
}
}
hope it'll be helpful.
In the new relase of SwiftUI for iOs 14 and MacOs Big Sur they added the ability to programmatically scroll to a specific cell using the new ScrollViewReader:
struct ContentView: View {
let colors: [Color] = [.red, .green, .blue]
var body: some View {
ScrollView {
ScrollViewReader { value in
Button("Jump to #8") {
value.scrollTo(8)
}
ForEach(0..<10) { i in
Text("Example \(i)")
.frame(width: 300, height: 300)
.background(colors[i % colors.count])
.id(i)
}
}
}
}
}
Then you can use the method .scrollTo()
like this
value.scrollTo(8, anchor: .top)
Credit: www.hackingwithswift.com
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With