SwiftUI - 一起来仿写微信APP之一首页列表视图

简介

最近在学习 SwiftUI ,我一般都是先去学习界面布局,所以就想着仿写一下经常使用的软件的界面,所以先拿微信开刀。因为不想一次性发太多的内容,所以只好将主题分解,一部分一部分地去讲,接下来我们一起来学习吧。

如果你尝试过使用 SwiftUI 编写界面,你会发现是如此地舒心,我已深深地爱上了它。当然它的坑并不少,毕竟才刚出来,最低支持系统是 iOS13,估计还得等个几年才会慢慢在公司里使用上吧。但是这并不妨碍我们的学习。

在这篇文章里,我会一步一步编写微信的首页列表视图,一步一步将代码呈现上来,并仔细地讲解,我相信你们都可以看懂的,先来看看效果图。

image

很简单吧?是很简单,但是在编写的时候还是有些技巧在里面,毕竟,简单才不容易劝退嘛。在开始前先讲一下这篇文章将会用到的一些布局与组件,先给大家一个印象,方便后面的阅读理解。

HStack - 水平布局

VStack - 垂直布局

Text - 文本控件

Spacer - 扩展空间,使容器填满布局空间

Image - 图像控件

List - 列表控件

Divider - 分隔线控件

工作环境

Xcode - Version 11.3.1 (11C504)

Swift - version 5.1.3 (swiftlang-1100.0.282.1 clang-1100.0.33.15)

开始编写代码

编写列表行

我们来先把头像添加进来。

Image("1")
    .resizable() // 1
    .frame(width: 46, height: 46) // 2
    .cornerRadius(6)// 3

1 - 在 SwiftUI 中,如果需要控制图像的大小,则必须先调用resizable修饰

2- 设置图像大小

3 - 设置圆角大小,四个角的大小都相同

因为布局是横向的,所以我们在外层使用HStack包裹起来,然后添加联系人名字和最后发消息时间。

HStack {
    // 头像

    HStack {
        Text("女神")
            .font(.body) // 1

        Spacer()

        Text("下午 2:55")
            .font(.caption)
            .foregroundColor(Color.gray.opacity(0.5)) // 2
    }
}

1 - 使用 font 修饰字体,这里使用了苹果提供的标准字体,苹果还提供了 largeTitle, title, headline, subheadline, body, callout, footnote, caption。

2 - 使用 foregroundColor 修饰字体颜色,因为 gray 的灰色还是太黑了,所以这里又使用了 opacity 去修饰透明度为50%,使它显得更淡一点。

名字下方显示的是是最后发送或接收的消息内容,因此我们在外层使用 VStack 包裹起来。

VStack(alignment: .leading, spacing: 6) { // 1
    // 名称和时间

    Text("对不起,你是个好人")
        .font(.callout)
        .foregroundColor(Color.gray)
}

// 1 - 设定VStack里子控件居左对齐,默认是居中对齐。再设定子控件的间隙为 6 个像素,这样比较符合微信上面的设计。

现在样子已经出来了,我们先预览下效果。

image

我们给最外层的HStack增加padding,使它更美观一些,参数填写.all代表四周都需要边框。经过我的眼力观察,它的默认是 16px 的样子。

HStack {
    // 头像、名称、时间、消息内容
}
.padding(.all)

有了间距,好看多了。

image

接下来创建一个视图,它负责装载行视图,起名为GCMainRow

struct GCMainRow: View {
    var body: some View {
        HStack {
            Image("1")
                .resizable()
                .frame(width: 46, height: 46)
                .cornerRadius(6)
            
            VStack(alignment: .leading, spacing: 6) {
                HStack {
                    Text("女神")
                        .font(.body)

                    Spacer()

                    Text("下午 2:55")
                        .font(.caption)
                        .foregroundColor(Color.gray.opacity(0.5))
                }
                Text("对不起,你是个好人")
                    .font(.callout)
                    .foregroundColor(Color.gray)
            }
        }
        .padding(.all)
    }
}

然后在ContentView改为调用GCMainRow(),这样代码就好看很多了。

struct ContentView: View {
    var body: some View {
        GCMainRow()
    }
}

编写列表 List

好了,现在让我们来编写列表视图吧。我们在最外层使用List包裹GCMainRow,循环 20 个视图,数据多点才可以让我们滚动。

List(0 ..< 20) { _ in // 1
    GCMainRow()
}

1 - 因为我们不需要用到循环的一些数据,所以我们使用 _ 去忽略它。

List控件默认的都会有边距,下图黄色是GCMainRow的大小,可以看得出来旁边有空白的填充,这对我们当前的设计来说不太友好,因此我们需要想办法去掉这些边距填充。

image

List提供了listRowInsets来控制行的边距(上下左右),我们来试着使用一下。

