0

I have checked several questions here about the grid view but my question is a bit different , i want to create a grid view for buttons so each button when pressed navigate to different view, So it has look like this image: This is the grid buttons view

so i have wrote this code here but looks like i wasn't very successful to get what i want, Is there a better idea to achieve this design as grid view ?

import SwiftUI

struct MainCollectionView: View {


var MainCollectionView: CollectionView
   @State private var isActive : Bool = false
var body: some View {
    NavigationView{
    ScrollView {
        ForEach(0..<2) { row in
                HStack {
                    ForEach(0..<2) { col in
                        Button(MainCollectionView.title) {
                    
                    self.isActive = true
                
                    
                        }//button exit
                    .accentColor(Color.black)
                    .padding(.horizontal,16)
                    .padding(.vertical, 10)
                    .background(
                       Capsule().strokeBorder(Color.white, lineWidth: 1.25))
                        
                    }
                    
                }
                
            }
        }}
                        
                        
                        
                    }
                }
  
struct MainCollectionView_Previews: PreviewProvider {
    static var previews: some View {
        MainCollectionView(MainCollectionView: CollectionViewData[0])
    }
}
ijiojl
  • 1
  • 1
  • 1

2 Answers2

3

Create the first grid layout and pass your button view. Here is the grid layout demo. You need to change the button view and give frame to inner button view. Grid layout

struct GridLayout<Content: View>: View {
    
    private let rows: Int
    private let columns: Int
    private let content: (Int, Int) -> Content
    
    init(columns: Int, rows: Int, @ViewBuilder content: @escaping (Int, Int) -> Content) {
        self.rows = rows
        self.columns = columns
        self.content = content
    }
    
    var body: some View {
        GeometryReader { geo in
            VStack(spacing: 10) {
                ForEach(0 ..< rows, id: \.self) { row in
                    HStack(spacing: 10) {
                        ForEach(0 ..< self.columns, id: \.self) { column in
                            self.content(row, column)
                                .frame(width: geo.size.width / 2, height: geo.size.width / 2, alignment: .center)
                        }
                    }
                }
            }
        }
    }
}

MainCollectionView

struct MainCollectionView: View {
    @State private var isActive : Bool = false
    var body: some View {
        NavigationView{
            ScrollView {
                GridLayout(columns: 2, rows: 3) { (row, colom)  in
                    Button {
                        self.isActive = true
                    } label: {
                        Text("Title")
                            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
                    }
                    .background(
                        RoundedRectangle(cornerRadius: 15).strokeBorder(Color.black, lineWidth: 1.25))
                }.padding([.leading, .trailing], 20)
            }
        }
    }
}
Raja Kishan
  • 16,767
  • 2
  • 26
  • 52
  • thank you for your help i am just stuck in this grid view layout in preview it shows this (Missing arguments for parameters 'columns', 'rows', 'content' in call) ? – ijiojl Jan 25 '21 at 18:33
  • Pass same thing which I used in body – Raja Kishan Jan 25 '21 at 18:36
  • static var previews: some View { GridLayoutView(columns: Int, rows: Int, content: (Int, Int) -> MainCollectionView) }// i wrote it like this but it says failed to diagnose – ijiojl Jan 25 '21 at 18:44
  • I found that using a `GeometryReader` just complicated the layout. It does work perfectly well/ better without it, so I'd recommend not using that imo – mylogon Jun 22 '22 at 14:50
0

Based heavily off Raja's answer, I settled on the following

import SwiftUI

struct GridView<Content: View>: View {
  
  private let rows: Int
  private let columns: Int
  private let content: (Int) -> Content
  
  init(columns: Int, rows: Int, @ViewBuilder content: @escaping (Int) -> Content) {
    self.rows = rows
    self.columns = columns
    self.content = content
  }
  
  private func nthPosition(_ x: Int, _ y: Int) -> Int { // swiftlint:disable:this identifier_name
    (columns * y) + x
  }
  
  var body: some View {
    VStack(spacing: 10) {
      ForEach(0 ..< rows, id: \.self) { row in
        HStack(spacing: 10) {
          ForEach(0 ..< self.columns, id: \.self) { column in
            self.content(nthPosition(column, row))
              .frame(maxWidth: .infinity)
          }
        }
      }
    }
  }
}

struct GridView_Previews: PreviewProvider {
  
  private enum ItemThing: Int, CaseIterable {
    case car, dog, house
    
    var name: String {
      switch self {
      case .car: return "car"
      case .dog: return "dog"
      case .house: return "house"
      }
    }
  }

  static var previews: some View {
    GridView(columns: 3, rows: 2) { position in
      VStack {
        Text("Item \(position)")
        
        if let thing = ItemThing(rawValue: position) {
          Text(thing.name)
        }
      }
    }
  }
}

I found the return of the position value to be especially useful so I was able to get something back from an enum.

This is a half decent example for static lists, but for a dynamic list you might want something like

  static var cols = 3
  
  static var rows: Int {
    let count = ItemThing.allCases.count
    return Int(ceil(Double(count) / Double(cols)))
  }

  static var previews: some View {
    GridView(columns: cols, rows: rows) { position in
      VStack {
        Text("Item \(position)")
        
        if let thing = ItemThing(rawValue: position) {
          Text(thing.name)
        }
      }
    }
  }
mylogon
  • 2,772
  • 2
  • 28
  • 42