SwiftUI List - How to scroll to item - swiftui

I have list with items. How can i scroll to list 12. I can use geometry reader to calculate offset. But how to scroll to this offset?
List {
ForEach(0..<12) { index in
Text("...")
}
}

Form Xcode 12, You can turn in to a ScrollView and then you can do it by .scrollTo(id):
var body: some View {
ScrollViewReader { scrollProxy in
ScrollView {
ForEach((1...100), id: \.self) { Text("\($0)") }
}
Button("Go!") {
withAnimation { scrollProxy.scrollTo(50) }
}
}
}
Note that ScrollViewReader should support all scrollable content, but now it only supports ScrollView

Related

Picker scroll through one element horizontally

I have a SwiftUI Picker in which an item is selected. The text of one element can be large, so I used UIKit UIPickerView and set the manual height to 100, but at some point it became not enough. Is it possible to make scrolling horizontal for each element?
I want to get something like this:
Picker("Items", select: self._selectItem) {
ForEach(self.items, id: \.self) { item in
ScrollView(.horizontal, showsIndicators: false) {
Text(item.description)
}
.tag(item)
}
}
That should work fine. If you only want to scroll one item, you would have to insert a check of the item length.
let items = [
"A long item text.",
"And a even longer item text which is really going further.",
"Another item text which is really going further."
]
struct ContentView: View {
#State private var select = ""
var body: some View {
VStack {
Text("Make your selection!")
List(items, id: \.self) { item in
ScrollView(.horizontal) {
Text(item)
}
.listRowBackground(item == select ? Color.red : Color.white)
.onTapGesture {
select = item
}
}
}
}
}
I would strongly suggest to separate the picking from the text display and scrolling, e.g. like this:
struct ContentView: View {
#State private var select = items[0]
var body: some View {
VStack {
Text("Make your selection!")
Picker("Items", selection: $select) {
ForEach(items) { item in
Text(item.title)
.tag(item)
}
}
ScrollView {
Text(select.text)
}
.padding()
.frame(height: 200)
}
}
}

SwiftUI: Alternatives to using ListView?

I want to have a scrollable list that when each row is tapped, it takes you to a different view. Inside each row, I want to have a heart button that when tapped overrides the navigation behavior and just toggles a heart fill/unfill.
As an alternative to do this, would it make sense to use a ScrollView inside a NavigationView and then have each list item be a VStack?
Pseudocode hierarchy:
NavigationView {
ScrollView {
VStack {
// Button
}
}
}
Is there a better way ( or more preferred way) to accomplish this?
Use LazyVStack
let items = ["A","B"]
var body: some View {
ScrollView {
LazyVStack(spacing: 10) {
ForEach(items, id: \.self) { item in
Text(item)
}
}
}
}

SwiftUI Drag events how do restrict to detect only horizontal/vertical scrolls

I have List (vertically scrolled) with nested ScrollView (or View that has dragging attached to it). I would like to have nested view onDrag() to be detected only on horizontal scrolling and not on vertical one. Vertical scrolling should be propagated to wrapper List.
Well, the following demo of horizontal scroll view inside vertical list should be working, and it is, unless List for now has known "refresh" defect. So... I decided to post it just for demo... maybe with next SwiftUI/iOS update it will be fixed automatically.
Also I provided variant of horizontal scroll view inside vertical scroll view, as a workaround to List issue. Hope any of those would be helpful.
So, demo code for "vertical drag anywhere propagated to vertical scrolling container, horizontal drag works only in horizontal scroll view"
A - Working variant of body based on vertical ScrollView (to replace in below module)
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .leading) {
ForEach (0..<20, id: \.self) { i in
VStack {
self.rowItem(index: i)
Divider()
}
}
}
}
}
B - "Working" variant with List (having refresh issue)
struct TestScrollInList: View {
var body: some View {
List {
ForEach (0..<20, id: \.self) { i in
self.rowItem(index: i)
}
}
}
func rowItem(index i: Int) -> AnyView {
if i % 2 == 0 {
return AnyView(self.plainItem(index: i))
} else {
return AnyView(self.scrollableItem(index: i))
}
}
func plainItem(index i: Int) -> some View {
Text("Plain item \(i)")
}
func scrollableItem(index i: Int) -> some View {
ScrollView (.horizontal, showsIndicators: false) {
HStack {
ForEach(0..<10) { j in
RoundedRectangle(cornerRadius: 8)
.fill(j % 2 == 0 ? Color.green : Color.yellow)
.frame(width: 100, height: 100)
}
}
}
.frame(maxHeight: 110)
}
}
struct TestScrollInList_Previews: PreviewProvider {
static var previews: some View {
TestScrollInList()
}
}

Button and List interaction modifications

I am trying to remove the "sectionStyle" for my List rows. In order to achieve a tableview-like view, you would write the following:
List(items, id: \.id) { item in
ItemRow(with: item)
}
If you want to interpret tap events on each row, you would wrap the ItemRow into a Button:
List(items, id: \.id) { item in
Button(action: rowTapped) {
ItemRow(item: item)
}
}
The interesting part is that when the List knows about the Button, it will automatically give a selection animation like tableView's selectionStyle. For my application, I'd like to not have that. Is there a way to set the "selectionStyle" for the List to "none"?
There are a number of List behaviours, that I haven't been able to alter. It's quite easy to build your own list though if you have specific needs different than the system List view. Here's a simple example that I've used.
struct MyListView:View {
var body: some View {
ScrollView {
ForEach(items, id: \.id) { item in
VStack {
Button(action: self.rowTapped) {
ItemRow(item: item)
}
Divider()
}
}
}.padding()
}
func rowTapped() {
print("rowTapped")
}
}
struct ItemRow:View {
var item:Item
var body: some View {
HStack {
Text(item.name)
Spacer() // force text to left justify
}
}
}

How to perform animated transitions of a view inside a ScrollView?

I have a ScrollView, which contains an HStack, which contains a ForEach, which contains some custom view with an attached transition. It looks something like:
ScrollView {
HStack {
ForEach(self.objects) { object in
ObjectView(object)
.transition(.slide)
}
}
}
When I update the contents of self.objects (within a call to withAnimation), I want the old/new ObjectViews to animate in and out as specified by the transition.
Unfortunately, no animated transitions appear. If I remove the ScrollView (leaving the HStack to be the root view), the animated transitions work as expected.
Is there a way I can animate the transitions of views inside a ScrollView?
maybe you are interested...this works:
struct ObjectView : View {
var text: String
var body: some View {
Text(text)
}
}
struct ContentView: View {
#State var objects = ["a", "b", "c"]
var body: some View {
Group() {
Button("add") {
withAnimation() {
self.objects.append("f")
}
}
ScrollView {
HStack {
ForEach(objects, id: \.self) { object in
ObjectView(text: object)
.transition(.slide)
}
}.frame(width: 500)
}
}
}
}