How to Create Grid in Swiftui

How can I create a custom view that only displays a grid of lines?

You can do custom drawing with SwiftUI using Path (Path Documentation, Tutorial)

To draw a grid you can use something like the following:

struct ContentView : View {
var horizontalSpacing: CGFloat = 48
var verticalSpacing: CGFloat = 48

var body: some View {
GeometryReader { geometry in
Path { path in
let numberOfHorizontalGridLines = Int(geometry.size.height / self.verticalSpacing)
let numberOfVerticalGridLines = Int(geometry.size.width / self.horizontalSpacing)
for index in 0...numberOfVerticalGridLines {
let vOffset: CGFloat = CGFloat(index) * self.horizontalSpacing
path.move(to: CGPoint(x: vOffset, y: 0))
path.addLine(to: CGPoint(x: vOffset, y: geometry.size.height))
}
for index in 0...numberOfHorizontalGridLines {
let hOffset: CGFloat = CGFloat(index) * self.verticalSpacing
path.move(to: CGPoint(x: 0, y: hOffset))
path.addLine(to: CGPoint(x: geometry.size.width, y: hOffset))
}
}
.stroke()
}
}
}

How to create grid in SwiftUI

You can create your customView like this to achieve UICollectionView behavior:-

struct ContentView : View {
var body: some View {
VStack(alignment: .leading, spacing: 10) {
ScrollView(showsHorizontalIndicator: true) {
HStack {
ForEach(0...10) {_ in
GridView()
}
}
}
List {
ForEach(0...5) {_ in
ListView()
}
}
Spacer()
}
}
}

struct ListView : View {
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello World!"/*@END_MENU_TOKEN@*/)
.color(.red)
}
}

struct GridView : View {
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Image("marker")
.renderingMode(.original)
.cornerRadius(5)
.frame(height: 200)
.border(Color.red)
Text("test")
}
}
}

Sample Image

How to make SwiftUI Grid lay out evenly based on width?

Preview

You can use this method to achieve what you're looking for, solution source: https://www.fivestars.blog/articles/flexible-swiftui/

ContentView

struct ContentView: View {
// MARK: - PROPERTIES

var data = [
"Beatles",
"Pearl Jam",
"REM",
"Guns n Roses",
"Red Hot Chili Peppers",
"No Doubt",
"Nirvana",
"Tom Petty and the Heart Breakers",
"The Eagles"

]

// MARK: - BODY

var body: some View {
FlexibleView(
availableWidth: UIScreen.main.bounds.width, data: data,
spacing: 15,
alignment: .leading
) { item in
Text(verbatim: item)
.padding(8)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(Color.gray.opacity(0.2))
)
}
.padding(.horizontal, 10)
}

}

// MARK: - PREVIEW

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

FlexibleView

// MARK: - FLEXIBLE VIEW

struct FlexibleView<Data: Collection, Content: View>: View where Data.Element: Hashable {
let availableWidth: CGFloat
let data: Data
let spacing: CGFloat
let alignment: HorizontalAlignment
let content: (Data.Element) -> Content
@State var elementsSize: [Data.Element: CGSize] = [:]

var body : some View {
VStack(alignment: alignment, spacing: spacing) {
ForEach(computeRows(), id: \.self) { rowElements in
HStack(spacing: spacing) {
ForEach(rowElements, id: \.self) { element in
content(element)
.fixedSize()
.readSize { size in
elementsSize[element] = size
}
}
}
}
}
}

func computeRows() -> [[Data.Element]] {
var rows: [[Data.Element]] = [[]]
var currentRow = 0
var remainingWidth = availableWidth

for element in data {
let elementSize = elementsSize[element, default: CGSize(width: availableWidth, height: 1)]

if remainingWidth - (elementSize.width + spacing) >= 0 {
rows[currentRow].append(element)
} else {
currentRow = currentRow + 1
rows.append([element])
remainingWidth = availableWidth
}

remainingWidth = remainingWidth - (elementSize.width + spacing)
}

return rows
}
}

View Extension

// MARK: - EXTENSION

