SwiftUI实战-仿写微信App(四)

2,572 阅读2分钟

这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战

前文

一、目标

实现通讯录列表以及右边的字母导航栏。

image.png

二、思路

其实SwiftUI提供了List组件是最简单实现这种列表布局的,不过这个页面并非完全规整的List布局,因为有搜索框,而在iOS14还无法自定义List子组件去掉下划线这种操作。所以需要换一种实现方式。

iOS15中通过给子组件增加.listRowSeparator(.hidden)属性,可以去掉下划线。

所以只能使用ScrollView的方式,通过自定义子组件样式,一样可以达成目的。

不过通讯录列表有个难点在于滑动到标题时,需要固定在顶部直到划过这个内容,如下图所示:

202108042134.gif

三、固定标题

看个代码例子

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

image.png

剖析下结构,就很简单了。

  • 使用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")
}

202108042134.gif

五、字母导航栏

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 判断。

屏幕录制2021-08-04 23.02.11.gif

六、导航事件

导航按钮的作用是能够点击立即跳转到对应元素的位置,所以需要ScrollViewReader组件支持

将该组件内嵌在ScrollView里,可以通过.scrollTo(id)的方式跳转到对应id的元素位置上。

先将对应标题元素打上id标记

image.png

然后增加onChange方法,监听wordNavigationtappedIndex字段,当点击对应的字母时,改变wordNavigationtappedIndex的值,就会触发onChange方法方法,再调用scrollTo方法即可跳转。

.onChange(of: wordNavigationtappedIndex, perform: { index in
    value.scrollTo(index)
})

这里之所以使用onChange监听wordNavigationtappedIndex的方式,是因为字母导航不在ScrollViewReader范围内,所以无法直接通过value.scrollTo(index)的方式跳转。

屏幕录制2021-08-04 23.32.49.gif

附:代码地址

gitee.com/dkwingcn/we…