一、目标
模拟微信的布局,使用SwiftUI
仿写微信App
。
二、思路
- 首先
将红色框选的区域,作为导航区(Navigation
)。
- 其次
蓝色框选的区域是内容列表(ContentView
)。
- 最后
绿色框选的区域是页签(TabView
)。
实际上,作为App
的页签,优先要做的,它是微信
、通讯录
、发现
和我
四个页面的导航按钮。通过点击TabView
上的导航按钮,可以分别跳转到对应的页面上。
三、TabView
页签
SwiftUI
中,TabView
是官方已经定义好的通用组件。
TabView
作为组件的主体,包裹导航按钮和导航对应的页面。
比如文档中的事例中,Text("The First Tab")
即为导航对应的页面。
.tabItem
中包裹的即为导航按钮。
如下图
所以,回归主题,通过改动导航按钮得到如下代码:
TabView {
Text("微信页面")
.tabItem {
Image(systemName: "message.fill")
Text("微信")
}
Text("通讯录页面")
.tabItem {
Image(systemName: "person.2.fill")
Text("通讯录")
}
Text("发现页面")
.tabItem {
Image(systemName: "location.circle")
Text("发现")
}
Text("我页面")
.tabItem {
Image(systemName: "person")
Text("我")
}
}
效果如下
请忽略图标不太一致的问题,图标默认使用的是
SF Symbols
图标库中的图标,这是苹果官方定义的一套图标库,大约有1500多个图标,但由于微信的图标是自设计的图标,所以有些图标在库中并没有,我找了一些类似的。当然可以通过Creating Custom Symbol Images for Your App创建自定义图标,导入Xcode中后,可以像SF Symbols
内置的图标一样使用。
四、Navigation
导航
设置好页签后,回到文章开头,下一步实现导航区。
SwiftUI
中,通过NavigationView
可以将包裹住的View
组件转换为可导航
的(通过NavigationLink
的配合,可以实现页面的跳转,此刻暂且不提)。
然后通过设置导航属性,实现目标。代码如下:
NavigationView{
Text("微信页面")
.navigationTitle("微信")
.navigationBarItems(leading: Text("· ·"), trailing: Image(systemName: "plus.circle"))
.navigationBarTitleDisplayMode(.inline)
}.tabItem {
Image(systemName: "message.fill")
Text("微信")
}
.navigationTitle
设置导航标题.navigationBarItems
设置左右导航按钮.navigationBarTitleDisplayMode
设置导航区域显示模式,默认为.automatic
不显示导航区,只有滑动页面的时候才会复现出来。.inline
即为直接显示导航区。
此时我们再看实际效果:
五、内容布局
列表暂且不提,先看每个列表内容的布局。
首先,将横向的区域分成两块。1是图片,2是内容。
SwiftUI
中,通过HStack
布局组件,设置横向布局。
模拟代码如下:
HStack{
Image()
Content()
}
其次,拆分内容。纵向分为3块,上方是发布人大众点评
,空格Spacer()
与日期2021/7/31
。中间是最新消息必吃榜发布......
,最下方是一个分割线Divider()
。
SwiftUI
中,通过VStack
布局组件,设置纵向布局,再结合HStack
。
模拟代码如下:
VStack{
HStack{
Text("大众点评")
Spacer()
Text("2021/7/31")
}
Text("必吃榜发布......")
Divider()
}
再调整下颜色、文本大小、padding
等,最终代码如下:
HStack{
Image("dzdp").resizable().scaledToFill().frame(width: 75, height: 75).cornerRadius(10).clipped().padding()
VStack(alignment: .leading){
HStack{
Text("大众点评").foregroundColor(.blue).font(.title3)
Spacer()
Text("2021/7/31").padding().foregroundColor(.gray)
}
Text("必吃榜发布!解密本地人爱吃的10碗面").foregroundColor(.gray).font(.callout).bold()
Divider()
}
}
预览效果如下:
这段代码结构,可以被多次复用,所以创建文件MessageView.swift
,将该代码迁入进来。
再将图片、发布人、日期、最新消息字段作为参数传入,再来看代码:
import SwiftUI
struct MessageView: View {
var imageName: String
var publisher: String
var date: String
var message: String
var body: some View {
HStack{
Image(imageName).resizable().scaledToFill().frame(width: 75, height: 75).cornerRadius(10).clipped().padding()
VStack(alignment: .leading){
HStack{
Text(publisher).foregroundColor(.blue).font(.title3)
Spacer()
Text(date).padding().foregroundColor(.gray)
}
Text(message).foregroundColor(.gray).font(.callout).bold()
Divider()
}
}
}
}
struct MessageView_Previews: PreviewProvider {
static var previews: some View {
MessageView(imageName: "dzdp", publisher: "大众点评", date: "2021/7/31", message: "必吃榜发布!解密本地人爱吃的10碗面")
}
}
视图
View
抽象:文中的各个内容页,比如微信
、通讯录
等随着后期内容的扩增,会变得越来越庞大,也难以维护,所以在此,将各个内容页创建对应的SwiftUI
文件抽象出来。
ChatListView
对应微信
。MailListView
对应通讯录
。ExploreView
对应发现
。MineView
对应我
。
结果如下图:
六、列表循环ForEach
SwiftUI
内置了一个循环组件的迭代器ForEach
,与for
循环很类似,先简单示范一下:
VStack{
ForEach(0..<5){index in
MessageView(imageName: "dzdp", publisher: "大众点评", date: "2021/7/31", message: "必吃榜发布!解密本地人爱吃的10碗面")
}
}
这是最简单的一种循环方式,ForEach的入参是一个区间运算符
。0..<5
代表从0开始,每次+1,循环到4为止。
这种计数的循环方式明显不符合要求,因为每个列表的内容都不一样。
所以需要以下2步:
-
- 创建一个
Message
,包含imageName
、publisher
、date
、message
4个字段。
- 创建一个
-
- 创建一个
Messages
数组,用于存储不同的Message
。
- 创建一个
所以创建Message.swift
,代码如下:
import Foundation
struct Message: Identifiable{
var id: Int
var imageName: String
var publisher: String
var date: String
var message: String
}
这里,Message实现
Identifiable
协议,并且增加了id
字段,这是因为ForEach
遍历的元素必须满足该条件。
然后创建数组messages
let messages: [Message] = [
Message(id: 0, imageName: "jd", publisher: "京东JD.COM", date: "2021/7/31", message: "交易完成通知"),
Message(id: 1, imageName: "hdl", publisher: "海底捞火锅", date: "2021/7/31", message: "这碗胡辣汤,可得劲儿!"),
Message(id: 2, imageName: "dzdp", publisher: "大众点评", date: "2021/7/31", message: "必吃榜发布!解密本地人爱吃的10碗面")
]
代码以及效果:
最后启动Simular
看最终效果