使用 SwiftUI 和 GraphQL 克隆 Instagram – ProfileView
今天,在使用 GraphQL 克隆 Instagram 应用程序的第 3 部分中,我们将深入了解 SwiftUI,构建我们的 “个人资料 “视图。
我们将学习在 SwiftUI 中重用结构体,并讨论一些控件:VStack、HStack、GeometryReader、Text、Button、Spacer、Image、Divider 等。
在之前的文章中,我们学习了如何在 Instagram 克隆应用程序中使用相应的用户界面注册 用户和登录用户。今天,我们将让它变得更漂亮。
系好安全带,让我们开始吧!
为了更好地学习,请下载带有源代码的iOS Instagram 克隆项目。
Contents
想快速上手?
从我们的集线器克隆这个应用程序,就能轻松上手了!
我们要构建的内容
我们将构建以下视图:
SwiftUI 可以让您轻松构建复杂的界面,因为您可以非常简单直接地重用大量组件。
唯一的问题是,我们必须学会如何创建这些可重用的组件。 一个视图实际上是多个视图的组合,它们集成到一个最终结果中。
组件简化列表如下:
其中
- Vstack
- Hstack
- 实际控件(按钮、分隔线等)
什么是 HStack、Vstack、Zstack?
SwiftUI 通过使用 VStack(垂直堆栈)在垂直方向对齐视图,使用 HStack(水平堆栈)在水平方向对齐视图,以及使用 Zstack(Z 表示 z-index)叠加视图来构建用户界面。
每次需要将一个视图置于另一个视图之下时,都应使用 VStack。
每次需要将一个视图放在另一个视图旁边时,应使用 HStack。
每当需要一个视图覆盖另一个视图时,就应该使用 ZStack。
由于我们的主视图将由许多视图组成,一个视图水平放置在另一个视图之下,因此我们将把所有内容都包含在主 VStack 中,然后从这里开始,但为了占据整个屏幕,我们还需要使用另一个视图:GeometryReader。
通过它,我们可以根据(GeometryReader 的)尺寸和坐标来渲染所有内容。
可重用性
可重用性是 SwiftUI 的一大亮点,因此我们可以创建复杂的控件,并在任何需要的地方重用它们。
在本应用程序中,我们将在不同视图中重复使用的组件主要有两个:时间轴和底栏:
因此,我们将分别创建这两个 UI 代码,使其更有条理。所有其他视图专用的用户界面代码都可以保留在该视图的代码文件中。
那么,让我们开始吧…
创建个人资料视图
添加一个新的 SwiftUI 文件,并将其命名为 ProfileView.swift。
由于我们将排列组件以填满屏幕,因此让我们开始添加一个 GeometryReader,这样我们就能确保其中的所有控件都将使用其大小和坐标:
struct ProfileView:View { var body: some View { GeometryReader { geometry in } } }
现在,我们的视图将内置到一个主 VStack 中,该 VStack 将包含我们的所有控件,因此让我们也添加它
struct ProfileView:View { var body: some View { GeometryReader { geometry in VStack{ } } } }
我们的第一 “行 “控件将是最上面的一个:
看似简单的一行控件实际上是多个控件:
- 用户名 “文本
- 旁边向下的小箭头
- 填充小箭头和下一个控件之间空隙的间隔器
- 右侧的汉堡按钮
我们将在按钮上使用图标,因此一定要找到一些免费图标。Freepik 就是一个不错的选择。
现在,让我们添加 HStack:
struct ProfileView:View { var body: some View { GeometryReader { geometry in VStack{ HStack{ } } } } }
我们的第一个文本就在其中:
struct ProfileView:View { var body: some View { GeometryReader { geometry in VStack{ HStack{ Text("username") } } } } }
到目前为止,你可能已经看到了这里:
好吧这是一个开始。
让我们通过添加一个
(对齐方式:.leading)
代码将其对齐到左侧:
struct ProfileView:View { var body: some View { GeometryReader { geometry in VStack(alignment: .leading){ HStack{ Text("username") } } } } }
我们还可以添加一些文本属性,使其与我们的设计更加一致:
将其颜色设置为 AppDelegate 中定义的 lightBlueColor:
.foregroundColor(lightBlueColor)
更改字体重量:
.fontWeight(.semibold)
最后在前导侧添加一些 padding(空格):
.padding(.leading, 10)
现在您的代码应该是这样的
struct ProfileView:View { var body: some View { GeometryReader { geometry in VStack(alignment: .leading){ HStack{ 文本("用户名") .foregroundColor(lightBlueColor) .fontWeight(.semibold) .padding(.leading, 10) } } } } }
你的用户界面应该是这样的:
现在,让我们添加小箭头按钮。
正如您在本文第二部分中学到的,为我们的按钮添加向下箭头的图片,然后让我们将按钮添加到 SwiftUI 代码中:
Button(action: {}){ }
并添加图片:
按钮(操作:{}){ 图片("箭头向下") .resizable() .frame(width: 10, height: 10) }
为了保持一致,我们在顶部添加一些填充:
按钮(操作:{}){ 图像("箭头向下) .resizable() .frame(width: 10, height: 10) } .padding(.top, 5)
现在我们的完整代码应该是这样的
struct ProfileView:View { var body: some View { GeometryReader { geometry in VStack(alignment: .leading){ HStack{ 文本("用户名") .foregroundColor(lightBlueColor) .fontWeight(.semibold) .padding(.leading, 10) 按钮(操作:{}){ 图像("箭头向下) .resizable() .frame(width: 10, height: 10) } .padding(.top, 5) } } } } }
这就是我们的用户界面:
现在,让我们用同样的方法添加汉堡包按钮:
按钮(操作:{}){ 图片("菜单") .resizable() .frame(width: 20, height: 20) }.padding()
我们的完整代码
struct ProfileView:View { var body: some View { GeometryReader { geometry in VStack(alignment: .leading){ HStack{ 文本("用户名") .foregroundColor(lightBlueColor) .fontWeight(.semibold) .padding(.leading, 10) 按钮(操作:{}){ 图像("箭头向下) .resizable() .frame(width: 10, height: 10) } .padding(.top, 5) 按钮(操作:{}){ 图像("菜单) .resizable() .frame(width: 20, height: 20) }.padding() } } } } }
还有我们的视图:
如果我们能在这两个按钮之间放一个东西,占据它们之间的所有空间并对齐所有内容就好了
Spacer()
现在看起来不错!
我们的完整代码
struct ProfileView:View { var body: some View { GeometryReader { geometry in VStack(alignment: .leading){ HStack{ 文本("用户名") .foregroundColor(lightBlueColor) .fontWeight(.semibold) .padding(.leading, 10) 按钮(操作:{}){ 图像("箭头向下) .resizable() .frame(width: 10, height: 10) } .padding(.top, 5) 间隔() 按钮(操作:{}){ 图像("菜单) .resizable() .frame(width: 20, height: 20) }.padding() } } } } }
现在,我们只需固定 HStack 的高度,并在前导侧添加一些填充,就可以了:
.frame(height: 50) .padding(.leading, 10)
完整代码
struct ProfileView:View { var body: some View { GeometryReader { geometry in VStack(alignment: .leading){ HStack{ 文本("用户名") .foregroundColor(lightBlueColor) .fontWeight(.semibold) .padding(.leading, 10) 按钮(操作:{}){ 图像("箭头向下) .resizable() .frame(width: 10, height: 10) } .padding(.top, 5) 间隔() 按钮(操作:{}){ 图像("菜单) .resizable() .frame(width: 20, height: 20) }.padding() }.frame(height: 50) .padding(.leading, 10) } } } }
现在我们可以开始我们的
第二个 HStack
由于我们在主 VStack 中创建了一切,我们在第一个 HStack 之外添加的每个新控件都会自动归入主 VStack。
因此,是时候创建第二个 HStack 并构建屏幕的第二部分了:
这也将是一个 HStack,内部包含 4 个 VStack:一个用于图像和图像下方的文本,3 个用于数字和数字下方的文本。
你可能已经明白了这个概念,下面我将给出这个新 HStack 的完整代码:
HStack{ VStack{ Image("logo-social") .resizable() .frame(width: 90, height: 90) .clipShape(Circle()) .shadow(radius: 3) .overlay(Circle().stroke(Color.pink, lineWidth: 1)) 文本("您的姓名) .foregroundColor(lightBlueColor) .fontWeight(.semibold) }.padding(.leading, 10) VStack{ Text("10") .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) Text("Publications") .font(.system(size: 13)) .foregroundColor(lightBlueColor) }.padding(.leading, 30) VStack{ 文本("100) .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) 文本("关注者").font(.system(size: 13 .font(.system(size: 13)) .foregroundColor(lightBlueColor) }.padding() VStack{ 文本("1000) .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) Text("Following") .font(.system(size: 13)) .foregroundColor(lightBlueColor) } }.frame(height: 100) .padding(.leading, 10)
我们的完整代码如下
struct ProfileView:View { var body: some View { GeometryReader { geometry in VStack(alignment: .leading){ HStack{ 文本("用户名") .foregroundColor(lightBlueColor) .fontWeight(.semibold) .padding(.leading, 10) 按钮(操作:{}){ 图像("箭头向下) .resizable() .frame(width: 10, height: 10) } .padding(.top, 5) 间隔() 按钮(操作:{}){ 图像("菜单) .resizable() .frame(width: 20, height: 20) }.padding() }.frame(height: 50) .padding(.leading, 10) HStack{ VStack{ 图像("徽标-社交) .resizable() .frame(width: 90, height: 90) .clipShape(Circle()) .shadow(radius: 3) .overlay(Circle().stroke(Color.pink, lineWidth: 1)) 文本("您的姓名) .foregroundColor(lightBlueColor) .fontWeight(.semibold) }.padding(.leading, 10) VStack{ Text("10") .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) Text("Publications") .font(.system(size: 13)) .foregroundColor(lightBlueColor) }.padding(.leading, 30) VStack{ 文本("100) .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) 文本("关注者").font(.system(size: 13 .font(.system(size: 13)) .foregroundColor(lightBlueColor) }.padding() VStack{ 文本("1000) .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) Text("Following") .font(.system(size: 13)) .foregroundColor(lightBlueColor) } }.frame(height: 100) .padding(.leading, 10) } } } }
这样,我们的视图看起来就像这样了:
真漂亮
添加编辑个人资料按钮和分隔线
有人可能会认为,”编辑配置文件 “按钮和它下面的分割线应该放在一个 VStack 中:
但这其实没有必要,因为我们的整个视图都在主 VStack 中,所以我们只需将它们添加到代码中即可:
按钮(操作:{}){ 文本("编辑个人资料) .fontWeight(.bold) .foregroundColor(lightBlueColor) }.frame(width: 400) .padding() 分割线()
会是这样
struct ProfileView:View { var body: some View { GeometryReader { geometry in VStack(alignment: .leading){ HStack{ 文本("用户名") .foregroundColor(lightBlueColor) .fontWeight(.semibold) .padding(.leading, 10) 按钮(操作:{}){ 图像("箭头向下) .resizable() .frame(width: 10, height: 10) } .padding(.top, 5) 间隔() 按钮(操作:{}){ 图像("菜单) .resizable() .frame(width: 20, height: 20) }.padding() }.frame(height: 50) .padding(.leading, 10) HStack{ VStack{ 图像("徽标-社交) .resizable() .frame(width: 90, height: 90) .clipShape(Circle()) .shadow(radius: 3) .overlay(Circle().stroke(Color.pink, lineWidth: 1)) 文本("您的姓名) .foregroundColor(lightBlueColor) .fontWeight(.semibold) }.padding(.leading, 10) VStack{ Text("10") .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) Text("Publications") .font(.system(size: 13)) .foregroundColor(lightBlueColor) }.padding(.leading, 30) VStack{ 文本("100) .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) 文本("关注者").font(.system(size: 13 .font(.system(size: 13)) .foregroundColor(lightBlueColor) }.padding() VStack{ 文本("1000) .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) Text("Following") .font(.system(size: 13)) .foregroundColor(lightBlueColor) } }.frame(height: 100) .padding(.leading, 10) 按钮(操作:{}){ Text("Edit Profile") .fontWeight(.bold) .foregroundColor(lightBlueColor) }.frame(width: 400) .padding() 分割线() } } } }
以及我们的用户界面:
模拟时间轴
我们的时间轴视图将用于应用程序的其他部分,因此将其放在不同的文件中是合理的。
它也可以在我们的 ProfileView 中重复使用,但这样可以让我们在拆分代码时更有条理。
创建 TimelineView.swift 文件。
在 SwiftUI 中有许多不同的数据显示方式,但我为我的应用程序选择了这种方式:
- 我们的时间线视图是一个由LineViews组成的 VStack
- 每个LineView是由 3 个PreviewView组成的 HStack
- 每个预览视图中都有一个图像
首先,我要创建一个结构来保存数据。我将调用该结构预览,它将有 2 个参数:id(Int 类型),用于遍历;imageURL(String 类型),用于保存我将传递的图片的 URL:
结构预览 var id: Int let imageUrl:字符串 }
正如我所说,你可以选择另一种方式来显示数据,但我觉得这种方式非常容易理解,所以让我们先为预览视图添加结构体。
我们的结构体有一个 Preview 属性,我稍后会对其进行设置,但会使用 imageURL 属性来渲染图片:
struct PreviewView:View { let preview:预览 var body: some View { 图像(preview.imageUrl) .resizable() .frame(width: 136, height: 136) } }
完成这些后,我们就可以为 LineView 添加结构了,它将接收一个包含 3 个预览的数组,并显示在行中。
我将在将来修改该结构以反映真实数据,但现在这样就可以了:
struct LineView:View { let previewArray:[Preview] (预览数组 var body: some View { HStack(spacing: 2){ PreviewView(preview: 预览数组[0]) PreviewView(preview: previewArray[1]) PreviewView(preview: previewArray[2]) } } }
最后,我们可以创建一个预览对象数组,并对其进行循环:
let previews:[Preview] = [ 预览(id: 0, imageUrl: "1")、 预览(id: 1, imageUrl: "2")、 Preview(id: 2, imageUrl: "3")、 预览(id: 3, imageUrl: "4")、 预览(id: 4, imageUrl: "5")、 预览(id: 5, imageUrl: "6")、 预览(id: 6, imageUrl: "7")、 预览(id: 7, imageUrl: "8")、 预览(id: 8, imageUrl: "9")、 预览(id: 9, imageUrl: "10")、 预览(id: 10, imageUrl: "11")、 预览(id: 11, imageUrl: "12")、 预览(id: 12, imageUrl: "13") ]
这个数组有 13 个对象,我用 1 到 13 的名称引用了将要使用的图片。我还将这些图片保存在了我的 Assets 文件夹中,但同样,我也会在将来进行更改:
现在一切都完成了,我们可以遍历数组,通过传递 3 个预览对象来创建 LinePreviews。
请注意,我传递的是同一个对象,但再次说明,这只是临时显示,以后还会更改:
var body: some View { ScrollView{ VStack(alignment: .leading, spacing: 2){ ForEach(previews, id: \.id) { preview in LineView(previewArray: [preview, preview, preview]) } } } }
因此,我们的完整代码应该是这样的:
struct TimelineView:View { let previews:[Preview] = [ 预览(id: 0, imageUrl: "1")、 预览(id: 1, imageUrl: "2")、 Preview(id: 2, imageUrl: "3")、 预览(id: 3, imageUrl: "4")、 预览(id: 4, imageUrl: "5")、 预览(id: 5, imageUrl: "6")、 预览(id: 6, imageUrl: "7")、 预览(id: 7, imageUrl: "8")、 预览(id: 8, imageUrl: "9")、 预览(id: 9, imageUrl: "10")、 预览(id: 10, imageUrl: "11")、 预览(id: 11, imageUrl: "12")、 预览(id: 12, imageUrl: "13") ] var body: some View { ScrollView{ VStack(alignment: .leading, spacing: 2){ ForEach(previews, id: \.id) { preview in LineView(previewArray: [preview, preview, preview]) } } } } } struct TimelineView_Previews:PreviewProvider { static var previews: some View { TimelineView() } } struct Preview { var id: Int let imageUrl:字符串 } struct LineView:View { let previewArray:[Preview] 预览数组 var body: some View { HStack(spacing: 2){ PreviewView(preview: 预览数组[0]) PreviewView(preview: previewArray[1]) PreviewView(preview: previewArray[2]) } } } struct PreviewView:View { let preview:预览 var body: some View { 图像(preview.imageUrl) .resizable() .frame(width: 136, height: 136) } }
如果我们在 Divider 下方的 ProfileView.swift 中调用它:
... 按钮(操作:{}){ Text("Edit Profile") .fontWeight(.bold) .foregroundColor(lightBlueColor) }.frame(width: 400) .padding() 分割线() TimelineView().padding(.leading, 10) ...
我们还可以在其下方添加另一个分割线,这样就能得到我们想要的最终效果:
... 按钮(操作:{}){ 文本("编辑个人资料) .fontWeight(.bold) .foregroundColor(lightBlueColor) }.frame(width: 400) .padding() 分割线() TimelineView().padding(.leading, 10) 分割线() ...
看起来怎么样?
看起来漂亮吗?
让我们在最后加上我们的…
底视图
底部视图将是另一个文件,因为我们将在应用程序的多个部分中使用它。
创建 BottomView.swift 文件,在其中创建一个由 4 个按钮组成的 HStack(因为按钮将并排放置),并在其中添加 Spacers。不要忘记图标!
struct BottomView:View { var body: some View { HStack{ Button(action: {}){ 图片("主页") .resizable() .frame(width: 30, height: 30) }.padding() 间隔() 按钮(操作:{}){ 图片("搜索") .resizable() .frame(width: 30, height: 30) }.padding() 间隔() 按钮(操作:{}){ 图片("plus-按钮") .resizable() .frame(width: 30, height: 30) }.padding() 间隔() 按钮(操作:{}){ 图片("心形") .resizable() .frame(width: 30, height: 30) }.padding() 间隔() 按钮(操作:{}){ 图片("用户") .resizable() .frame(width: 30, height: 30) }.padding() }.frame(height: 35) } }
这很简单!让我们将其集成到 ProfileView.swift 中,就在最后一个分割线的下方:
... Divider() TimelineView().padding(.leading, 10) 分割线() BottomView() ...
因此,ProfileView 的完整代码如下
导入 SwiftUI struct ProfileView:View { var body: some View { GeometryReader { geometry in VStack(alignment: .leading){ HStack{ 文本("用户名") .foregroundColor(lightBlueColor) .fontWeight(.semibold) .padding(.leading, 10) 按钮(操作:{}){ 图像("箭头向下) .resizable() .frame(width: 10, height: 10) } .padding(.top, 5) 间隔() 按钮(操作:{}){ 图像("菜单) .resizable() .frame(width: 20, height: 20) }.padding() }.frame(height: 50) .padding(.leading, 10) HStack{ VStack{ 图像("徽标-社交) .resizable() .frame(width: 90, height: 90) .clipShape(Circle()) .shadow(radius: 3) .overlay(Circle().stroke(Color.pink, lineWidth: 1)) 文本("您的姓名) .foregroundColor(lightBlueColor) .fontWeight(.semibold) }.padding(.leading, 10) VStack{ Text("10") .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) Text("Publications") .font(.system(size: 13)) .foregroundColor(lightBlueColor) }.padding(.leading, 30) VStack{ 文本("100) .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) 文本("关注者").font(.system(size: 13 .font(.system(size: 13)) .foregroundColor(lightBlueColor) }.padding() VStack{ 文本("1000) .font(.system(size: 30)) .foregroundColor(lightBlueColor) .fontWeight(.bold) Text("Following") .font(.system(size: 13)) .foregroundColor(lightBlueColor) } }.frame(height: 100) .padding(.leading, 10) 按钮(操作:{}){ Text("Edit Profile") .fontWeight(.bold) .foregroundColor(lightBlueColor) }.frame(width: 400) .padding() 分割线() TimelineView().padding(.leading, 10) 分割线() BottomView() } } } } struct ProfileView_Previews:PreviewProvider { static var previews: some View { ProfileView() } }
最后…
我们拥有了完整的 ProfileView:
多棒啊
总结
今天我们学习了如何在应用程序中模拟个人资料视图。它仍然只是一个模型,但我们会逐渐赋予它一些功能。
你学会了如何在 SwiftUI 中创建和重用组件,以及如何漂亮地使用它们来创建一个复杂的视图。太棒了
我们将在下一篇文章中创建其他一些视图!
敬请期待!
参考资料
- 本系列的第 1 部分是文章Instagram Clone。
- 第 2 部分是Swift Instagram Clone 一文。
- 第 3 部分是Instagram 克隆简介。
- 第 4 部分是Insta Clone Homeview 一文。
- 下载带有源代码的iOS Instagram克隆项目并开始使用Back4App。
现在就注册Back4App并 开始创建您的Instagram克隆应用程序。
SwiftUI 的优点是什么?
如果您想构建类似 Instagram 的克隆应用,SwiftUI 将发挥其魔力。
它有助于构建复杂的界面
组件的复用
复用界面的过程简单易行
您只需掌握如何将这些组件一起复用即可。
什么是 Hstack、Vstack 和 Zstack?
这些名称因美国代表而联系在一起。SwiftUI 使用以下模式构建其 UI。
-UI 使用垂直堆栈垂直构建
-UI 使用水平堆栈水平构建
-重叠视图使用 ZStack 构建
SwiftUI 最好的功能是什么?
SwiftUI 最大的特点就是可重用性。你可以反复使用组件来构建视图。这适用于所有应用。你需要知道如何使用这些组件。