UPDATE: This answer is related to iOS 13. For iOS 14 we have LazyGrids + a lot more stuff and following this answer will not be helpful.
For making a CollectionView without using UIKit, first of all we need an array extension. the array extension will help us chunk our array which we want to make a TableView around. Below is the code for the extension, + 3 examples. To a-little-bit-further understand how this extension works, take a look at this site, which i copied the extension from : https://www.hackingwithswift.com/example-code/language/how-to-split-an-array-into-chunks
extension Array {
func chunked(into size: Int) -> [[Element]] {
return stride(from: 0, to: count, by: size).map {
Array(self[$0 ..< Swift.min($0 + size, count)])
}
}
}
let exampleArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
print(exampleArray.chunked(into: 2)) // prints [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]]
print(exampleArray.chunked(into: 3)) // prints [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]
print(exampleArray.chunked(into: 5)) // prints [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12]]
Now lets make our SwiftUI view:
struct TestView: View {
let arrayOfInterest = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18].chunked(into: 4)
// = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16], [17, 18]]
var body: some View {
return VStack {
ScrollView {
VStack(spacing: 16) {
ForEach(self.arrayOfInterest.indices, id:\.self) { idx in
HStack {
ForEach(self.arrayOfInterest[idx].indices, id:\.self) { index in
HStack {
Spacer()
Text("\(self.arrayOfInterest[idx][index])")
.font(.system(size: 50))
.padding(4)
.background(Color.blue)
.cornerRadius(8)
Spacer()
}
}
}
}
}
}
}
}
}
struct TestView_Preview : PreviewProvider {
static var previews: some View {
TestView()
}
}
Image of Preview of the code above
Explanation:
First of all we need to make it clear how many columns do we need and put that number into our chunked extension. In my example, we have an array(arrayOfInterest) of numbers from 1 to 18 which we want to show in our view, and i decided that i want my view to have 4 columns, so i chunked it into 4(so 4 is number of our columns).
To make a CollectionView, the most obvious thing is that our CollectionView is a LIST of items, so it should be in a list to make it easily scrollable (NO, DO NOT DO THAT! use a ScrollView instead. i've seen weird behaviours while those 2 foreachs are in a list).
after the ScrollView we have 2 ForEach s, the first one enables us to loop as many Rows as needed, while the second one helps us make the columns.
I know i didn't explain the code perfectly, but i'm sure it is worth sharing with you so can make you table views easier.
This Image is an early example of a real app i'm making, and it looks nothing short of CollectionView, so you can be sure that this approach works well.
QUESTION: whats the point of having an array and trying to let swift make those indices for foreach?
its simple! if you have an array which defines its values/number-of-values in runtime, e.g. you are getting the numbers from a web api and that api tells you how many numbers are in your array, then you'll need to use some approach like this and let swift take care of indices of foreachs.
UPDATE:
More Info, reading these is optional.
LIST VS SCROLLVIEW: as some of you may not know, list works a little bit different from a scroll view. when you create a scrollview, it always calculates whole the ScrollView, then shows it to us. but list doesnt do that, when using lists, swift automatically calculates only a few of the list's components which are needed to show the current view, and when you scroll down to the bottom of the list, it only replaces the old values which are being scrolled out, with the new values of those which are at the bottom of the screen, with. so in general, list is always lighter, and can be much much faster when you are working with a heavy view, because it doesn't calculate all of your view at the beginning, and only calculates necessary things, while ScrollView doesn't.
WHY DID YOU SAY WE SHOULD USE SCROLLVIEW INSTEAD OF LIST?
as i said before, there are some interactions with list that you probably dont like. for example when creating a list, every row is tappable, which is fine, but what is not fine is that ONLY the whole row is tappable! that means you cant set a tap action for the left side of a row, and a different one for the right side! this is just one of the weird interactions of a List()
this either needs some knowledge i dont have! or is a big xcode-ios issue, or maybe its just fine and as intended! what i think is that its an apple issue and i hope it'll get fixed till at most the next WWDC. (UPDATE: and it of course got fixed with introduction of all the stuff like LazyGrids for iOS14-SwiftUI)
ANY WAYS TO OVERCOME THIS PROBLEM?
as far as i know, the only way is to use UIKit. I've tried many many ways with SwiftUI, and although i've found out that you can get help from ActionSheet and ContextMenu to make lists better in terms of options when you tap them, i was unable to get the optimal intended functionality out of a SwiftUI List. so from my POV, SwiftUI devs can only wait for now.