一、目标
模拟微信的布局,使用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、message4个字段。
- 创建一个
-
- 创建一个
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看最终效果