I have a horizontal scroll view with lists. When scrolling horizontally, how to make the lists to snap to the edges.
struct RowView: View {
var post: Post
var body: some View {
GeometryReader { geometry in
VStack {
Text(self.post.title)
Text(self.post.description)
}.frame(width: geometry.size.width, height: 200)
//.border(Color(#colorLiteral(red: 0.1764705926, green: 0.01176470611, blue: 0.5607843399, alpha: 1)))
.background(Color(#colorLiteral(red: 0.721568644, green: 0.8862745166, blue: 0.5921568871, alpha: 1)))
.cornerRadius(10, antialiased: true)
.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
}
}
}
struct ListView: View {
var n: Int
@State var posts = [Post(id: UUID(), title: "1", description: "11"),
Post(id: UUID(), title: "2", description: "22"),
Post(id: UUID(), title: "3", description: "33")]
var body: some View {
GeometryReader { geometry in
ScrollView {
ForEach(0..<self.n) { n in
RowView(post: self.posts[0])
//.border(Color(#colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1)))
.frame(width: geometry.size.width, height: 200)
}
}
}
}
}
struct ContentView: View {
init() {
initGlobalStyles()
}
func initGlobalStyles() {
UITableView.appearance().separatorColor = .clear
}
var body: some View {
GeometryReader { geometry in
NavigationView {
ScrollView(.horizontal) {
HStack {
ForEach(0..<3) { _ in
ListView(n: 1000) // crashes
.frame(width: geometry.size.width - 60)
}
}.padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 0))
}
}
}
}
}
When I give the value of ListView(n: 1000)
, the view is crashing. The app launches and a white screen is shown for some time and then I get a black screen.
2019-10-06 15:52:57.644766+0530 MyApp[12366:732544] [Render] CoreAnimation: Message::send_message() returned 0x1000000e
How to fix this? My assumption is that it would be using something like dequeue cells like UITableView
, but not sure why it's crashing.
There are a couple of issues with the code provided. The most important is you are not using a List
just a ForEach
nested in a ScrollView
, which is like equivalent of placing 1000 UIViews in a UIStack - not very efficient. There is also a lot of hardcoded dimensions and quite a few of them are duplicates but nevertheless add a significant burden when the views are calculated.
I have simplified quite a lot and it runs with n = 10000 without crashing:
struct ContentView: View {
var body: some View {
GeometryReader { geometry in
NavigationView {
ScrollView(.horizontal) {
HStack {
ForEach(0..<3) { _ in
ListView(n: 10000)
.frame(width: geometry.size.width - 60)
}
} .padding([.leading], 10)
}
}
}
}
}
struct ListView: View {
var n: Int
@State var posts = [Post(id: UUID(), title: "1", description: "11"),
Post(id: UUID(), title: "2", description: "22"),
Post(id: UUID(), title: "3", description: "33")]
var body: some View {
List(0..<self.n) { n in
RowView(post: self.posts[0])
.frame(height: 200)
}
}
}
struct RowView: View {
var post: Post
var body: some View {
HStack {
Spacer()
VStack {
Spacer()
Text(self.post.title)
Text(self.post.description)
Spacer()
}
Spacer()
} .background(RoundedRectangle(cornerRadius: 10)
.fill(Color(#colorLiteral(red: 0.721568644, green: 0.8862745166, blue: 0.5921568871, alpha: 1))))
}
}
ScrollView
don't reuse anything. But List
do.
so change this:
ScrollView {
ForEach(0..<self.n) { n in
,,,
}
}
to this:
List(0..<self.n) { n in
,,,
}
You can use Lazy stacks like the LazyVStack
and the LazyHStack
. So even if you use them with ScrollView
, It will be smooth and performant.
I've created SwiftUI horizontal list which loads views only for visible objects + extra elements as a buffer. Moreover, it exposes the offset parameter as binding so you can follow it or modify it from outside.
You can access source code here HList
Give it a go! This example is prepared in the swift playground.
Example use case
struct ContentView: View {
@State public var offset: CGFloat = 0
var body: some View {
HList(offset: self.$offset, numberOfItems: 10000, itemWidth: 80) { index in
Text("\(index)")
}
}
}
To see content being reused in action you could do something like this
struct ContentView: View {
@State public var offset: CGFloat = 0
var body: some View {
HList(offset: self.$offset, numberOfItems: 10000, itemWidth: 80) { index in
Text("\(index)")
}
.frame(width: 200, height: 60)
.border(Color.black, width: 2)
.clipped()
}
}
If you do remove .clipped()
at the end you will see how the extra component is reused while scrolling when it moves out of the frame.
Update
While the above solution is still valid, however, it is a custom component. With WWDC2020 SwiftUI introduces lazy components such as LazyHStack but keep in mind that:
The stack is “lazy,” in that the stack view doesn’t create items until it needs to render them onscreen.
Meaning, elements are loaded lazy
but after that, they are kept in the memory.
My custom HList only refers to visible components. Not the one which already has appeared.
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