前文
一、目标
本节我们要实现聊天窗口页面。
二、思路
-
导航区。通过点击导航区左上角的按钮
<
回到列表页。同理,通过点击列表页的某个列表元素能够跳转到对应的窗口页。 -
内容区域。聊天气泡+用户图标,在通过循环输出。
-
工具栏,4个图标按钮+输入框。
三、NavigationLink页面跳转
首先回到上节说到的MessageView
。SwiftUI
通过NagivationLink
组件可以实现从页面A跳转到页面B的效果。
NavigationLink(
destination: Text("Destination"),
isActive: $MessageChatPresented,
label: {
MessageView(imageName: message.imageName, publisher: message.publisher, date: message.date, message: message.message)
})
label
指向的是MessageView
即页面A。destination
指向的是即将跳转的页面B。isActive
是一个Bool
值,通过isActive=true
实现跳转
,反之isActive=false
实现返回
的效果。这里通过@State
注解修饰MessageChatPresented
变量,使其从值传递
编程引用传递
进而可以在父子组件间传递,在子组件修改它的值,父组件也会跟着改变。$
符号修饰MessageChatPresented
变量,使其转换成Binding
,这的作用是是通知子组件,这个变量是引用传递
,所以子组件接收时也需用@Binding
注解修饰。这种用法即为双向绑定
。
实现效果如下:
NavigationLink
默认会指定给destination
一个返回按钮<
,如果想要自定义按钮并实现返回效果,可以通过修改isActive
对应的MessageChatPresented
变量的值为false
即可。
同理,单独创建一个MessageChatView.swift
文件用来编写聊天窗口。然后修改destination: MessageChatView()
。
再看导航窗口,差一个标题和右上角的按钮。
同上节,使用NagivationTitle
和navigationBarItems
即可。
代码如下:
Text("Hello, World!")
.navigationTitle(title)
.navigationBarItems(trailing: Image(systemName: "person"))
四、Path聊天气泡
现在需要绘制聊天内容的主题,也就是聊天气泡。气泡的左右有一个小小的三角,是个不规则的形状,所以现有的Shape
组件无法满足条件,需要自行绘制。
SwiftUI
提供Shape
协议,实现该协议并完成path
方法即可绘制自定义的图形。
struct ChatBubbleShape: Shape {
func path(in rect: CGRect) -> Path {
let width = rect.width
let height = rect.height
let path = Path{p in
p.move(to: CGPoint(x: 0, y: 0)) //移动到坐标轴0,0,默认为左上角
p.addLine(to: CGPoint(x: width, y: 0)) //向右移动width距离
p.addLine(to: CGPoint(x: width, y: height/3)) //向下移动到height/3处
p.addLine(to: CGPoint(x: width+20, y: height/2)) //向右移动20,向下移动到height/2处
p.addLine(to: CGPoint(x: width, y: 2*height/3)) //向左移动20,向下移动到2*height/3处
p.addLine(to: CGPoint(x: width, y: height)) //向下移动到height处
p.addLine(to: CGPoint(x: 0, y: height)) //向左移动到x=0
p.addLine(to: CGPoint(x: 0, y: 0)) //向上移动到x=0,y=0
}
return path
}
}
绘图顺序如下图所示:
调整下样式,最终效果如下:
最后的工作也很简单和重复,在此不一一列举,只是说明下实现方式。
- 聊天气泡分为左右2种:参考上方的绘图思路。
- 气泡宽度:可以根据字符的长度动态调整。
- 圆角问题:结合
p.addCurve
即可。参考示例
最后使用ScrollView
可以实现滚动视图,通过对消息内容的遍历即可实现效果。
五、工具栏与TextField
工具栏菜单由4个按钮和一个文本域组成。
HStack{
Image(systemName: "text.bubble").font(.title)
Image(systemName: "waveform.circle").font(.title)
TextField("输入", text: $text).textFieldStyle(RoundedBorderTextFieldStyle())
Image(systemName: "face.smiling").font(.title)
Image(systemName: "plus.circle").font(.title)
}.padding()
TextField
最少包含2个参数,第一个参数为placeholder
。第二个参数为输入框的值,需要使用@State
和$
修饰的方式,实现父子间的双向绑定,这样一旦用户通过键盘输入任意值,子组件TextField
感知到后就会修改这个值使得父组件感知。
最后通过ZStack
加上Rectangle
的方式,使工具栏不透明,代码如下:
ZStack(alignment: .bottomLeading){
Rectangle().fill().foregroundColor(.white)
HStack{
Image(systemName: "text.bubble").font(.title)
Image(systemName: "waveform.circle").font(.title)
TextField("", text: $text).textFieldStyle(RoundedBorderTextFieldStyle())
Image(systemName: "face.smiling").font(.title)
Image(systemName: "plus.circle").font(.title)
}.padding()
}.frame(height: 50)
最终效果: