实战教程·元宇宙来了,准备好你的电子名片了吗?(二)

语言: CN / TW / HK

theme: smartblue

前提回顾

在上一章中,我们创建了CardView构件,使用了构件编程方式搭建视图。使用构件的好处是当我们要整体修改构件内容时,就可以只修改构件的结构体,而无需每个卡片视图都修改。

示例,在上一章中我们设置了平台图标图片,但由于平台图标样式的不统一性,看起来有些不协调。这时候,我们可以给CardView构件中的Image控件设置为圆形,让其更加美观些,如下图所示:

对于搭建好的构件,只需要修改CardView构件本身,就可以在引用此构件的所有地方统一修改其样式。

是不是很方便?接下来让我们继续完成下面的内容。

结构化编程:搭建数据模型

使用构件创建身份卡片视图后,我们发现如果每次要新增一个卡片,就需要在卡片视图中需要复制CardView构件并赋值。当我们需要创建很多身份卡片时,就会需要很多一样的代码,这不够优雅。

有没有办法像搭建CardView构件一样,只搭建一个结构然后填充数据,让结构自动“遍历”出内容?

SwiftUI声明式语言中,可以使用List列表组件循环遍历内容,在此之前我们需要定义好数据模型。我们创建一个新的Swift文件,命名为Model,如下图所示:

Model文件作为我们搭建数据模型的文件,定义数据需要声明的变量。首先我们引用SwiftUI,如下代码所示:

import SwiftUI

接下来搭建数据模型,如下代码所示:

struct Model: Identifiable { var id = UUID() var platformIcon: String var title: String var platformName: String var indexURL: String }

数据模型可以使用Class类或者Struct结构体声明,这里我们使用Struct结构体声明了一个数据模型Model,遵循Identifiable协议。“Identifiable可识别”协议可以通过id区分不同的数据,当我们创建的数据中有两条一模一样的数据时,由于“Identifiable可识别”协议,系统将赋予不同的id,实现呈现两条数据内容的效果。

使用Identifiable协议除了声明需要的参数变量外,还需要在数据模型中声明一个id,并复制为UUID。该数据模型中的需要使用的变量参数,我们可以直接使用之前在CardView身份卡片构件声明的参数。

声明好参数后,我们为展示数据,可以创建一个数据数组作为示例数据,如下代码所示:

``` // MARK: 示例数据

var models = [ Model(platformIcon: "icon_juejin", title: "移动端签约作者", platformName: "稀土掘金技术社区", indexURL: "http://juejin.cn/user/3897092103223517"), Model(platformIcon: "icon_csdn", title: "iOS创作者", platformName: "CSDN博客", indexURL: ""), Model(platformIcon: "icon_aliyun", title: "专家博主", platformName: "阿里云社区", indexURL: ""), Model(platformIcon: "icon_huaweiyun", title: "专家博主", platformName: "华为云社区", indexURL: ""), ] ```

如此便完成了数据模型的搭建。

卡片视图:List列表和ForEach循环

我们回到ContentView视图中,删除原有的身份卡片视图的代码。卡片视图我们可以看作一个列表,每张身份卡片就是一行行呈现的数据,列表的数据源则来源于上述创建好的数组models,如下图所示:

List { ForEach(models) { item in CardView(platformIcon: item.platformIcon, title: item.title, platformName: item.platformName, indexURL: item.indexURL) } .listRowBackground(Color.clear) .listRowSeparator(.hidden) }

上述代码中,我们使用List列表和ForEach循环结构,遍历models数组中定义好的数据内容,并在调用CardView身份卡片构件的时候赋值为循环后item的一条条数据。

由于演示的iPhonex模拟器,因此为了呈现更好的列表样式效果,使用listRowBackground列表项背景修饰符,将列表背景颜色修饰为透明。并使用listRowSeparator列表分割线修饰符,隐藏List自带的分割线。完成这些操作后后,就可以完成展示身份卡片构件的样式内容。

在模拟器中预览的效果中,身份卡片左右边距太宽了,我们可以直接修改CardView构件的代码,去掉最后的padding边距,如下图所示:

导航菜单:标题和新建卡片入口

卡片视图完善之后,我们来搭建顶部导航菜单。SwiftUI提供了NavigationView导航视图控件方便我们快速搭建应用的顶部导航菜单。从视图层级来看,顶部导航菜单是“包裹”整个页面视图的,因此NavigationView导航视图控件需要在ContentView的body视图最外层,如下代码所示:

