First and foremost, in development, you need to learn a critical skill that will carry you forward. That skill is breaking big problems into small problems and working on individual parts one at a time. For example your problem can be broken down into three individual components. This same principal can help you define methods, reusable views, etc. The idea is called "Single Responsibility Principal." which means each and every thing should only do one thing, and do that one thing really well. I could take this code and re-use it anywhere for other views with the right changes.
Break It down
- Scrollable horizontally
- Scrollable vertically
- Each
Row
needs to support multiple Columns
So Once you've broken it down you need to work one issue at a time. Let's start with the scrollable horizontally.
Horizontal Scrolling
Here is a quick and dirty solution to solve for horizontal scrolling. This is only part of the problem but it sets us up for a foundation to work the rest of the problem.
struct FirstView: View {
var body: some View {
ScrollView {
ForEach(0..<5) { index in
// NOTE: ANY VIEW can go in here, including collections :D
Rectangle()
.fill(.black)
.frame(width: UIScreen.main.bounds.size.width * 0.8,
height: UIScreen.main.bounds.size.height * 0.8)
}
}
}
}
Adding Vertical Scrolling
The next part of the problem is to add some vertical scrolling. With our ForEach
loop we previously defined we can add any view inside of each "Section" so to speak, so we could add another scrolling view.
struct FirstView: View {
var body: some View {
ScrollView(.vertical) {
ForEach(0..<5) { _ in
// NOTE: Notice I just added another ScrollView and changed it's direction to horizontal.
ScrollView(.horizontal) {
LazyHStack {
ForEach(0..<5) { _ in
Rectangle()
.fill(.black)
.frame(width: UIScreen.main.bounds.size.width * 0.8,
height: UIScreen.main.bounds.size.height * 0.8)
}
}
}.frame(height: UIScreen.main.bounds.height)
}
}
}
}
Another thing to notice here is on my inner ForEach loop I used a LazyHStack
which tells it to layout those views horizontally, which is a requirement to have them laid out in that direction. To boot it also helps save memory because the views will only be loaded when they are going to be presented. You can also wrap the outer in a LazyVStack
for the same effect.
Make it accept differing amount of views
This final piece has a ton of leeway to handle it the way you want and you're likely going to want to add in some QOL
things or Quality of Life changes, eg snapping to a column/row, centering, etc but I'll leave that up to you to search up as there's tons of information available on the internet for that stuff. Notice that I'm using 0..<5
for the ForEach
loops. That is the value you're going to want to change. I recommend using an [[<someObject>]]
to manage that. That's an array of arrays if you're confused. The reason for that is the outer array object could contain your Rows
where the inner array could contain your Columns
and it makes going through them infinitely easier to manage and track. An example object might look something like this.
var posts: [[Post]] = [[Post1, Post2, Post3],
[Post1, Post2, Post3]]
Which would return two rows
with three columns
each. And you could plug the count of that into your ForEach
in some fashion like this. ForEach(0..<posts.count)
and then on the inner ForEach(0..<posts[index].count)
.
I hope this helps in some small way, and good luck!