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

4,522 阅读6分钟

前提回顾

在上两个章节中,我们完成了个人主页以及添加身份卡页面的设计,其中学习如何数据模型、列表创建卡片展示视图,以及打开页面和关闭页面的交互动作,在本章节中,我们来学习搭建添加身份卡页面的相关内容。

界面分析:页面元素及其优先级

添加身份卡页面,需要根据个人主页身份卡的内容来构建,身份卡对应展示的信息,就是我们需要在新增时创建的信息,如下图所示:

对于信息的优先级,主要信息为平台称号,其次信息是平台图标和平台名称,再次隐藏信息是链接地址。

确定页面元素的优先级需要遵循用户惯性逻辑以及应用想给用户呈现的关键点,在身份卡中对于用户而言最核心的信息是在平台所取得的称号,而平台称号也是在个人主页身份卡中用户视线首要关注的内容。

其次的平台信息部分,由平台图标和平台名称组成,这是由于这两块信息具有相关性,平台名称对应着平台图标,因此在产品设计上可以耦合在一起。

最后是链接地址部分,后续我们希望关联对应平台地址后,能点击身份卡片在应用内打开浏览器并访问链接地址,而链接地址在整个身份卡片中是不展示其内容的,因此信息的优先级可以放置至最后。

变量声明:身份卡必要参数准备

分析完界面元素后,我们在创建页面之前先确定好需要使用到的变量,如下代码所示:

@State var platformIcon: String = "icon_juejin"
@State var title: String = "移动端签约作者"
@State var platformName: String = "稀土掘金技术社区"
@State var indexURL: String = "https://juejin.cn/user/3897092103223517"

上述代码中,我们都使用@State进行变量的声明,@State可以存储声明的变量及其变量的值。并且为了呈现效果,我们给声明的参数都赋予了初始值。

界面设计:平台称号输入框

声明好需要的变量后,我们来搭建平台称号输入框内容,我们可以和搭建closeBtn关闭按钮视图一样,单独搭建平台输入框视图,再将该视图内容赋予NewView主要的视图中,如下代码所示:

// 头衔名称输入框
func titleInputView() -> some View {
    TextField("请输入头衔", text: $title)
        .padding()
        .background(Color(.systemGray6))
        .cornerRadius(8)
        .padding(.horizontal)
}

上述代码中,我们创建了一个平台称号输入框视图titleInputView,主体内容使用TextField输入框控件,输入框提示文字为“请输入头衔”,输入内容绑定声明的变量title。

在样式方面,为了突出输入框视图内容,使用padding修饰符“撑开”输入框外层区域,将外层区域填充背景色为systemGray6,并设置圆角度数为8,最后设置水平位置两边留白,便实现了上图效果。

界面设计:自定义平台选择器

接下来我们来设计“选择平台”的操作逻辑。可以简单设想下,用户在“添加身份卡”页面输入完平台称号后,下一步是选择该称号所在的社区或者平台。

最直观的展现方式是将平台都排列出来让用户进行选择,而由于展示空间有限,我们可以只展示平台的图标。当用户看到平台图标时,通过点击选择以确定平台,而且平台选择单张身份卡只能选择一个平台。

有了基础的想法后,我们来实现这个交互逻辑。

首先需要创建一个平台展示的数据集合,我们声明一个数据用于存放数据,如下代码所示:

private let platforms = [
        ("稀土掘金技术社区", "icon_juejin"),
        ("CSDN博客", "icon_csdn"),
        ("阿里云社区", "icon_aliyun"),
        ("华为云社区", "icon_huaweiyun"),
    ]

上述代码中,我们声明了一个常量数组platforms,在platforms数组内有两个内容,前一个参数是平台名称,后一个参数是平台图标。

声明好数据后,我们来创建平台选择样式部分,这次需要用到的SwiftUI控件是LazyVGrid垂直网格布局容器,LazyVGrid垂直网格布局容器类似VStack垂直布局容器,不同的是VStack垂直布局容器只能将内部的元素垂直排布,而LazyVGrid垂直网格视图容器可以保持垂直布局的情况下,将内部的元素分为几列,如下图所示:

