STRVChatUI 0.0.7

STRVChatUI 0.0.7

Jan Schwarz维护。



 
依赖
STRVChatCore~> 0.0.9
MessageKit~> 3.4.2
 

  • 作者
  • Jan Schwarz, Tomáš Čejka, Daniel Pecher 和 Mireya Orta

iOS Chat Component Swift 5.3

描述

此仓库包含一个通用聊天框架,可以被轻松扩展以用于任何包含标准应用内聊天组件的项目。此库分为多个模块(层)。每个模块都是一个独立的框架目标,因此可以隐藏彼此的实现细节。每个模块也是自己的Pod。

安装

CocoaPods

要使用 CocoaPods 将聊天组件集成到您的 Xcode 项目中,请在您的 Podfile 中指定它。

source 'https://cdn.cocoapods.org/'

target 'TARGET_NAME' do
  # For plug&play solution with predefined MessageKit UI and Firestore backend
  pod 'STRVChat'

  # If you want to use just some of the layers
  pod 'STRVChat/Core'
  pod 'STRVChat/NetworkingFirestore'
  pod 'STRVChat/UI'
end

初始化

默认聊天组件实现使用后台抓取和计划任务,必须在应用代理的 didFinishLaunchingWithOptions 方法中实例化。

示例

样例应用,了解如何使用本库。

架构

该库由四个模块组成

  • 聊天
  • 聊天核心
  • 聊天网络Firestore
  • 聊天 UI

请参阅此图,以了解架构的视觉概述

ChatCore - 核心模型和协议

ChatCore

UI可以使用的主要类,用于所有聊天操作。它包含所有必要的方发,并实现了ChatCoreServicing协议。

DataPayload

此类携带从监听器(例如会话集合)接收的数据以及数据是否已到达末尾的信息。这对UI隐藏“加载更多”按钮等非常有用。未来可能包含更多元数据。

ChatNetworkingFirestore - Firestore 网络层(可选)

如果你的项目使用 Firestore 作为聊天数据库,可以使用此 pod。如果需要使用其他 API,可以编写自己的网络层。请参阅编写自定义网络层

ChatUI - 使用 MessageKit 的 UI(可选)

简单的 UI 实现,用于测试。大多数项目可能希望编写自己的 UI。请参阅编写自定义 UI 层

Chat - 粘合剂

如果你想使用现有的ChatCoreServicingChatNetworkServicingChatUIServicing实现,并且不需要自定义任何功能,可以使用库的这一部分。它包含所有组成部分正常工作所需的粘合代码。目前它只包含 Pump:n Pie 项目的聊天解决方案,但如果你想从不同的组件构建自己的聊天,这很简单。请参阅粘合 UI 层和网络层

编写自定义UI层

提供的UI层相当简单,不提供任何定制化选项,因此在项目中通常会创建自己的UI层。唯一需要的是向ChatCore类提供ChatUIModels实现,指定要将哪些模型提供给你的完成处理程序。

class ChatCore<Networking: ChatNetworkServicing, Models: ChatUIModels>: ChatCoreServicing
public protocol ChatUIModels {
    associatedtype CUI: ConversationRepresenting
    associatedtype MUI: MessageRepresenting
    associatedtype MSUI: MessageSpecifying
    associatedtype USRUI: UserRepresenting
}

编写自定义网络层

实现自定义网络层所需仅是遵循ChatNetworkServicing协议,并将此实现提供给ChatCore,如下一章所示。

将UI层和网络层组合

以下以上面提到的完整Pumpkin Pie解决方案为例。

首先,你需要你的ChatNetworkServicingChatCoreServicingChatUIServicing实现。从现在开始,假设你想要使用ChatCore作为ChatCoreServicing的实现,因为它足够通用,可以与ChatNetworkServicingChatUIServicing实现的各种组合一起工作。在Pumpkin Pie的情况下,这些实现分别是ChatNetworkFirestoreChatUI

其次,你需要确保所有的网络模型都遵循ChatUIConvertible,例如

extension ConversationFirestore: ChatUIConvertible {
    public var uiModel: Conversation {
      // return Conversation instance created using data from `self`
    }

    public init(uiModel: Conversation) {
      // call self.init using data from `uiModel`
    }
}

并且确保所有UI模型都遵循ChatNetworkingConvertible,如下所示

extension Conversation: ChatNetworkingConvertible {
    public typealias NetworkingModel = ConversationFirestore
}

第三,你需要确保你的ChatUIServicing实现与ChatCore兼容,只需这样做:

public class ChatUI<Core: ChatUICoreServicing>: ChatUIServicing {
  ...
}

public protocol ChatUICoreServicing: ChatCoreServicing where C == Conversation, M == MessageKitType, MS == MessageSpecification, U == User { }

extension ChatCore: ChatUICoreServicing where Models.CUI == Conversation, Models.MSUI == MessageSpecification, Models.MUI == MessageKitType, Models.USRUI == User { }

接下来,你需要将你的ChatUIServicing实现包装在ChatInterfacing中,以便管理不同场景的UI的不同实例

public class PumpkinPieInterface: ChatInterfacing {
    public let identifier: ObjectIdentifier
    public let uiService: ChatUI<...>    
    public weak var delegate: Delegate?
    public var rootViewController: UIViewController {
        uiService.rootViewController
    }
        
    init(...) {...}
}

最后,通过实现ChatSpecifying协议将一切组合在一起

public class PumpkinPieChat: DefaultChatSpecifying {
    public typealias UIModels = ChatModelsUI
    public typealias Networking = ChatNetworkingFirestore
    public typealias Core = ChatCore<ChatNetworkingFirestore, UIModels>
    public typealias Interface = PumpkinPieInterface