extension View {
func readSize(onChange: @escaping (CGSize) -> Void) -> some View {
background(
GeometryReader { geometryProxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: geometryProxy.size)
}
)
.onPreferenceChange(SizePreferenceKey.self, perform: onChange)
}
}

private struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {}
}

how to build a grid list like this in swiftUI?



struct ContentView: View {
let numberOfColumn = 5
let rows = [GridItem(.fixed(50)),GridItem(.fixed(50))]
let space:Float = 10.0

var body: some View {
GeometryReader { proxy in
ScrollView(.horizontal) {
LazyHGrid(rows: rows ,spacing: 10, content: {
ForEach(0 ..< numberOfColumn * 2) { item in
Rectangle() //replace with your cell view
.frame(width: CGFloat( getColumnWidth(width: Float(proxy.size.width))))
}
})
}
}
}

func getColumnWidth(width: Float) -> Float {
((width) - ((space)*Float(numberOfColumn-1))) / Float(numberOfColumn)
}
}

SwiftUI - grid / table with column headings

You can just pass the headers into the LazyVGrid before you show the content:

struct Item: Identifiable {
let id = UUID()
var item: String
var description: String = "This is the item description"
var quantity: Int = 1
var price: Double = 0
}


struct ContentView: View {

let data = [
Item(item: "Image 1", quantity: 2, price: 1.99),
Item(item: "Image 2", quantity: 1, price: 3.99),
Item(item: "Image 3", quantity: 5, price: 9.99),
]

let columns = [
GridItem(.flexible(), alignment: .topLeading),
GridItem(.flexible(minimum: 150), alignment: .topLeading),
GridItem(.flexible(), alignment: .topLeading),
GridItem(.flexible(), alignment: .topLeading),
]

var body: some View {

LazyVGrid(columns: columns) {

// headers
Group {
Text("Item")
Text("")
Text("Qty")
Text("Price")
}
.font(.headline)

// content
ForEach(data) { item in
Text(item.item)
Text(item.description)
Text("\(item.quantity)")
Text("$\(item.price, specifier: "%.2f")")
}
}
.padding()
}
}

Swift: How to build a grid in swift ui

Are you looking for something like this:

screenshot

For Xcode 11, iOS 13:

fileprivate let dataDict = [
"a": "apple",
"b": "bat",
"c": "chango",
"d": "december",
"e": "elephant"
]

struct Logo: View {
@State private var currentStoreName: String = ""

private let storeLogo = ["a", "b", "c", "d", "e"]
/// number of columns in grid
private let columns = 3

var body: some View {
makeGrid()
}

private func makeGrid() -> some View {
let count = storeLogo.count
let rows = count / columns + (count % columns == 0 ? 0 : 1)

return VStack(alignment: .leading) {
ForEach(0..<rows) { row in
HStack(alignment: .firstTextBaseline) {
ForEach(0..<self.columns) { column -> AnyView in
let index = row * self.columns + column
if index < count {
let logoKey = self.storeLogo[index]
let logo = dataDict[logoKey]
return AnyView(Text(logo ?? "not found"))
} else {
return AnyView(EmptyView())
}
}
}
}
}
}
}

For Xcode 12 beta, iOS 14:

The newly introduced LazyVGrid or LazyHGrid makes it easier and cleaner to do:

struct Logo: View {
@State private var currentStoreName: String = ""

private let storeLogo = ["a", "b", "c", "d", "e", "f", "g", "h"]
/// number of columns in grid
private let columns = Array(
repeating: GridItem(.fixed(20), spacing: 5, alignment: .center),
count: 3
)

var body: some View {
ScrollView {
LazyVGrid(columns: columns, alignment: .leading, spacing: 5, content: {
ForEach(storeLogo, id: \.self) {
Text($0).foregroundColor(.red)
}
})
}
}
}

How to make SwiftUI LazyGrid rows set heights according to contents, independently

As @Yrb suggests a solution (which seems to me the simplest) is to modify the answer I linked by creating a struct for the rows and then moving the height calculating extension to extend that struct rather than the entire view.

This beautifully calculates the height of each row independently of the others.



Related Topics



Leave a reply



Submit