4

I am trying to implement grid layout in collectionView. This is my current view enter image description here

instead of 1 item per collection I would like to show 3 items

this is my productView

struct ProductSearchView: View {
@Environment(\.presentationMode) var presentationMode

 @ObservedObject var model: SearchResultViewModel
var body: some View{
    NavigationView {
         List {
                ForEach(0 ..< Global.productArry.count) { value in
                  Text(Global.productArry[value].name)
                    CollectionView(model: self.model, data: Global.productArry[value])
                }
                
         }.navigationBarTitle("Store")
    }


}

}

and this is my collection view

struct CollectionView: View {
@ObservedObject var model: SearchResultViewModel

let data: Product
    var body: some View {
        VStack {
            HStack {
                Spacer()
                    AsyncImage(url: URL(string: self.data.productImageUrl)!, placeholder: Text("Loading ...")
                    ).aspectRatio(contentMode: .fit)
                Spacer()
               
            }
            VStack {
                Spacer()
                Text(self.data.name)
                Spacer()
                
            }
            HStack {
                Text("Aisle: \(self.data.location_zone)\(String(self.data.aisleNo))").bold()
                Text("$\(String(self.data.productPrice))")
            }
        }.onAppear(perform:thisVal)
     }

func thisVal (){
    
    print(self.data.productImageUrl)
    
}

}

how can I implement a grid of three items ?

e.iluf
  • 1,389
  • 5
  • 27
  • 69
  • If I understood your issue correctly, I guess https://stackoverflow.com/a/63397243/10005005 this will help you. Giving columns as 3 will solve your issue. Please check! – Sona Aug 15 '20 at 09:51

3 Answers3

1

Use a LazyVGrid. The following example is very simple and easy to read and you can change it to anything you need.

struct ContentView: View {
    let data = (1...100).map { "Item \($0)" }

    let columns = [
        GridItem(.adaptive(minimum: 80))
    ]

    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns, spacing: 20) {
                ForEach(data, id: \.self) { _ in
                    Circle().scaledToFill()
                }
            }
            .padding(.horizontal)
        }
    }
}

Demo

Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278
0

The code snippet below is what you need if you are using SwiftUI version 1 which came with Xcode 11. It is a custom implementation which simply use a combination of ScrollView, VStack and HStack as well as a little maths to calculate the frame size depending on the number of items.

import SwiftUI

struct GridView<Content, T>: View where Content: View {
    // MARK: - Properties
    var totalNumberOfColumns: Int
    var numberRows: Int {
        return (items.count - 1) / totalNumberOfColumns
    }
    var items: [T]
    /// A parameter to store the content passed in the ViewBuilder.
    let content: (_ calculatedWidth: CGFloat,_ type: T) -> Content
    
    // MARK: - Init
    init(columns: Int, items: [T], @ViewBuilder content: @escaping(_ calculatedWidth: CGFloat,_ type: T) -> Content) {
        self.totalNumberOfColumns = columns
        self.items = items
        self.content = content
    }
    
    // MARK: - Helpers
    /// A function which help checking if the item exist in the specified index to avoid index out of range error.
    func elementFor(row: Int, column: Int) -> Int? {
        let index:Int = row * self.totalNumberOfColumns + column
        return index < items.count ? index : nil
    }
    
    // MARK: - Body
    var body: some View {
        GeometryReader { geometry in
            ScrollView{
                VStack {
                    ForEach(0...self.numberRows,id: \.self) { (row) in
                        HStack {
                            ForEach(0..<self.totalNumberOfColumns) { (column) in
                                Group {
                                    if (self.elementFor(row: row, column: column) != nil)  {
                                        self.content(geometry.size.width / CGFloat(self.totalNumberOfColumns), self.items[self.elementFor(row: row, column: column)!])
                                            .frame(width: geometry.size.width / CGFloat(self.totalNumberOfColumns), height: geometry.size.width / (CGFloat(self.totalNumberOfColumns)), alignment: .center)
                                    }else {
                                        Spacer()
                                    }
                                }
                            }
                        }
                        
                    }
                }
            }
        }
    }
}


Usage: It is generic ready to use with any view of your choice. For instance the Preview code below uses the system images.

struct GridView_Previews: PreviewProvider {
    static var previews: some View {
        GridView(columns: 3, items: ["doc.text.fill","paperclip.circle.fill", "globe","clear.fill","sun.min.fill", "cloud.rain.fill", "moon","power"], content: { gridWidth,item  in
            Image(systemName: item)
                .frame(width: gridWidth, height: gridWidth, alignment: .center)
                .border(Color.orange)
        })
        .padding(10)
    }
}

Output OutputImage

You can change the code to fit your needs for example paddings etc.

NOTE: Use this method only if you don't have lots of views as it is not very performant. If you have a lot of views to scroll I suggest you use the newer and simple build-in GridItem from Apple which was introduced this year at WWDC. But you will have to use Xcode 12 which currently is available as Beta version.

Mussa Charles
  • 4,014
  • 2
  • 29
  • 24
-1

put for loop logic in HStack like givern below.

HStack {
ForEach(0..<3) { items in
Spacer()
   AsyncImage(url: URL(string: self.data.productImageUrl)!, placeholder: Text("Loading ...")
                ).aspectRatio(contentMode: .fit)
Spacer()
}.padding(.bottom, 16)
}