聊天 | 媒体 | 语音消息 | 附加功能 |
---|---|---|---|
聊天
一个SwiftUI聊天UI框架,具有完全可自定义的消息单元格和内置的媒体选择器
功能
- 显示您的消息并支持分页,允许您创建和"发送"新消息(发送意味着调用闭包,因为用户将提供实际的API调用)
- 允许您为消息和输入视图传递自定义视图构建器
- 内置照片和视频库/相机选择器,支持多媒体资产选择
- 可以在长按消息单元格时显示全屏菜单(自动为长消息显示滚动)
- 通过消息菜单或闭包支持"回复消息"。删除和编辑功能即将推出
- 该库允许在消息中以任意组合发送以下内容:
- 带/不带markdown的文本
- 照片/视频
- 音频录音 即将推出:
- 用户位置
- 文档
- 带预览的链接
使用方法
像这样创建一个聊天视图:
@State var messages: [Message] = []
var body: some View {
ChatView(messages: messages) { draft in
yourViewModel.send(draft: draft)
}
}
其中:
messages
- 要显示的消息列表
didSendMessage
- 用户按下发送按钮时调用的闭包
Message
是Chat
用于内部实现的类型。在上面的代码中,它期望用户提供一个Message
结构体列表,并在didSendMessage
闭包中返回一个DraftMessage
。您可以将其双向映射到您的API期望的自己的Message
模型,或者按原样使用。
可用的聊天类型
聊天类型 - 决定消息的顺序和新消息动画的方向。可用选项:
conversation
- 最新消息在底部,新消息从底部出现comments
- 最新消息在顶部,新消息从顶部出现
回复模式 - 决定回复消息的显示方式。可用选项:
quote
- 当回复消息A时,新消息将作为最新消息出现,在其正文中引用消息Aanswer
- 当回复消息A时,新消息将直接出现在消息A下方作为单独的单元格,而不在其正文中重复消息A
要指定这些选项,可以通过init
传递:
ChatView(messages: viewModel.messages, chatType: .comments, replyMode: .answer) { draft in
yourViewModel.send(draft: draft)
}
自定义UI
您可以这样自定义消息单元格:
ChatView(messages: viewModel.messages) { draft in
viewModel.send(draft: draft)
} messageBuilder: { message, positionInUserGroup, positionInCommentsGroup, showContextMenuClosure, messageActionClosure, showAttachmentClosure in
VStack {
Text(message.text)
if !message.attachments.isEmpty {
ForEach(message.attachments, id: \.id) { at in
AsyncImage(url: at.thumbnail)
}
}
}
}
messageBuilder
的参数:
message
- 包含用户信息、附件等的消息positionInUserGroup
- 消息在同一用户连续消息集合中的位置positionInCommentsGroup
- 消息在其连续评论组中的位置(仅适用于.answer回复模式,.quote模式下为nil)showContextMenuClosure
- 显示消息上下文菜单的闭包messageActionClosure
- 传递用户交互的闭包,例如.replyshowAttachmentClosure
- 您可以将附件传递给此闭包以使用ChatView的全屏媒体查看器
您可以这样自定义输入视图(底部带按钮的文本字段):
ChatView(messages: viewModel.messages) { draft in
viewModel.send(draft: draft)
} inputViewBuilder: { textBinding, attachments, inputViewState, inputViewStyle, inputViewActionClosure, dismissKeyboardClosure in
Group {
switch inputViewStyle {
case .message: // 聊天屏幕上的输入视图
VStack {
HStack {
Button("发送") { inputViewActionClosure(.send) }
Button("附加") { inputViewActionClosure(.photo) }
}
TextField("输入您的消息", text: textBinding)
}
case .signature: // 照片选择屏幕上的输入视图
VStack {
HStack {
Button("发送") { inputViewActionClosure(.send) }
}
TextField("为照片添加说明", text: textBinding)
.background(Color.green)
}
}
}
}
inputViewBuilder
的参数:
textBinding
用于绑定您自己的TextFieldattachments
是一个包含照片、视频、录音和您正在回复的消息的结构体inputViewState
- 输入视图的状态,如果可能,由库自动控制,或通过您调用inputViewActionClosure
来控制inputViewStyle
-.message
或.signature
(聊天屏幕或照片选择屏幕)inputViewActionClosure
用于在点击您的自定义按钮时调用。例如,如果您想用自己的按钮发送消息,可以调用inputViewActionClosure(.send)
,然后库会重置文本和附件并调用didSendMessage
发送闭包dismissKeyboardClosure
- 调用此闭包以关闭键盘
自定义消息菜单
长按消息将显示该消息的菜单(可以关闭,请参见修饰符)。要定义自定义消息菜单操作,请声明一个符合MessageMenuAction
的枚举。然后,如果您将枚举的名称传递给它,库将在长按消息时显示您的自定义菜单选项,而不是默认选项(请参见代码示例)。一旦选择了操作,将调用特殊回调。这里是一个简单的例子:
enum Action: MessageMenuAction {
case reply, edit
func title() -> String {
switch self {
case .reply:
"回复"
case .edit:
"编辑"
}
}
func icon() -> Image {
switch self {
case .reply:
Image(systemName: "arrowshape.turn.up.left")
case .edit:
Image(systemName: "square.and.pencil")
}
}
}
ChatView(messages: viewModel.messages) { draft in
viewModel.send(draft: draft)
} messageMenuAction: { (action: Action, defaultActionClosure, message) in // <-- 在这里:指定你的 `MessageMenuAction` 枚举的名称
switch action {
case .reply:
defaultActionClosure(message, .reply)
case .edit:
defaultActionClosure(message, .edit { editedText in
// 在你的后端更新这条消息的文本
print(editedText)
})
}
}
messageMenuAction
的参数:
selectedMenuAction
- 用户从菜单中选择的操作。注意:声明这个变量时,需要明确指定其类型(你自定义的 MessageMenuAction 的子类)defaultActionClosure
- 一个闭包,接受 MessageMenuAction 默认实现的案例,提供简单的操作处理器;如果你需要默认操作,可以调用这个闭包,传入选中的消息并选择一个默认操作(.reply, .edit);或者你可以为所有操作编写自定义实现,在这种情况下只需忽略这个闭包message
- 显示菜单的消息
在实现你自己的 MessageMenuActionClosure
时,编写一个 switch 语句遍历你的 MessageMenuAction
的所有情况,在每个 case 中编写你自己的操作处理器,或者调用默认的处理器。注意:并非所有默认操作都能开箱即用 - 例如,对于 .edit
,你仍需提供一个闭包来在后端保存编辑后的文本。请参考 ChatExample 项目中的 CommentsExampleView 以了解 MessageMenuActionClosure 的使用示例。
小型视图构建器:
这些使用 AnyView
,所以请尽量保持简单
betweenListAndInputViewBuilder
- 在聊天列表视图和输入视图之间显示的内容mainHeaderBuilder
- 整个聊天的头部,会随所有消息和头部一起滚动headerBuilder
- 日期分节头部构建器
修饰符
isListAboveInputView
- 消息表格是否在输入字段视图上方
showDateHeaders
- 是否在不同日期之间显示带日期的分节头部,默认为 true
isScrollEnabled
- 禁止消息 UITableView
的滚动
showMessageMenuOnLongPress
- 开启/关闭长按显示菜单
showNetworkConnectionProblem
- 开启/关闭网络错误显示
assetsPickerLimit
- 设置库内置 MediaPicker 的限制
setMediaPickerSelectionParameters
- 一个结构体,持有 MediaPicker 的选择参数(assetsPickerLimit 和其他如 mediaType, selectionStyle 等)
orientationHandler
- 处理屏幕旋转
enableLoadMore(offset: Int, handler: @escaping ChatPaginationClosure)
- 当用户滚动到距离末尾第 offset
条消息时,调用处理函数,以便用户加载更多消息
chatNavigation(title: String, status: String? = nil, cover: URL? = nil)
- 传递聊天导航栏的信息
仅适用于内置消息视图
avatarSize
- 默认头像是一个圆形,你可以在这里指定其直径
tapAvatarClosure
- 点击头像时调用的闭包
messageUseMarkdown
- 是否使用 markdown(例如用 ** 使文字加粗)
showMessageTimeView
- 在消息角落显示时间戳
setMessageFont
- 传递自定义字体用于消息
仅适用于内置输入视图
setAvailableInput
- 在默认 InputView 中隐藏一些按钮。可用选项有:
- .full
- 媒体 + 文本 + 音频
- .textAndMedia
- .textAndAudio
- .textOnly
示例
有两个示例项目:
- 一个是简单的机器人,每2秒发送随机文本/媒体消息。它没有后端和本地存储。每次启动都是全新的。
- 另一个与 Firestore 数据库集成。它具有所有必要的后端支持,包括存储媒体和音频消息、未读消息计数器等。你需要创建自己的 Firestore 应用和数据库。还需用你自己的替换
GoogleService-Info
。之后你可以在多个模拟器/设备上进行测试。
创建你的 firestore 应用 https://console.firebase.google.com/ 创建 firesote 数据库(用于轻量级文本数据) https://firebase.google.com/docs/firestore/manage-data/add-data 创建云 firestore 数据库(用于图片和语音录音) https://firebase.google.com/docs/storage/web/start
示例
要尝试 Chat 示例:
- 克隆仓库
git clone git@github.com:exyte/Chat.git
- 打开终端并运行
cd <ChatRepo>/Example
- 等待 SPM 下载完成包
- 运行它!
安装
Swift Package Manager
dependencies: [
.package(url: "https://github.com/exyte/Chat.git")
]
CocoaPods
pod 'ExyteChat'
Carthage
github "Exyte/Chat"
要求
- iOS 16+
- Xcode 14+
我们的其他开源 SwiftUI 库
PopupView - 吐司和弹出窗口库 Grid - 最强大的网格容器 ScalingHeaderScrollView - 带有随滚动缩小的粘性头部的滚动视图 AnimatedTabBar - 带有多种预设动画的标签栏 MediaPicker - 可自定义的媒体选择器 OpenAI OpenAI REST API 的包装库 AnimatedGradient - 动画线性渐变 ConcentricOnboarding - 动画引导流程 FloatingButton - 浮动按钮菜单 ActivityIndicatorView - 多种动画加载指示器 ProgressIndicatorView - 多种动画进度指示器 FlagAndCountryCode - 每个国家的电话区号和国旗 SVGView - SVG 解析器 LiquidSwipe - 液体导航动画