List(0 ..< 20) { item in
    GCMainRow()
        .listRowInsets(EdgeInsets())
}

我们发现,这样写是没有作用的,listRowInsets的生效条件是“不能直接在List中使用,需要配合For Each语句才能生效”,我们再修改一下代码。

List {
    ForEach(0 ..< 20) { item in
        GCMainRow()
            .listRowInsets(EdgeInsets())
    }
}

好了,这次生效了,这就是我们要的结果。

image

自定义分隔线

我们仔细观察一下分隔线,在微信里分隔线是左对齐在名称和消息内容的,所以我们需要把现有的分隔线隐藏掉,然后再实现它。

在这里讲解一下,List是基于UITableView去实现的,这意味着我们可以通过appearance全局修改它的所有属性,正如我们现在需要取消它默认的分隔线,将separatorStyle设置为.none即可。

init() {
    UITableView.appearance().separatorStyle = .none
}

因为分隔线是贴着右边缘的,所以我们需要在包裹着名称、时间、消息内容的VStack外层再包裹一层VStack,在其中再添加分隔线Divider,并将里层的VStack设定右边距,最后将最外层的HStackpadding改为上和左边距。是不是听得有点懵?没关系,看看代码就很容易理解了。

HStack(alignment: .top) { // edit
    // 头像
    VStack { // new
        VStack(alignment: .leading, spacing: 6) {
            // 名称、时间、消息内容
        }
        .padding(.trailing) // new
        Divider() // new
    }
}
.padding(.top) // edit
.padding(.leading) // edit

我们现在来看一下效果。

image

未读消息小红点

未读消息在头像的右上方,小红点的中心点是位于头像的右上顶端。我们可以使用overlay叠加一个视图,来制作小红点吧。

Image("1")
        // ...
    .overlay(
        Color.red // 1
            .frame(width: 16, height: 16)
            .cornerRadius(8)
            .offset(x: 23, y: -23) // 2
)

// 1 - Color 本身也是一个视图组件,这是官方的定义 @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) extension Color : View { }

// 2 - 设定视图的偏移量,那么23是怎样得出来的呢?很简单,因为默认的overlay视图是位于父视图的中心,那么我们要将它放置在右上角,那么只需要宽高都除以2就可以了,那么这里的结果就是,x轴增加23px,y轴减少23px

接下来是未读数量,和上面的相似,在Color视图上利用overlay叠加Text视图就可以了。

Color.red
    .overlay( // 1
        Text("1")
            .font(.caption)
            .foregroundColor(.white)
)
    .frame(width: 16, height: 16)
    .cornerRadius(8)
    .offset(x: 23, y: -23)

// 1- 值得注意的是,因为文本也是需要偏移到右上角的,所以必须放在前面,不然它默认就是居中的

至此,代码演示结束,预览一下静态图,文章开头有 gif 动态图效果。

image

总结

好了,这篇文章就到这里,篇幅有点长了。不过没关系,我们来总结一下关键点:

  1. List默认是有行边距的,要取消或修改它的行边距,我们必须通过For Each再配合上listRowInsets才能实现。
  2. List是基于UITableView去实现的,这意味着我们可以通过appearance全局修改它的所有属性。
  3. 使用overlay可以给视图叠加一个视图。

Demo 源码下载

我已经把 Demo 上传至 GitHub 上面,项目名字是 SwiftUI-Tutorials,目录名为GCWechatList,有需要的朋友可以去下载运行一下,当然你也可以跟着文章去做一遍,这样更有利于你掌握此方面的知识。

文章篇幅有点长,虽然教的东西也挺简单,但概述得比较详细。任何东西都是先从简单入手的,才不会造成劝退不是吗?哈哈,此文章针对于新手而言还是很友好的,对于已经会的人来讲就可能废话有点多了,如果必须要喷,请轻喷,我比较玻璃心。

如果本文章对你有帮助,请关注我,你的关注就是我后续写文章的动力,下期会更精彩噢!

关于作者

博文作者:GarveyCalvin

微博:https://weibo.com/feiyueharia

博客园:https://www.cnblogs.com/GarveyCalvin

本文版权归作者,欢迎转载,但必须保留此段声明,并给出原文链接,谢谢合作!

公众号

作者第一次运营公众号,请你们一定要关注我的公众号,给我点动力,后期主要运营公众号为主。这是第二篇发布的文章,需要你们的支持,谢谢你们!

image

QQ群

一起讨论 SwiftUI,群主喜欢看热闹,当吃瓜人员。进来时填写你在哪里看到此文章的,并介绍下自己,一句话就行。

image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,123评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,031评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,723评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,357评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,412评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,760评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,904评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,672评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,118评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,456评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,599评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,264评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,857评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,731评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,956评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,286评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,465评论 2 348