NavigationStack { //视图内容 .navigationBarTitle("文如秋雨",displayMode: .inline) }

在iOS16版本,使用NavigationStack导航视图控件搭建顶部导航菜单,iOS16版本以下的机型使用NavigationView导航。

搭建好顶部导航栏后,我们创建一个新的按钮,作为创建身份卡片的入口。可以使用navigationBarItems导航栏按钮修饰符创建导航菜单上的按钮入口,不过在此之前,我们需要搭建好按钮的视图,再将按钮赋予navigationBarItems导航栏按钮修饰符。如下代码所示:

``` func addBtn() -> some View { Button(action: {

    }) {
        Image(systemName: "plus.circle.fill")
            .font(.system(size: 17))
            .foregroundColor(.blue)
    }
}

```

上述代码中,我们完成了一个按钮视图addBtn。一般在编程过程中,若视图中除样式代码外还有交互操作,通常会将其抽离出来作为一个单独的视图进行构建,然后再在修饰符或者其他视图中使用,这是SwiftUI开发中经常会遇到的事情。

完成按钮视图的构建后,我们将按钮视图加到顶部导航菜单中,如下代码所示:

.navigationBarItems(trailing: addBtn())

模态弹窗:打开一个新页面

个人主页部分基本已经完成了,下面我们来完成点击顶部导航菜单的“添加”按钮,打开“新建身份卡”页面。

首先我们先创建一个新的SwiftUI文件,命名为NewView,如下图所示:

回到ContentView视图中,使用Sheet模态弹窗控件,创建打开弹窗的样式,如下代码所示:

.sheet(isPresented:Is Presented) { Content }

上述代码中,我们使用sheet修饰符控件搭建打开模态弹窗效果,但需要使用sheet修饰符控件,需要提前声明好触发打开动作的变量,如下代码所示:

@State var showNewView:Bool = false

并将声明好的触发变量绑定到sheet修饰符控件中,以及设置Content跳转的页面目标为NewView。如下代码所示:

.sheet(isPresented: $showNewView) { NewView() }

最后在点击addBtn按钮视图时,切换showNewView打开视图的变量状态,便可实现点击“添加按钮”打开模态弹窗,如下代码所示:

self.showNewView.toggle()

关闭弹窗的方法也很类似,我们来到NewView视图,给NewView视图的内容添加顶部导航菜单,并也创建一个关闭弹窗的按钮,如下代码所示:

``` struct NewView: View { var body: some View { NavigationStack { Text("Hello, World!") .navigationBarTitle("添加身份卡", displayMode: .inline) .navigationBarItems(trailing: closeBtn()) } }

func closeBtn() -> some View {
    Button(action: {
    }) {
        Image(systemName: "xmark.circle.fill")
            .font(.system(size: 17))
            .foregroundColor(.gray)
    }
}

} ```

上述代码中,我们使用的方法和个人主页的内容基本一致,使用NavigationStack导航菜单控件创建导航菜单,并使用navigationBarTitle顶部导航标题控件创建标题。单独创建关闭按钮视图closeBtn,并将它赋予导航菜单中的navigationBarItems导航菜单栏目修饰符,以创建导航菜单上的按钮。

关闭弹窗:双向绑定或全局变量

实现打开模态弹窗后,我们来实现关闭模态弹窗,下面我们来介绍两种关闭模态弹窗的方法:双向绑定、全局变量。

由于我们在ContentView个人主页声明了一个变量showNewView来打开模态弹窗,当点击addBtn按钮时更新其状态。因此我们也可以在NewView页面中也声明一个双向绑定的变量showNewView,并在点击closeBtn时更新其状态,实现关闭弹窗的交互。

首先先声明一个双向绑定的变量showNewView如下图所示:

@Binding var showNewView:Bool

然后在点击closeBtn关闭按钮时更新其状态,如下代码所示:

self.showNewView.toggle()

由于使用@Binding声明变量,因此在NewView视图中声明的变量缺少赋值,将可能导致无法预览,因此我们在预览时需要给NewView视图赋予默认值,如下代码所示:

NewView(showNewView: .constant(false))

建立双向绑定的视图,在所有页面也都需要进行@Binding声明变量的绑定,因此我们回到ContentView视图中,绑定showNewView变量,如下代码所示:

NewView(showNewView: $showNewView)

完成后,可以在模拟器中操作打开模态弹窗和关闭模态弹窗的交互。

另一种关闭模态弹窗的方法是声明一个全局变量,实现关闭弹窗的效果,如下代码所示:

``` //声明环境变量 @Environment(.presentationMode) var presentationMode

//调用 self.presentationMode.wrappedValue.dismiss() ```

上述代码中,我们声明了一个全局变量presentationMode,在点击closeBtn关闭按钮的时候调用presentationMode全局变量的wrappedValue.dismiss方法实现关闭弹窗效果。

两种方式在实际开发过程中均有使用,除模态弹窗外,在页面之间使用入栈出栈的方式进行切换页面也经常会用到。

在模态弹窗交互逻辑中更经常使用全局变量关闭弹窗的方式,无需进行双向绑定已经给视图添加默认值,也就无需示例在其他页面缺少绑定参数的麻烦。

本章小结

在本章中,我们主要实现了使用数据模型和List列表来构建身份卡片视图的内容,在减少代码量的同时,其实也是为了之后对身份卡片进行新增、编辑、删除做前期准备。

在SwiftUI中,复杂的具有相似结构的界面设计,通常会使用List列表和ForEach来实现,常见的类似Todo待办事项App中的卡片列表、信息流App的信息列表、短视频平台的视频封面卡片视图等等。

另外我们还学习了使用NavigationStack导航菜单控件(iOS16版本及以上机型使用,低于此版本机型使用NavigationView,Apple经常会在新控件完善后弃用旧控件,这点有点烦)及其标题、导航按钮修饰符的使用,并进一步了解了使用sheet模态弹窗修饰符打开弹窗。关闭弹窗部分,我们介绍了两种方式实现关闭弹窗,并简单介绍了其使用特点。

在下面的章节中,我们将继续完成“添加身份卡”页面的相关操作,请保持期待吧~

版权声明

本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!

「其他文章」