LazyGrid in SwiftUI

679 阅读4分钟

LazyGrid分为LazyVGridLazyHGrid两种布局,但是用法都大同小异,这里就拿LzayVGrid来举例

LazyVGrid是SwiftUI中实现懒加载网格布局的强大工具。
它的主要作用有:

  1. 懒加载网格内容,避免一次性加载全部 Views导致性能问题
  2. 支持可变长和可变宽的网格布局
  3. 提供便捷的对齐、间距等布局配置方式
  4. 支持自适应不同尺寸的内容排布

下面是一个例子

image.png

struct LazyGridSample: View {
    let columns = [
        GridItem(.fixed(80))
    ]
    
    var body: some View {
        LazyVGrid(columns: columns) {
            ForEach(0..<21) { index in
                Rectangle()
                    .fill()
                    .frame(height: 80)
            }
        }
    }
}

这里是循环了21个矩形,数据源是 columns , columns 的值有固定的要求,它是一个GridItem类型的数组。

上面代码会把21个矩形,按照一列的方式来排放,并且固定宽度为80。

GridItem 有三个参数,分别是sizespacingalignment, 主要说说size参数,其他的两个参数前面的教程都有讲过。

Size 是一个枚举类型,默认是 .flexible()

// 方法定义
public init(_ size: GridItem.Size = .flexible(), spacing: CGFloat? = nil, alignment: Alignment? = nil)

// size 枚举值
public enum Size {

        /// 具有指定固定大小的单个项目。
        case fixed(CGFloat)

        // 填充剩余空间
        case flexible(minimum: CGFloat = 10, maximum: CGFloat = .infinity)

         // 自适应最小大小
        case adaptive(minimum: CGFloat, maximum: CGFloat = .infinity)
    }

首先说说Size的第一个值 .fixed()

.fixed()

它的特点是以固定宽度来显示视图

我们稍微改造一下上面例子中的 columns 数组。

image.png

struct LazyGridSample: View {
    let columns = [
        GridItem(.fixed(80)),
        GridItem(.fixed(120)),
        GridItem(.fixed(80))
    ]
    
    var body: some View {
        LazyVGrid(columns: columns) {
            ForEach(0..<21) { index in
                Rectangle()
                    .fill()
                    .frame(height: 100)
            }
        }
    }
}

现在是把21个矩形,按照三列来显示,左右两列的大小是固定大小为80,中间的固定宽度为120

.flexible()

它的特点是尽量占用剩余空间,这个很抽象,我们用例子来说明

我们把上面例子中的GridItem的size值变成 flexible 类型。来看看效果

image.png

    struct LazyGridSample: View {
        let columns = [
            GridItem(.flexible())
        ]
        
        var body: some View {
            LazyVGrid(columns: columns) {
                ForEach(0..<21) { index in
                    Rectangle()
                        .fill()
                        .frame(height: 100)
                }
            }
        }
    }

在这里它会尽量占用和屏幕一样宽的宽度,因为除了它本身视图以外,这一行没有其他试图,所以它会尽可能的占用剩余空间,所以就变成了一行

我们在稍加修改,把columns,变成三个GridItem,并且size的值都是尽量占用剩余空间的.flexible()值,下面就是效果,因为每个GridItem之间是有spacing的,所以会看到中间有空格,你可以把spacing设置为0。

image.png

我们可以看到上图,三个GridItem会平分一行,尽可能的占用剩余面积

如果上面的例子还不是太懂,可以看下面的例子

image.png

例子中,左边列是固定宽度为100,左边为flexible。它会尽可能大的占据整行除了左边100的部分

.adaptive()

大小将自动根据可用空间进行调整,这意味着它可以根据网格布局中的剩余空间动态调整大小,以填充可用空间,而不需要明确指定固定的大小

image.png

    struct LazyGridSample: View {
        let columns = [
            GridItem(.adaptive(minimum: 50, maximum: 300))
        ]
        
        var body: some View {
            LazyVGrid(columns: columns) {
                ForEach(0..<21) { index in
                    Rectangle()
                        .fill()
                        .frame(height: 50)
                }
            }
        }
    }

看下面的例子

image.png

    struct LazyGridSample: View {
        let columns = [
            GridItem(.adaptive(minimum: 50, maximum: 300)),
            GridItem(.adaptive(minimum: 150, maximum: 300))
        ]
        
        var body: some View {
            LazyVGrid(columns: columns) {
                ForEach(0..<21) { index in
                    Rectangle()
                        .fill()
                        .frame(height: 50)
                }
            }
        }
    }

当我们变成两列时,第一列设置为最小宽度为50,第二列最小宽度为150时。它会把右边的三列变成一列,最小宽度为150,因为右边的距离只可以显示一个宽度为150的大小。所以只会显示一列

如果有兴趣你可以把上面的Columns的数据变成下面这样

 let columns = [
        GridItem(.adaptive(minimum: 10, maximum: 300)),
        GridItem(.adaptive(minimum: 50, maximum: 300))
    ]

效果就变成下面这样了

image.png

在最后结束前,我会写一个和实际业务很类似的页面。来巩固一下前面提到的知识点。

以下是示例代码和效果图

ezgif.com-video-to-gif.gif

struct LazyGridSample: View {
    let columns = [        GridItem(.flexible()),        GridItem(.flexible()),        GridItem(.flexible())    ]
    
    var body: some View {
        ScrollView {
            
            Rectangle()
                .fill(Color.orange)
                .frame(height: 250)
            
            LazyVGrid(
                columns: columns,
                alignment: .center,
                spacing: 6,
                pinnedViews: [.sectionHeaders]
            ) {
                Section {
                    ForEach(0..<19) { index in
                        Rectangle()
                            .fill()
                            .frame(height: 150)
                    }
                } header: {
                    Text("Section 1")
                        .font(.title)
                        .foregroundColor(Color.white)
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .background(Color.blue)
                        .padding()
                }
                
                Section {
                    ForEach(0..<49) { index in
                        Rectangle()
                            .fill(Color.purple)
                            .frame(height: 150)
                    }
                } header: {
                    Text("Section 2")
                        .font(.title)
                        .foregroundColor(Color.white)
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .background(Color.orange)
                        .padding()
                }
            }
        }
    }
}

以上效果很像一个照片瀑布流,例子里面有两个section,顶部是一个黄色矩形.其中LazyVGrid参数中的 PinnedViews需要说明一下

LazyVGrid(
                columns: columns,
                alignment: .center,
                spacing: 6,
                pinnedViews: [.sectionHeaders]
            ) {}

它接受一个PinnedScrollableViews类型数组参数,这里限定为我们使用的是一个sectoin视图,它的值限定为sectionFooterssectionHeaders, 它的作用是,在视图滚动时,会固定住footer或者header,效果就是上图中的,section 2 会把 section 1 挤上去。

欢迎大家留言,也 Star 我的Github SwiftUI 系列教程