如何在 SwiftUI 中创建网格

How to create grid in SwiftUI

我知道我们可以像这样在垂直 SwiftUI 中创建一个列表,

struct ContentView : View {
    var body: some View {
        NavigationView {
            List {
                Text("Hello")
            }
        }
    }
}

但是有什么方法可以像我们在 UICollectionView

中那样将列表分成 2 个或 3 个或更多个像网格一样覆盖屏幕的跨度

您可以像这样创建自定义视图以实现 UICollectionView 行为:-

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")
        }
    }
}

签出基于 ZStack 的示例 here

Grid(0...100) { _ in
    Rectangle()
        .foregroundColor(.blue)
}

iOS/iPadOS 14 Xcode 12 可用。您可以使用 LazyVGrid 只加载用户在屏幕上看到的内容而不是整个列表,List 默认情况下是惰性的。

import SwiftUI

//MARK: - Adaptive
struct ContentView: View {
    
    var body: some View {
        ScrollView {
            LazyVGrid(columns: [GridItem(.adaptive(minimum:100))]) {
                ForEach(yourObjects) { object in
                    YourObjectView(item: object)
                }
            }
        }
    }
}

//MARK: - Custom Columns
struct ContentView: View {
        
    var body: some View {
         ScrollView {   
             LazyVGrid(columns: Array(repeating: GridItem(), count: 4)) {
                 ForEach(yourObjects) { object in
                     YourObjectView(item: object)
                 }
             }
         }
    }
}

不要忘记将信息 objects 替换为您的信息,并将 YourObjectView 替换为您的 customView.

iOS14

您可以使用 2 个新的原生 View

  1. LazyHGrid

  2. LazyVGrid

使用代码或直接来自库:

该库包含一个完整的示例代码,您可以自行测试。

SwiftUI 的 LazyVGridLazyHGrid 为我们提供了相当灵活的网格布局。

最简单的网格由三部分组成:您的原始数据、描述您想要的布局的 GridItem 数组,以及汇集在一起​​的 LazyVGrid 或 LazyHGrid您的数据和布局。

例如,这将使用大小为 80 磅的单元格创建垂直网格布局:

struct ContentView: View {
    let data = (1...100).map { "Item \([=10=])" }

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

    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns, spacing: 20) {
                ForEach(data, id: \.self) { item in
                    Text(item)
                }
            }
            .padding(.horizontal)
        }
        .frame(maxHeight: 300)
    }
}

使用 GridItem(.adaptive(minimum: 80)) 意味着我们希望网格在每行中容纳尽可能多的项目,每个项目的最小尺寸为 80 点.

如果你想控制列数,你可以使用 .flexible() 代替,这也让你指定每个项目应该有多大,但现在让你控制如何有很多专栏。例如,这将创建五列:

struct ContentView: View {
    let data = (1...100).map { "Item \([=11=])" }

    let columns = [
        GridItem(.flexible()),
        GridItem(.flexible()),
        GridItem(.flexible()),
        GridItem(.flexible()),
        GridItem(.flexible())
    ]

    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns, spacing: 20) {
                ForEach(data, id: \.self) { item in
                    Text(item)
                }
            }
            .padding(.horizontal)
        }
        .frame(maxHeight: 300)
    }
}

第三种选择是使用固定尺寸。例如,这将使第一列恰好为 100 点宽,并允许第二列填满所有剩余的 space:

struct ContentView: View {
    let data = (1...100).map { "Item \([=12=])" }

    let columns = [
        GridItem(.fixed(100)),
        GridItem(.flexible()),
    ]

    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns, spacing: 20) {
                ForEach(data, id: \.self) { item in
                    Text(item)
                }
            }
            .padding(.horizontal)
        }
        .frame(maxHeight: 300)
    }
}

您还可以使用 LazyHGrid 制作水平滚动网格,其工作方式大致相同,只是它在其初始化程序中接受行。

例如,我们可以创建 10 个水平滚动的并排标题图像,如下所示:

struct ContentView: View {
    let items = 1...50

    let rows = [
        GridItem(.fixed(50)),
        GridItem(.fixed(50))
    ]

    var body: some View {
        ScrollView(.horizontal) {
            LazyHGrid(rows: rows, alignment: .center) {
                ForEach(items, id: \.self) { item in
                    Image(systemName: "\(item).circle.fill")
                        .font(.largeTitle)
                }
            }
            .frame(height: 150)
        }
    }
}

如您所见,创建水平和垂直网格所需的代码几乎相同,只是更改列的行。

如果您需要支持 iOS13,您将无法访问 LazyHGrid 或 LazyVGrid,因此请阅读下面的替代方法……

SwiftUI 为我们提供了用于垂直布局的 VStack 和用于水平布局的 HStack,但两者都没有——没有任何东西可以在网格结构中布置视图。

幸运的是,我们可以利用 SwiftUI 的视图构建器系统自己编写一个。这意味着编写一个必须使用行数和列数创建的类型,加上一个闭包,它可以 运行 检索网格中给定单元格的视图。在 body 中,它可以遍历所有行和列,并在 VStack 和 HStack 中创建单元格以制作网格,每次调用视图闭包询问单元格中应该包含什么。

在代码中它看起来像这样:

struct GridStack<Content: View>: View {
    let rows: Int
    let columns: Int
    let content: (Int, Int) -> Content

    var body: some View {
        VStack {
            ForEach(0 ..< rows, id: \.self) { row in
                HStack {
                    ForEach(0 ..< columns, id: \.self) { column in
                        content(row, column)
                    }
                }
            }
        }
    }

    init(rows: Int, columns: Int, @ViewBuilder content: @escaping (Int, Int) -> Content) {
        self.rows = rows
        self.columns = columns
        self.content = content
    }
}

// An example view putting GridStack into practice.
struct ContentView: View {
    var body: some View {
        GridStack(rows: 4, columns: 4) { row, col in
            Image(systemName: "\(row * 4 + col).circle")
            Text("R\(row) C\(col)")
        }
    }
}

这将创建一个 4x4 网格,每个单元格中都有一个图像和文本。

快乐编码;)