LazyVGrid垂直网格布局容器使用需要提前声明网格的列数,如下代码所示:

 private var gridItemLayout = [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]

上述代码中,我们声明了布局容器的GridItem列项为4列自适应调整的列项,将参数赋予变量值gridItemLayout。

紧接着我们来使用LazyVGrid垂直网格布局容器,LazyVGrid垂直网格布局容器的使用方法和List列表的使用方法类似,通过与ForEach循环遍历配合使用,我们依旧使用单独构建视图的方法创建它,如下代码所示:

// 平台选择器
func platformPicker() -> some View {
    LazyVGrid(columns: gridItemLayout, spacing: 10) {
        ForEach(0 ..< platforms.count, id: .self) { item in
            Image(platforms[item].1)
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: 48, height: 48)
                .clipShape(Circle())
        }
    }
}

上述代码中,我们创建了“选择平台”视图platformPicker,在platformPicker视图中使用LazyVGrid垂直网格布局容器搭建网格视图,LazyVGrid的columns列项为上面声明的gridItemLayout,网格之间元素的spacing间距为10。

和List列表的方法一样,我们使用ForEach循环遍历platforms数组的数据,并使用了Image图片控件来展示platforms数组中的数据。由于platforms数组使用两个数据,因此我们使用下标法指向数组的第二个数据(计算机数据从0开始计数)。

在这里还需要考虑一个问题,当我们平台图标太多时,可能会占据整个App的页面,我们可以在LazyVGrid垂直网格布局容器最外层增加一个ScrollView滚动视图容器,并设置滚动视图的高度,如此LazyVGrid垂直网格布局容器不管有多少元素,都只会在ScrollView滚动视图容器中展示,如下代码所示:

ScrollView {
            LazyVGrid(columns: gridItemLayout, spacing: 10) {
                ForEach(0 ..< platforms.count, id: .self) { item in
                    Image(platforms[item].1)
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(width: 48, height: 48)
                        .clipShape(Circle())
                }
            }
        }
        .padding()
        .background(Color(.systemGray6))
        .cornerRadius(8)
        .padding(.horizontal)
        .frame(maxHeight: 180)

很好,完成了“平台选择器”的样式之后,我们来实现交互逻辑部分,首先我们要看到用户点击选择的平台是哪一个,并将其凸显出来。

由于ForEach是遍历数据给到了item,因此当用户点击了指定的item时,该item对应的样式与其他未选中的样式分隔开。为了实现这个效果,我们首先要声明一个变量,知道选中的是哪一个item,如下代码所示:

 @State var selectedItem = 0

当选中时,即item的值等于selectedItem时,我们设置图片加一层边框,如果不是,则保持原样,如下代码所示:

if item == selectedItem {
    Image(platforms[item].1)
        .resizable()
        .aspectRatio(contentMode: .fit)
        .frame(width: 48, height: 48)
        .clipShape(Circle())
        .overlay(
            Circle()
                .stroke(Color.green, lineWidth: 4)
        )
} else {
    Image(platforms[item].1)
        .resizable()
        .aspectRatio(contentMode: .fit)
        .frame(width: 48, height: 48)
        .clipShape(Circle())
		.onTapGesture {
        	selectedItem = item
	}
}

上述代码中,我们通过判断当前的item是否等于selectedItem来构建不同的图片样式,如果一致,则Image图片使用overlay修饰符覆盖一层绿色的圆形边框。如果不一致,则保持原来的样式,在原有的样式上增加onTapGesture点击事件,当点击的时候,让selectedItem选中的item等于点击的item。

可以在模拟器上试试效果,如下图所示:

另外,当点击平台图标时,我们需要给声明的变量进行赋值,如下代码所示:

platformIcon = platforms[item].1
platformName = platforms[item].0

如此,便完成了“自定义平台选择器”的所有内容。

界面设计:链接地址多行文本框

接下来我们来完成链接地址的交互设计,TextEditor多行文本框控件和TextField输入框控件的使用方法类似,不同的是至今TextEditor多行文本框控件都没有提供placeholder提示文字的设置方法,只能由开发者自行实现。

