SwiftUI极简教程16:List列表的使用方法进阶学习

简介: SwiftUI极简教程16:List列表的使用方法进阶学习

在本章中,我们将基于List列表的基本使用方法上,进阶学习List列表的更多用法。

本章节将分成3个部分讲解。


1、onDelete滑动删除和onMove拖动排序

2、ContextMenu上下文菜单

3、ActionSheets弹窗的使用


那我们开始吧。


第一部分:onDelete滑动删除和onMove拖动排序


首先,我们先创建一个新项目,命名为SwiftUIList02

image.png

我们创建一个简单的列表,这里引用之前的List创建的代码。

完整代码如下:


import SwiftUI
struct Message: Identifiable {
    var id = UUID()
    var name: String
    var image: String
}
// 定义数组,存放数据
var Messages = [
    Message(name: "这是微信", image: "weixin"),
    Message(name: "这是QQ", image: "qq"),
    Message(name: "这是微博", image: "weibo"),
    Message(name: "这是手机", image: "phone"),
    Message(name: "这是邮箱", image: "mail"),
]
struct ContentView: View {
    var body: some View {
        // 列表
        List {
            ForEach(Messages) { Message in
                HStack {
                    Image(Message.image)
                        .resizable()
                        .frame(width: 40, height: 40)
                        .cornerRadius(5)
                    Text(Message.name)
                        .padding()
                }
            }
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
复制代码

这里我们使用了List列表和ForEach循环的方法建立了一个列表。

运行后的效果如下:

image.png

实现单条List列表数据的滑动删除,我们需要调用.onDelete(perform:XXXX)修饰符,它是ForEach的修饰符,用来删除List中的一条条数据;

perform中引用的是删除的方法,我们定义一个删除方法为deleteRow,具体实现方法如下:


//滑动删除方法
func deleteRow(at offsets: IndexSet) {
    Messages.remove(atOffsets: offsets)
}
复制代码


deleteRow删除列的方法中,我们接收单一的 IndexSet类型的参数,它是用来定位要删除的列的位置的。

然后调用remove(atOffsets:XXXX)方法来删除Messages数组中的被定位的特定项。

这样我们就可以实现列表的滑动删除,运行模拟器,点击一行向右滑动,我们就可以实现删除一行数据。

image.png

科普一个知识点。

我们运行的时候会发现,我们滑动删了1行,系统自动又“创建”了一行回来。

这是因为每次我们删除List中特定的数据项时,系统会自动更新UI,而更新的数据源是我们创建的Messages数组。


也就是说,每次删除后,系统重新“渲染”页面,但Messages数组数据没有变化,也就删除了啥,就恢复了啥,就变成了怎么也删除不了了。

我们希望的是,删除了特定的数据项时,Messages数组的值也需要同步被更改。

因此,我们必须让SwiftUI监控属性,并在属性值发生变化时更新UI。我们使用@State定义数据:


@State var messagesItems = Messages
复制代码


并且将ForEach循环的数据源由Messages数组,变为我们用@State定义的messagesItems,同时在deleteRow方法中,操作remove的对象也换成messagesItems数组。

这样,我们每次删除数组特定项的时候,messagesItems数组就知道我们删除了什么数据,并且“记住”它,在UI刷新的时候,ForEach就基于messagesItems数组内的内容遍历数据。

image.png

好了,我们实现了List页面的滑动删除操作了。

再补充一个知识点。

如果我们给List列表创建导航栏,还可以使用SwiftUI已经封装好的EditButton编辑按钮,从而实现列表快速进入编辑状态,这在Apple自家的备忘录中可以看到。

当我们点击Edit按钮时,按钮文字会变成done,同时List列表都变成可删除的模式。


NavigationView {
    // 列表
    List {
        ForEach(messagesItems) { Message in
            HStack {
                Image(Message.image)
                    .resizable()
                    .frame(width: 40, height: 40)
                    .cornerRadius(5)
                Text(Message.name)
                    .padding()
            }
        }.onDelete(perform: deleteRow)
    }.navigationBarItems(trailing: EditButton())
}


image.png

再延伸地补充一个知识点。

List列表编辑模式下,除了删除之前,还可以针对于数据项进行拖动排序,我们用的是.onMove(perform:XXXX)修饰符,它也是ForEach的修饰符,用来实现List列表的拖动单条数据的改变它的排序顺序;


NavigationView {
    // 列表
    List {
        ForEach(messagesItems) { Message in
            HStack {
                Image(Message.image)
                    .resizable()
                    .frame(width: 40, height: 40)
                    .cornerRadius(5)
                Text(Message.name)
                    .padding()
            }
        }
        .onDelete(perform: deleteRow)
        .onMove(perform: moveItem)
    }.navigationBarItems(trailing: EditButton())
}
复制代码


perform中引用的是排序的方法,我们定义一个删除方法为moveItem,具体实现方法如下:


// 拖动排序方法
func moveItem(from source: IndexSet, to destination: Int) {
    messagesItems.move(fromOffsets: source, toOffset: destination)
}
复制代码


同样,我们接收单一的 IndexSet类型的参数,它是用来定位要排序的列的位置。

然后它的排序数值为Int类型,简单来说,初始的排序是0、1、2、3、4,假设我们把3拖动到0,那么系统将自动更新重新排列顺序。

从而实现排序的效果。

image.png

快来试试吧!

第二部分:ContextMenu上下文菜单


ContentMenu上下文菜单是iOS13引用的一个新功能,效果为长按列表时,弹出一个悬浮窗口,用于快捷操作。

iPhone当中存在大量这样的交互,示例:长按系统设置,打开快捷操作弹窗。


image.png

实现ContentMenu上下文菜单的方法也很简单,使用.contentMenu修饰符,在修饰符里面构建需要展示或者操作的内容。

这里我们尝试做一个长按删除的操作,长按列表的一个项目,弹出上下文ContentMenu菜单,里面是一个删除按钮,点击按钮,删除指定行的数据。


.contextMenu {
    Button(action: {
        // 点击删除
    }) {
        HStack {
            Text("删除")
            Image(systemName: "trash")
        }
    }
}


image.png


然后,我们实现下删除的操作,会有些复杂,请耐心查阅。

它不像我们使用ForEach使用的.onDelete删除修饰符的方法不一样,ContentMenu上下文菜单没有索引定位到特定的数据项,也就不知道我们点击选中的是哪一条数据。

这就需要我们自己定位到Messages数组里面的Messageid,这样我们就可以通过id定位到是数组中的哪一条数据了。

方法如下:


//删除的方法
func delete(item Message: Message) {
    if let index = self.messagesItems.firstIndex(where: { $0.id == Message.id }) {
        self.messagesItems.remove(at: index)
    }
}


我们定义了一个delete的函数方法,接收了一个Message对象,并在Messages数组中搜索Message对象的索引。

为了找到Message对象的索引,我们调用firstIndex函数循环遍历数组,并将给定Messagesid与数组中的id进行比较。

如果有id一样,则firstIndex函数返回Messages数组的索引。

这样我们就知道长按的是哪一条数据了!

接下来,我们就可以通过调用remove(at:XXXX)修饰符从Messages数组中删除对应的数据项。

self.delete(item: Message)


image.png

我们运行一下模拟器,长按,系统会给出ContentMenu上下文菜单,它是一个删除按钮,我们点击删除按钮,该行数据就被删除了。

完整代码如下:

import SwiftUI
struct Message: Identifiable {
    var id = UUID()
    var name: String
    var image: String
}
// 定义数组,存放数据
var Messages = [
    Message(name: "这是微信", image: "weixin"),
    Message(name: "这是QQ", image: "qq"),
    Message(name: "这是微博", image: "weibo"),
    Message(name: "这是手机", image: "phone"),
    Message(name: "这是邮箱", image: "mail"),
]
struct ContentView: View {
    // 定义数组
    @State var messagesItems = Messages
    var body: some View {
        NavigationView {
            // 列表
            List {
                ForEach(messagesItems) { Message in
                    HStack {
                        Image(Message.image)
                            .resizable()
                            .frame(width: 40, height: 40)
                            .cornerRadius(5)
                        Text(Message.name)
                            .padding()
                    }
                    .contextMenu {
                        Button(action: {
                            // 点击删除
                            self.delete(item: Message)
                        }) {
                            HStack {
                                Text("删除")
                                Image(systemName: "trash")
                            }
                        }
                    }
                }
                .onDelete(perform: deleteRow)
                .onMove(perform: moveItem)
            }.navigationBarItems(trailing: EditButton())
        }
    }
    //删除的方法
    func delete(item Message: Message) {
        if let index = self.messagesItems.firstIndex(where: { $0.id == Message.id }) {
            self.messagesItems.remove(at: index)
        }
    }
    // 滑动删除方法
    func deleteRow(at offsets: IndexSet) {
        messagesItems.remove(atOffsets: offsets)
    }
    // 拖动排序方法
    func moveItem(from source: IndexSet, to destination: Int) {
        messagesItems.move(fromOffsets: source, toOffset: destination)
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

image.png

第三部分:ActionSheets弹窗的使用


弹窗当中,我们在之前的章节学习了ModelView弹窗,这里我们再拓展一种弹窗模式,叫做.actionSheet

它算是.sheet的另一种形式,常用在用户敏感操作的二次确认,但又不像Alerts警告弹窗那么严肃,属于一般强调。

image.png

.actionSheetAlerts警告弹窗的实现方式大体相同。


.actionSheet(isPresented:$showActionSheet) {
    //ActionSheet结构体
}


我们还是使用isPresented触发,我们定义一个变量showActionSheet的状态,初始状态是false


@State var  showActionSheet = false
复制代码


接下来,我们实现下ActionSheet结构体:


// ActionSheet弹窗
.actionSheet(isPresented: self.$showActionSheet) {
    ActionSheet(
        title: Text("你确定要删除此项吗?"),
        message: nil,
        buttons: [
            .destructive(Text("删除"), action: {
                //点击删除
                }),
            .cancel(Text("取消")
                ])
    }


image.png

下面,我们做一个“有趣”的操作。

我们承接上一部分的内容,长按列表,弹出一个ContentMenu上下文菜单,里面是一个删除按钮,点击删除按钮,打开ActionSheet弹窗,里面又是一个删除按钮,点击ActionSheet弹窗内的删除按钮,删除列表的数据项。

image.png

我们就实现了基于ActionSheet弹窗的删除操作啦!

完整代码如下:


import SwiftUI
struct Message: Identifiable {
    var id = UUID()
    var name: String
    var image: String
}
// 定义数组,存放数据
var Messages = [
    Message(name: "这是微信", image: "weixin"),
    Message(name: "这是QQ", image: "qq"),
    Message(name: "这是微博", image: "weibo"),
    Message(name: "这是手机", image: "phone"),
    Message(name: "这是邮箱", image: "mail"),
]
struct ContentView: View {
    // 定义数组
    @State var messagesItems = Messages
    @State var showActionSheet = false
    var body: some View {
        NavigationView {
            // 列表
            List {
                ForEach(messagesItems) { Message in
                    HStack {
                        Image(Message.image)
                            .resizable()
                            .frame(width: 40, height: 40)
                            .cornerRadius(5)
                        Text(Message.name)
                            .padding()
                    }
                    // 上下文菜单
                    .contextMenu {
                        Button(action: {
                            // 点击打开ActionSheet弹窗
                            self.showActionSheet.toggle()
                        }) {
                            HStack {
                                Text("删除")
                                Image(systemName: "trash")
                            }
                        }
                    }
                    // ActionSheet弹窗
                    .actionSheet(isPresented: self.$showActionSheet) {
                        ActionSheet(
                            title: Text("你确定要删除此项吗?"),
                            message: nil,
                            buttons: [
                                .destructive(Text("删除"), action: {
                                    //点击删除
                                    self.delete(item: Message)
                                }),
                                .cancel(Text("取消"))
                            ])
                    }
                }
                .onDelete(perform: deleteRow)
                .onMove(perform: moveItem)
            }.navigationBarItems(trailing: EditButton())
        }
    }
    // 删除的方法
    func delete(item Message: Message) {
        if let index = messagesItems.firstIndex(where: { $0.id == Message.id }) {
            messagesItems.remove(at: index)
        }
    }
    // 滑动删除方法
    func deleteRow(at offsets: IndexSet) {
        messagesItems.remove(atOffsets: offsets)
    }
    // 拖动排序方法
    func moveItem(from source: IndexSet, to destination: Int) {
        messagesItems.move(fromOffsets: source, toOffset: destination)
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

快来动手试试吧!



相关文章
|
4天前
|
索引 Python
List(列表)
List(列表)。
11 4
|
1月前
|
测试技术 开发者 Python
在 Python 中创建列表时,应该写 `[]` 还是 `list()`?
在 Python 中,创建列表有两种方法:使用方括号 `[]` 和调用 `list()` 函数。虽然两者都能创建空列表,但 `[]` 更简洁、高效。性能测试显示,`[]` 的创建速度比 `list()` 快约一倍。此外,`list()` 可以接受一个可迭代对象作为参数并将其转换为列表,而 `[]` 则需要逐一列举元素。综上,`[]` 适合创建空列表,`list()` 适合转换可迭代对象。
在 Python 中创建列表时,应该写 `[]` 还是 `list()`?
|
17天前
|
JavaScript 数据管理 虚拟化
ArkTS List组件基础:掌握列表渲染与动态数据管理
在HarmonyOS应用开发中,ArkTS的List组件是构建动态列表视图的核心。本文深入探讨了List组件的基础,包括数据展示、性能优化和用户交互,以及如何在实际开发中应用这些知识,提升开发效率和应用性能。通过定义数据源、渲染列表项和动态数据管理,结合虚拟化列表和条件渲染等技术,帮助开发者构建高效、响应式的用户界面。
152 2
|
26天前
|
NoSQL 关系型数据库 MySQL
Redis 列表(List)
10月更文挑战第16天
16 2
|
1月前
|
JavaScript
DOM 节点列表长度(Node List Length)
DOM 节点列表长度(Node List Length)
|
5月前
|
安全 Java
java线程之List集合并发安全问题及解决方案
java线程之List集合并发安全问题及解决方案
891 1
|
4月前
|
Java API Apache
怎么在在 Java 中对List进行分区
本文介绍了如何将列表拆分为给定大小的子列表。尽管标准Java集合API未直接支持此功能,但Guava和Apache Commons Collections提供了相关API。
|
4月前
|
运维 关系型数据库 Java
PolarDB产品使用问题之使用List或Range分区表时,Java代码是否需要进行改动
PolarDB产品使用合集涵盖了从创建与管理、数据管理、性能优化与诊断、安全与合规到生态与集成、运维与支持等全方位的功能和服务,旨在帮助企业轻松构建高可用、高性能且易于管理的数据库环境,满足不同业务场景的需求。用户可以通过阿里云控制台、API、SDK等方式便捷地使用这些功能,实现数据库的高效运维与持续优化。
|
4月前
|
存储 安全 Java
详解Java中集合的List接口实现的ArrayList方法 | Set接口实现的HashSet方法
详解Java中集合的List接口实现的ArrayList方法 | Set接口实现的HashSet方法
|
5月前
|
Java API
使用 Java 来实现两个 List 的差集操作
使用 Java 来实现两个 List 的差集操作
136 3

热门文章

最新文章