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")
}
}
}
How to make SwiftUI Grid lay out evenly based on width?
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:
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
How to Subclass Nsoperation in Swift to Queue Skaction Objects for Serial Execution
Uibarbuttonitem Selector Not Working
Making Swift Generics Play with Overloaded Functions
How Do Generators Whose Element Is Optional Know When They'Ve Reached the End
How to Say "If X == a or B or C" as Succinctly in Swift as Possible
Most Efficient Way to Access Multi-Dimensional Arrays in Swift
Pattern Match and Conditionally Bind in a Single Switch Statement
Binding in a Foreach in Swiftui
What Is the Type of the Logical Operators
Subtle Cast Warning When Using SQLite.Swift ... Binding? to Any
In Swift,There's No Way to Get the Returned Function's Argument Names
Create an Outlet in Storyboard to an Inherited Property
How to Send Push Notifications Without Using Firebase Console
Opposite of _Conversion in Swift to Assign to a Value of a Different Type