聊天 媒体 语音消息 附加功能

聊天

一个 SwiftUI 聊天 UI 框架,具有完全可定制的消息单元格和内置的媒体选择器

SPM Compatible Cocoapods Compatible Carthage Compatible License: MIT

功能特点

使用方法

像这样创建一个聊天视图

@State var messages: [Message] = []

var body: some View {
    ChatView(messages: messages) { draft in
        yourViewModel.send(draft: draft)
    }
}

其中
messages - 要显示的消息列表
didSendMessage - 当用户按下发送按钮时调用的闭包

MessageChat 用于内部实现的类型。在上面的代码中,它期望用户提供一个 Message 结构体列表,并在 didSendMessage 闭包中返回一个 DraftMessage。您可以将它双向映射到您的 API 期望的自己的 Message 模型,或者按原样使用。

可用的聊天类型

聊天类型 - 决定消息的顺序和新消息动画的方向。 可用选项

回复模式 - 决定回复消息的外观。 可用选项

要指定任何这些,请通过 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 的参数

您可以像这样自定义输入视图(底部带有按钮的文本字段)

ChatView(messages: viewModel.messages) { draft in
    viewModel.send(draft: draft)
} inputViewBuilder: { textBinding, attachments, inputViewState, inputViewStyle, inputViewActionClosure, dismissKeyboardClosure in
    Group {
        switch inputViewStyle {
        case .message: // input view on chat screen
            VStack {
                HStack {
                    Button("Send") { inputViewActionClosure(.send) }
                    Button("Attach") { inputViewActionClosure(.photo) }
                }
                TextField("Write your message", text: textBinding)
            }
        case .signature: // input view on photo selection screen
            VStack {
                HStack {
                    Button("Send") { inputViewActionClosure(.send) }
                }
                TextField("Compose a signature for photo", text: textBinding)
                    .background(Color.green)
            }
        }
    }
}

inputViewBuilder 的参数

自定义消息菜单

长按消息将显示此消息的菜单(可以关闭,请参阅修饰符)。 要定义自定义消息菜单操作,请声明一个符合 MessageMenuAction 的枚举。 然后,如果您将枚举的名称传递给它(请参阅代码示例),则库将在长按消息时显示您的自定义菜单选项,而不是默认选项。 选择操作后,将调用特殊的 callbcak。 这是一个简单的例子

enum Action: MessageMenuAction {
    case reply, edit

    func title() -> String {
        switch self {
        case .reply:
            "Reply"
        case .edit:
            "Edit"
        }
    }
    
    func icon() -> Image {
        switch self {
        case .reply:
            Image(systemName: "arrowshape.turn.up.left")
        case .edit:
            Image(systemName: "square.and.pencil")
        }
    }
    
    // Optional
    // Implement this method to conditionally include menu actions on a per message basis
    // The default behavior is to include all menu action items
    static func menuItems(for message: ExyteChat.Message) -> [Action] {
        if message.user.isCurrentUser  {
            return [.edit]
        } else {
            return [.reply]
        }
    }
}

ChatView(messages: viewModel.messages) { draft in
    viewModel.send(draft: draft)
} messageMenuAction: { (action: Action, defaultActionClosure, message) in // <-- here: specify the name of your `MessageMenuAction` enum
    switch action {
    case .reply:
        defaultActionClosure(message, .reply)
    case .edit:
        defaultActionClosure(message, .edit { editedText in
            // update this message's text on your BE
            print(editedText)
        })
    }
}

messageMenuAction 的参数

在实现您自己的 MessageMenuActionClosure 时,编写一个 switch 语句,遍历您的 MessageMenuAction 的所有情况,在每种情况下编写您自己的操作处理程序,或调用默认的处理程序。 注意:并非所有的默认操作都能开箱即用 - 例如,对于 .edit,您仍然需要提供一个闭包来将编辑后的文本保存在您的 BE 上。 请参阅 ChatExample 项目中的 CommentsExampleView,以获取 MessageMenuActionClosure 用法示例。

自定义滑动操作