不过没关系,实现多行文本框的方法及其提示文字的方法也很简单,如下代码所示:

// 链接地址
func indexURLView() -> some View {
    ZStack(alignment: .topLeading) {
        TextEditor(text: $indexURL)
            .font(.system(size: 17))
            .padding(15)

        if indexURL.isEmpty {
            Text("请输入主页链接")
                .foregroundColor(Color(UIColor.placeholderText))
                .padding(20)
        }
    }
    .background(Color(.systemGray6))
    .cornerRadius(8)
    .padding()
    .frame(maxHeight: 240)
}

上述代码中,我们创建了一个多行文本框视图indexURLView作为链接地址的输入框,在indexURLView视图中使用ZStack堆栈视图建立层级关系,当TextEditor输入框的绑定的内容indexURL为空时,则展示一个Text文本作为提示文字。

如此就实现了TextEditor多行文本框及其提示文字的交互。

交互设计:添加身份卡按钮及其方法

完成添加身份卡的页面元素后,我们来实现将输入的内容添加到“个人主页”的方法。

首先我们先创建基本的按钮,如下代码所示:

// 添加按钮
func addBtn() -> some View {
    Button(action: {
    }) {
        Text("添加")
            .font(.system(size: 17))
            .foregroundColor(.white)
            .bold()
            .padding()
            .frame(maxWidth: .infinity)
            .background(Color.green)
            .cornerRadius(8)
            .padding(.horizontal)
    }
}

回到NewView的Body部分,所有元素我们均以搭建完成,所有视图的排布我们可以使用VStack垂直布局容器进行排布,如下代码所示:

VStack(spacing: 15) {
    titleInputView()

    platformPicker()

    indexURLView()

    addBtn()

    Spacer()
}

样式完成之后,我们来实现添加身份卡到个人主页的交互。

我们来到Model.swift数据模型文件,原来我们声明了models数组变量,我们需要将其剪切到ContentView文件中来,作为ContentView中的一部份,用于后面的数据双向绑定使用,如下图所示:

然后回到NewView文件中,声明用于双向绑定的数据模型,如下代码所示:

@Binding var models: [Model]

由于使用了@Binding进行双向绑定参数,因此NewView中不能存在private声明的变量,因此我们需要删除原来platforms数组和gridItemLayout网格布局的private,并且给NewView赋予默认值,如下代码所示:

NewView(models: .constant([]))

还需要回到ContentView文件,对使用NewView视图中声明的双向绑定的models进行绑定,如下图所示:

最后我们还需要回到NewView视图中,实现添加身份卡的方法,如下代码所示:

let newItem = Model(platformIcon: platformIcon, title: title, platformName: platformName, indexURL: indexURL)
models.append(newItem)

上述代码中,我们在点击addBtn添加按钮时,实现添加身份卡的方法,首先先声明一个常量newItem存放符合Model数据模型的数据,并将数据模型的对应的值填充为我们在NewView声明的变量(由于采用一样的命名,因此就有了上面的效果),然后调用append的方法将符合数据模型的数据添加到models数组中。

此时我们就可以删除NewView中声明的变量的默认值,让用户自定定义填充,如下代码所示:

@State var title: String = ""
@State var indexURL: String = ""

同时我们也可以删除在ContentView中models数组的默认值,让我们自己操作创建数据,如下代码所示:

@State var models:[Model] = []

整体预览

接下来,让我们在模拟器中操作电子名片App的全路径,如下图所示:

iShot_2022-11-13_17.19.38.gif

本章小结

在本章中,我们学习了如何搭建“添加身份卡”页面,包含输入框基础控件的使用,多行文本框基础控件的使用。

本章的核心内容是自定义选择器的创建,我们使用ScrollView滚动视图容器、LazyVGrid垂直网格容器搭建了选择器,并实现了单选的功能。最后我们通过双向绑定视图数组数据的方式,实现了添加身份卡到个人主页的方法。

在本章的例子中,其实可以运用到其他App案例中,最常见的示例:todo待办事项和note笔记。

下面的章节中,我们来继续学习点击身份卡片在App打开浏览器并访问链接地址的功能及其他相关操作,请保持期待吧~

版权声明

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