    public init(...) {...}

    func interface(with identifier: ObjectIdentifier) -> Interface {...}

    func runBackgroundTasks(completion: @escaping (UIBackgroundFetchResult) -> Void) {...}

    func resendUnsentMessages() {...}

    func setCurrentUser(userId: EntityIdentifier, name: String, imageUrl: URL?) {...}
}

Chat核心使用

监听器

由于聊天是一种实时功能,客户端需要尽快通知有关任何新数据更新的情况。这就是为什么回话和消息的获取是通过持久连接来完成的。每次您调用core.listenToConversationscore.listenToMessages时,您都会接收到一个ChatListener(目前仅作为String类型别名)实例,您必须将其存储在某处。当您准备好停止监听这组特定数据时,请调用core.remove(listener: ChatListener),以确保没有不必要的网络连接留下。

发送消息

core.send(message: .text(message: "Hello!"), to: currentChatId) { result in

    // Called after successfuly sending a message

    switch result {
    case .success(let message):
        print(message.id)
    case .failure(let error):
        print(error)
    }
}

监听会话

core.listenToConversations { result in

    // Called on every conversations update

    switch result {
    case .success(let payload):
        // payload contains current list of fetched conversations
        // as well as some additional data (see `DataPayload` for more info)
    case .failure(let error):
        print(error)
    }
}

监听一个会话的消息

core.listenToMessages(conversation: conversationId) { result in

    // Called on every messages update
    
    switch result {
    case .success(let payload):
        // payload contains current list of fetched conversations
        // as well as some additional data (see `DataPayload` for more info)
    case .failure(let error):
        print(error)
    }
}

分页

当您需要加载会话或消息的下一页时,只需分别调用core.loadMoreConversationscore.loadMoreMessages。您在调用core.listenTo...方法时提供的现有回调将在更新后的数据集上被调用。(因此,在首次调用core.listenToConversations时,您将收到1 * n个会话,并在调用core.loadMoreConversations后收到2 * n个会话)。

任务管理

为了使聊天核心组件考虑得更加周全,使用了任务管理器进行了增强。任务管理器类允许聊天核心进行具有额外属性的调用,并处理所有背后的逻辑。使用任务管理会影响聊天核心本身的对任务完成的处理。

支持的属性
  • 初始化后
  • 后台任务
  • 后台线程
  • 重试(具有有限/无限重试类型的重试)
初始化后

一些调用(如加载对话)可以进入任务管理器的队列,即使聊天核心尚未连接。这些任务将在队列中保留,并在核心加载后执行。

后台任务

归因于后台任务的调用将在应用进入后台后尝试继续。最初,所有任务都与应用的《code>beginBackgroundTaskWithName:expirationHandler:方法挂钩。其次,如果iOS版本为13+且有未完成的任务在队列中,则使用《code>BGSCheduledTask在一段时间后激活应用以重新执行这些任务。如果聊天在较旧的iOS版本上运行,则使用后台获取。

后台线程

归因于后台线程的任务将在其专用的后台线程中运行。

重试

为了允许任务重试,有两种方法:有限和无限。无限类型用于整个聊天核心的初始加载。默认尝试次数为3的有限重试类型用于发送消息。请注意,只有网络错误在进行重试时才有效,其他错误不计入。

调用示例

下面的示例中可以找到发送消息任务方法。任务管理器包装发送消息网络调用,并对其应用一些属性。如果使用重试属性,需要控制taskCompletion闭包的响应,如示例中失败的情况所示。

taskManager.run(attributes: [.backgroundTask, .afterInit, .backgroundThread, .retry(.finite())]) { [weak self] taskCompletion in
            let mess = Networking.MS(uiModel: message)
            self?.networking.send(message: mess, to: conversation) { result in
                switch result {
                case .success(let message):
                    _ = taskCompletion(.success)
                    completion(.success(message.uiModel))

                case .failure(let error):
                    if taskCompletion(.failure(error)) == .finished {
                        completion(.failure(error))
                    }
                }
            }
        }

聊天核心状态

当聊天核心创建时,会有一些状态可以在其生命周期内发生变化。这可能观察这些变化,从而使UI能够正确反应。核心包含可达性观察器以检查网络连接的可用性。

可用状态
  • 初始 核心创建后
  • 加载中 初始化后,核心尝试连接服务提供商并设置所有内容
  • 连接状态 所有加载工作完成后,核心准备好执行其工作
  • 正在连接 当核心收到网络断开的通知时

缓存未发送的消息

当核心发送消息时,它将自动存储到本地安全缓存(密钥链)中。在消息发送完成后(例如后台任务)将从缓存中删除。如果由于任何原因发送失败,用户可以在UI中尝试重试或删除消息。

功能

对话

  • 对话列表
  • 当对话更新时获取通知
  • 实现后台获取以动态重试机制
  • 在线状态指示器

消息

  • 当创建/更新消息时获取通知
  • 已读回执 - 显示时间戳和日期旁边最后一个对方已读的消息
  • 缓存未能发送的消息
  • 发送消息的重试机制
  • 即使在用户将应用置于后台时,也要继续发送消息
  • 删除消息

用户

  • 名称
  • 头像

支持的消息类型

  • 文本
  • 照片
  • 视频
  • 位置

未来功能

这些功能将在开发后阶段添加,但框架必须考虑到它们。

  • 持久化
  • 管理
  • 扩展 - RxSwift, PromiseKit等
  • 输入指示
  • 通知

参考文献

现有解决方案

在pod中使用的开源UI组件