这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战
前文
一、目标
实现通讯录列表以及右边的字母导航栏。
二、思路
其实SwiftUI
提供了List
组件是最简单实现这种列表布局的,不过这个页面并非完全规整的List
布局,因为有搜索框,而在iOS14
还无法自定义List
子组件去掉下划线这种操作。所以需要换一种实现方式。
iOS15
中通过给子组件增加.listRowSeparator(.hidden)
属性,可以去掉下划线。
所以只能使用ScrollView
的方式,通过自定义子组件样式,一样可以达成目的。
不过通讯录列表有个难点在于滑动到标题时,需要固定在顶部直到划过这个内容,如下图所示:
三、固定标题
看个代码例子
ScrollView{
LazyVStack(pinnedViews: [.sectionHeaders]){
Section(header: Text("A").background(Color.red)){
Text("Apple")
Text("Aaron")
}
Section(header: Text("B").background(Color.red)){
Text("Black")
Text("Bad")
}
}
}
LazyVStack
提供参数pinnedViews
,可以设置锁定头部标题.sectionHeaders
还是结尾标题.sectionFooters
- 通过
Section
组件,可以将子组件划分为不同的组,header
指定标题内容,包裹的即为组内元素。
四、自定义列表元素,仿List
剖析下结构,就很简单了。
- 使用
VStack
可以纵向布局红色和绿色的元素 - 使用
HStack
可以横向布局图标和文本元素 - 使用
ZStack
叠加图标和矩形元素
代码如下:
VStack(alignment: .leading){
HStack{
ZStack{
Rectangle().fill().cornerRadius(5).foregroundColor(color).aspectRatio(1, contentMode: .fit).frame(height: 50)
Image(systemName: "person.2.circle").foregroundColor(.white).font(.system(size: 40))
}.padding(.trailing)
Text(text)
}
Divider()
}.padding()
aspectRatio
用于指定目标元素的宽高比率,对矩形和图片都适用
接下来再将header
元素改造一下。
创建MailSectionHeader.swift
文件。
struct MailSectionHeader: View {
var title: String
var body: some View {
HStack{
Text(title)
Spacer()
}.padding().background(Color(red: 237/255, green: 237/255, blue: 237/255))
}
}
然后改造Section
Section(header: MailSectionHeader(title: "A").background(Color.red)){
MailSection(text: "Apple")
MailSection(text: "Aaron")
}
五、字母导航栏
HStack{
Spacer()
VStack(spacing: 15){
ForEach(0..<words.count, id: \.self){index in
if index == tapped {
SelectedText(title: words[index]).onTapGesture {
tapped = index
}
} else{
Text(words[index]).onTapGesture {
tapped = index
}
}
}
}.padding()
}
样式之类的前面的章节讲过很多,主要讲一下怎么定位点击的导航字母。
- 使用
onTapGesture
增加点击事件,并记录点击的序号。 - 通过
@State
修饰的变量,会在变化的时候,使SwiftUI
触发更新UI
事件,进而重新执行if index == tapped
判断。
六、导航事件
导航按钮的作用是能够点击立即跳转到对应元素的位置,所以需要ScrollViewReader
组件支持
将该组件内嵌在ScrollView
里,可以通过.scrollTo(id)
的方式跳转到对应id的元素位置上。
先将对应标题元素打上id
标记
然后增加onChange方法
,监听wordNavigationtappedIndex
字段,当点击对应的字母时,改变wordNavigationtappedIndex
的值,就会触发onChange方法
方法,再调用scrollTo
方法即可跳转。
.onChange(of: wordNavigationtappedIndex, perform: { index in
value.scrollTo(index)
})
这里之所以使用
onChange
监听wordNavigationtappedIndex
的方式,是因为字母导航
不在ScrollViewReader
范围内,所以无法直接通过value.scrollTo(index)
的方式跳转。