List in SwiftUI

82 阅读3分钟

在 SwiftUI 中,List 是构建列表式界面非常重要的组件。它可以增,删,移动列表数据,下面我们一起来看看。

下面是初始效果

image.png

struct ListSample: View {
    @State var fruits: [String] = [
        "🍎", "🍊", "🍇", "🍓"
    ]
    
    var body: some View {
        NavigationView {
            List {
                Section {
                    ForEach(fruits, id: .self) {  fruit  in
                        HStack {
                            Text(fruit)
                                .frame(width: 60, height: 60)
                                .font(.title)
                                .background(
                                    Circle()
                                        .fill(Color.green.opacity(0.3))
                                )
                            Text("(Int.random(in: 1..<10)) kg")
                                .font(.subheadline)
                        }
                    }
                    .onDelete { indexSet in
                        fruits.remove(atOffsets: indexSet)
                    }
                    .onMove { indexSet, index in
                        fruits.move(fromOffsets: indexSet, toOffset: index)
                    }
                    .listRowBackground(Color.green.opacity(0.1))
                } header: {
                    HStack {
                        Text("🥑")
                        Text("fruit".capitalized)
                            .foregroundColor(Color.black)
                     
                        Text("((fruits.count))")
                            .font(.subheadline)
                            .fontWeight(.semibold)
                    }
                }
            }
            .navigationTitle(Text("Grocery list"))
            .navigationBarTitleDisplayMode(.automatic)
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    EditButton()
                }
                
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button("Add") {
                        fruits.append("🥝")
                    }
                }
            }
        }
    }
}

上述示例,是一个完整的增,删,移动的代码。

增加

由于我们给数据源的修饰符是带有 @State, 所以当我们往数组添加数据,列表就会自动刷新。因为当有数据更新,被 @State 修饰的属性会自动通知被使用的组件进行数据重新刷新。

所以当我们点击右上角的添加按钮时,我们往数组里面添加了一条数据。所以列表就会自动刷新

Button("Add") {
  fruits.append("🥝")
}

删除

我们目前的删除逻辑是耦合在UI一起的,其实我们应该分开UI和业务逻辑。目前的删除代码如下:

.onDelete { indexSet in
    fruits.remove(atOffsets: indexSet)
}

我们给List增加了onDelete方法,当我们左滑就可以看到删除按钮,当我们点击删除按钮就会删除掉当前这一行。

image.png

当我们点击onDelete方法查看苹果提供的官方文档时,我们会看到此删除方法需要一个IndexSet 参数。

我们可以改造一下现有代码。我们先把逻辑和UI分开. 代码如下:

.onDelete { indexSet in
  delete(indexSet: indexSet)
}

func delete(indexSet: IndexSet) {
  fruits.remove(atOffsets: indexSet)
}

我们把删除逻辑提取成一个函数,这样UI部分就只专注UI,不需要嵌套逻辑了。

但是苹果给我们提供了很简便的方式来书写代码,其实我们只需要传入删除方法的名称就可以完成删除功能了,简便写法方法如下:

.onDelete(perform: delete) // delete 是方法名称

// 下面方法不变
func delete(indexSet: IndexSet) {
  fruits.remove(atOffsets: indexSet)
}

移动

移动也很简单,也只需要一行代码。但是我们目前的业务逻辑依然和UI耦合在一起的。还是需要和删除方法一样。把UI部分和逻辑分开

UI和逻辑一起的代码:

.onMove { indexSet, index in
  fruits.move(fromOffsets: indexSet, toOffset: index)
}

分开后的代码:

.onMove(perform: move)

func move(indexSet: IndexSet, index: Int) {
  fruits.move(fromOffsets: indexSet, toOffset: index)
}

其实添加的逻辑代码和UI也是耦合的,也是需要分开的。

Screenshot 2023-07-20 at 22.10.49.png

listStyle

这是一个很重要的属性,它可以改变List的外观。下面我们一起来看看。为了更好的看出效果。我增加了一些数据。最终效果如下

image.png

struct ListSample: View {
    @State var fruits: [String] = [
        "🍎", "🍊", "🍇", "🍓"
    ]
    
    var vegetables: [String] = [
        "🥦", "🥒", "🧅", "🥬"
    ]
    
    var body: some View {
        NavigationView {
            List {
                Section {
                    ForEach(fruits, id: .self) {  fruit  in
                        HStack {
                            Text(fruit)
                                .frame(width: 50, height: 50)
                                .font(.title)
                                .background(
                                    Circle()
                                        .fill(Color.green.opacity(0.3))
                                )
                            Text("(Int.random(in: 1..<10)) kg")
                                .font(.subheadline)
                        }
                    }
                    .onDelete(perform: delete)
                    .onMove(perform: move)
                    .listRowBackground(Color.green.opacity(0.1))
                } header: {
                    HStack {
                        Text("🥑")
                        Text("fruit".capitalized)
                            .foregroundColor(Color.black)
                        
                        Text("((fruits.count))")
                            .font(.subheadline)
                            .fontWeight(.semibold)
                    }
                } footer: {
                    Text("🦶 fruit")
                }

                Section {
                    ForEach(vegetables, id: .self) {  vegetable  in
                        HStack {
                            Circle()
                                .fill(Color.yellow.opacity(0.3))
                                .frame(width: 50, height: 50)
                                .overlay(
                                    Text(vegetable)
                                        .font(.title)
                                )
                            Text("(Int.random(in: 1..<10)) kg")
                                .font(.subheadline)
                        }
                    }
                    .listRowBackground(Color.yellow.opacity(0.1))
                } header: {
                    HStack {
                        Text("🥕")
                        Text("vegetable".capitalized)
                            .foregroundColor(Color.black)
                        
                        Text("((vegetables.count))")
                            .font(.subheadline)
                            .fontWeight(.semibold)
                    }
                } footer: {
                    Text("🦶 vegetable")
                }
            }
            .navigationTitle(Text("Grocery list"))
            .navigationBarTitleDisplayMode(.automatic)
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    EditButton()
                }
                
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button("Add") {
                        fruits.append("🥝")
                    }
                }
            }
        }
    }
    
    func delete(indexSet: IndexSet) {
        fruits.remove(atOffsets: indexSet)
    }
    
    func move(indexSet: IndexSet, index: Int) {
        fruits.move(fromOffsets: indexSet, toOffset: index)
    }
}

需要注意的时,其实Section内部的代码应该提取出来,避免一个方法内部过长。但是这个任务就交给你吧。你来试试?

ListStyle总共有六个值

  1. automatic
  2. sidebar
  3. insetGrouped
  4. grouped
  5. inset
  6. plain

下面来做一个效果对比,以方便你在日后的开发中。更能一目了然的知道效果。

Frame 1.png

以上就是今天要说的内容,希望你喜欢。

大家有什么看法呢?欢迎留言讨论。
公众号:RobotPBQ