// Example: Adding Swipe Actions to your ChatView
ChatView(messages: viewModel.messages) { draft in
    viewModel.send(draft: draft)
} 
.swipeActions(edge: .leading, performsFirstActionWithFullSwipe: false, items: [
    // SwipeActions are similar to Buttons, they accept an Action and a ViewBuilder
    SwipeAction(action: onDelete, activeFor: { $0.user.isCurrentUser }, background: .red) {
        swipeActionButtonStandard(title: "Delete", image: "xmark.bin")
    },
    // Set the background color of a SwipeAction in the initializer,
    // instead of trying to apply a background color in your ViewBuilder
    SwipeAction(action: onReply, background: .blue) {
        swipeActionButtonStandard(title: "Reply", image: "arrowshape.turn.up.left")
    },
    // SwipeActions can also be selectively shown based on the message,
    // here we only show the Edit action when the message is from the current sender
    SwipeAction(action: onEdit, activeFor: { $0.user.isCurrentUser }, background: .gray) {
        swipeActionButtonStandard(title: "Edit", image: "bubble.and.pencil")
    }
])

swipeActions 的参数

小型视图构建器

这些使用 AnyView,所以请尽量保持它们足够简单

修饰符

isListAboveInputView - 消息表格是否在输入字段视图上方
showDateHeaders - 显示带有日期的部分标题,默认为 true
isScrollEnabled - 禁止消息的 UITabelView 滚动
showMessageMenuOnLongPress - 打开/关闭长按时显示菜单
showNetworkConnectionProblem - 打开/关闭显示网络错误
assetsPickerLimit - 设置库中内置的 MediaPicker 的限制
setMediaPickerSelectionParameters - 包含 MediaPicker 选择参数的结构(assetsPickerLimit 和其他参数,如 mediaType,selectionStyle 等)。
orientationHandler - 处理屏幕旋转

enableLoadMore(offset: Int, handler: @escaping ChatPaginationClosure) - 当用户从末尾滚动到第 offset 条消息时,调用处理函数,以便用户可以加载更多消息。 注意:除非向上滚动到最顶部,否则新消息不会出现在聊天中 - 这是一种优化。

自定义默认 UI

您可以使用 chatTheme 自定义默认 UI 的颜色和图像。 您可以传递所有/一些颜色和图像

.chatTheme(
    ChatTheme(
        colors: .init(
            mainBackground: .red,
            buttonBackground: .yellow,
            addButtonBackground: .purple
        ),
        images: .init(
            camera: Image(systemName: "camera")
        )
    )
)

请以类似的方式使用 mediaPickerTheme 来自定义内置的照片选择器。

仅对内置消息视图有意义

avatarSize - 默认的头像是一个圆形,您可以在此处指定其直径 tapAvatarClosure - 点击头像时调用的闭包
messageUseMarkdown - 使用 markdown(例如 ** 使某些内容加粗)或不使用 showMessageTimeView - 在消息的角落显示时间戳
setMessageFont - 传递自定义字体以用于消息

仅对内置输入视图有意义

setAvailableInput - 隐藏默认 InputView 中的某些按钮。 可用选项为: - .full - 媒体 + 文本 + 音频
- .textAndMedia
- .textAndAudio
- .textOnly

本地化

您可以使用标准的 SwiftUI 本地化过程来本地化输入,并将输入字符串添加到每种语言的 Localizable.strings 文件中。
该库使用以下可以本地化的文本

示例

有 2 个示例项目

创建您的 Firestore 应用 https://console.firebase.google.com/ 创建 Firestore 数据库(用于轻量级文本数据)https://firebase.google.com/docs/firestore/manage-data/add-data 创建 Cloud Firestore 数据库(用于图像和录音) https://firebase.google.com/docs/storage/web/start

示例

尝试 Chat 示例

安装

Swift Package Manager

dependencies: [
    .package(url: "https://github.com/exyte/Chat.git")
]

CocoaPods

pod 'ExyteChat'

Carthage

github "Exyte/Chat"

要求

我们其他的开源 SwiftUI 库

PopupView - Toast 和弹窗库
Grid - 最强大的 Grid 容器
ScalingHeaderScrollView - 带有粘性标题的滚动视图,滚动时标题会缩小
AnimatedTabBar - 带有多个预设动画的选项卡栏
MediaPicker - 可定制的媒体选择器
OpenAI 用于 OpenAI REST API 的 Wrapper 库
AnimatedGradient - 动画线性渐变
ConcentricOnboarding - 动画引导流程
FloatingButton - 悬浮按钮菜单
ActivityIndicatorView - 多个动画加载指示器
ProgressIndicatorView - 多个动画进度指示器
FlagAndCountryCode - 每个国家/地区的电话代码和标志
SVGView - SVG 解析器
LiquidSwipe - 流体导航动画