融云近期推出的 Global IM UIKit,支持开拓者高效知足外洋用户交互体验需求,且保留了相称的产品张力授予开拓者更多自由和灵巧性,是实现环球化社交组件的不二之选。关注【融云环球互联网通信云】理解更多
Global IM UIKit 采取“一键开关、二级重写、三级自定义”来扩充自定义能力,提升产品集成灵巧性,更好支持业务层创新。
一键开关:对付通用的、可列举的交互和 UI 自定义范围,Global IM UIKit 内部已经帮开拓者实现了干系功能和适配,只需一行代码,即可实现多种交互和 UI 的样式切换。
二级重写:支持 Module(页面)和 Component(组件)重写,包含 ViewModel、ViewController、View 的继续与重写。适用于对默认实现办法的极度个性化定制,扩展性极高。
三级自定义:针对通用的、不可列举的自定义范围,供应 API 自定义能力,例如:导航栏的操作项、输入框扩展功能入口等。
本文将以 iOS 端集成为例,分享 Global IM UIKit 中组件的灵巧拆分和高扩展性的设计思路,以及快速集成的实战教程。
紧张功能和核心架构在即时通讯组件中,紧张的功能可聚合为两大页面:
会话列表:以会话工具为元素,展示最近的会话工具,也是进行会话的紧张入口。Global IM UIKit 针对会话项目支持左滑编辑和右滑编辑,供应置顶、免打扰、标记已读等操作。
会话:以工具为元素,展示所有工具,也是所有的发起入口。Global IM UIKit 中常用的类型有:文本、图片、视频、文件、语音、贴纸等,支持回答、转发、复制等常用快捷交互。
除以上两大页面之外,还有很多其他衍生页面,如:多媒体、预览、用户列表、用户信息等。当然,作为国际化产品,多措辞和主题也是不可少的。
在 Global IM UIKit 中,虽然核心页面构造相对大略,但其社交功能聚合度非常高。尤其是在会话页面中,的吸收、发送、操作等,不仅仅是数据和 UI 的交互,还涉及的时序、安全、性能等,以及 SDK 的定制化需求。
利用传统的 MVC 框架会撑爆 Controller,给研发和掩护产生很多后患。以是,融云 Global IM UIKit 采取 MVVM 架构,将数据处理与 UI 渲染和交互合理解耦,降落代码逻辑繁芜度,合营灵巧的组件拆分,提高扩展性。
MVVM 也是一个遍及度高、接管度好的框架,开拓者可以快速上手。以会话列表和会话的对外接口类为例展示如下图:
UI 组件
Global IM UIKit 的一个页面对应一个 Module,Module 中包含一个或多个 Component,并管理这些 Component 的整体布局。
例如,全体会话页面是一个 Module,其又可以被划分为三个 Component,分别是:顶部导航栏、底部输入组件和中间的滚动区。
在 UI 组件中,PaaS 做事的难点在于:须要考虑与各种业务场景的适配,通过高可重用的组件和高开放性的接口,尽可能多地支持定制化需求。
以“底层精简+转让自由”为产品设计核心思路,Global IM UIKit 将 IM 会话页面 Module 和各组件 Component 的重写自由给到开拓者,开拓者可灵巧自定义而不影响可用性。
为实现这一点,Global IM UIKit 的会话页面需按业务逻辑拆分成独立功能组件。每个模块可以作为重用单元,有助于解耦,降落系统的繁芜性,同时隔离也有助于提升开拓效率。
在模块中整体采取 MVVM 的设计模式,对付功能较大略的组件依然采取了 MVC 的设计模式。当组件没有繁芜的交互和业务逻辑时,利用 MVC 会更加简洁明了。
多措辞和主题作为全局性的功能,会渗透到每个组件之中。在设计组件时,须要为多措辞和主题的一键切换能力做铺垫。
组件拆分
根据列表页面显示,可以划分为导航条(Header)、列表(List)、工具栏(ToolBar)和会话输入框(Input),如下图示:
组件中会根据功能聚合再次拆分为更细粒度的组件,例如:输入框的 Reference、TextInput、Sticker、Recorder 等子组件。
这样,Module 与 Component 的基本组件关系就整理拆分完毕了。开拓者可以用继续 Module,新增或修正 Component 的方法,实现组件的二级重写能力。
Component 便是 Global IM UIKit 模块化定义中的最小组成单元,也是开拓者可以重写的最小单元。同时,重写的组件可以在上一级组件中,通过 setter 方法,设置为自定义组件。
组件实现
Global IM UIKit 采取 Objective-C 措辞开拓,所有组件基于原生框架实现。
组件中的颜色和图片,利用系统的动态接口,对应该前设置的主题:
/// 根据主题切换颜色
+ (UIColor )colorWithDynamicProvider:(UIColor (^)(UITraitCollection traitCollection))dynamicProvider;
/// 为 Image 注册不同主题对应的图片,根据主题切换
- (void)registerImage:(UIImage )image withConfiguration:(UIImageConfiguration )configuration;
组件中的笔墨是经由本地化后的:
/// 系统确当地化接口
NSLocalizedStringFromTable(key, tbl, comment)
导航条、列表、工具栏、输入框是 Global IM UIKit 中最核心的几大 Component。
导航条
Header 组件是对系统导航条 UINavigationBar 的自定义,通过定义旁边按钮和标题,在 ViewController 加载时赋值给 UINavigationBar。
@interface RCBaseHeaderView : NSObject
/// 左侧按钮
@property (nonatomic, strong) NSMutableArray<RCBarItem > leftItems;
/// 右侧按钮,默认编辑按钮
@property (nonatomic, strong) NSMutableArray<RCBarItem > rightItems;
/// 标题 baseView
@property (nonatomic, strong) RCBarTitleView titleView;
@end
按钮支持标题、图片和自定义视图显示,点击事宜通过 Action 抛出。
@interface RCBarItem : NSObject
/// 名称
@property (nonatomic, copy, nullable) NSString title;
/// 图标
@property (nonatomic, strong, nullable) UIImage image;
/// 事宜回调,如果自定义视图,这里不会有事宜回调
@property (nonatomic, copy, nullable) RCBarItemAction action;
/// 自定义视图
/// 自定义视图的点击事宜也须要自定义,当点击时,不会触发 action 调用
@property (nonatomic, strong, nullable) UIView customView;
/// 标签
@property (nonatomic, assign) RCBarItemTag tag;
@end
标题是自定义视图,支持标题和副标题两个 Label。
@interface RCBarTitleView : UIView
/// 标题 label
@property (nonatomic, strong) UILabel titleLabel;
/// 状态 label
@property (nonatomic, strong) UILabel statusLabel;
@end
列表
在 List 组件中,利用 UITableView 实现列表展示,支持设置占位视图:
@interface RCBaseListView : UIView
// 列表
@property (nonatomic, strong) UITableView tableView;
// 占位页面
@property (nonatomic, strong) UIView emptyView;
@end
无论会话还是,都须要分页加载,UITableView 的 insert 方法非常契合会话列表的动态加载效果,可以流畅的插入下页数据。
在会话列表中,每个会话对应一个 Cell,会话的展示样式基本同等,在列表中利用 RCChatCell 展示。开拓者可以注册自定义 Cell:
@interface RCChatListView : RCBaseListView
...
/// 将会话 Cell 更换为继续 RCChatCell 的自定义 Cell
- (void)replaceChatCellWithClass:(Class)cellClass;
@end
在会话页面中,不同的展示样式差异很大,每个 Cell 都会继续于 RCMessageCell 自定义样式。同样,开拓者也可以注册自定义 Cell:
@interface RCMessageListView : RCBaseListView
...
/// 注册自定义 Cell
/// - Parameters:
/// - cellClass: 自定义 Cell,须要继续 RCBaseMessageCell
/// - messageClass: 体,支持自定义和内置
- (void)registerClass:(Class)cellClass
forMessageClass:(Class)messageClass;
@end
工具栏
工具栏常日用于多选操作,根据业务需求,设置编辑按钮,利用工具栏须要考虑系统的 tabBar。
@interface RCTabBar : UIView
/// 按钮容器,用于支配按钮
@property (nonatomic, strong) UIStackView containerHStackView;
/// 顶部线条
@property (nonatomic, strong) UIView topLineView;
/// 按钮
@property (nonatomic, copy) NSArray<RCBarItem > items;
@end
在会话列表中,编辑栏固定为底部显示,会向上推起会话列表。如果有系统 tabBar,就会将 tabBar 隐蔽,结束选择后,规复 tabBar。在会话页面中,工具栏会直接覆盖输入框。
输入框
Input 是一个功能聚合度比较高的组件,包含了文本输入、多媒体选择、录音、引用、贴纸等子组件。
@interface RCInputBar : UIView
/// 引用 view
@property (nonatomic, readonly) RCReferenceView referenceView;
/// 输入框
@property (nonatomic, readonly) RCInputTextView textView;
/// 贴纸 view
@property (nonatomic, readonly) RCStickerBoardView stickerBoardView;
/// 输入框左边 items,默认为 addItem
@property (nonatomic, copy) NSArray<RCBarItem > leftItems;
/// 输入框右边 items,默认为 recordItem,当输入文本时,变为 sendItem
@property (nonatomic, copy) NSArray<RCBarItem > rightItems;
/// 加号按钮扩展 items,默认为:相册、相机、文件
/// 在每次扩展面板展示前设置生效
@property (nonatomic, strong) NSMutableArray<RCBarItem > addExpandItems;
@end
组件定制化
为了与运用中的社交模块平滑衔接,Global IM UIKit 的 UI 组件须要支持灵巧定制和高重用。开拓者可以通过继续、布局和配置的二级重写和三级自定义办法,定制符合业务场景和体验的功能。
继续
推举开拓者利用继续的办法跳转会话列表和会话页面,在自定义的 ViewController 中,开拓者可以通过重写父方法,实现自定义操作:
@interface TestChatViewController : RCChatViewController
@end
@implementation TestChatViewController
- (void)reloadHeaderView {
[super reloadHeaderView];
// TODO custom
}
@end
也可以继续组件来实现自定义业务逻辑,在页面展示时通过 setter 的方法,设置为自定义的组件:
@interface TestChatHeaderView : RCChatHeaderView
@end
@implementation TestChatHeaderView
- (void)configure:(RCChatModel )model {
[super configure:model];
}
@end
TestChatViewController controller = [[TestChatViewController alloc] initWithChatModel:model];
controller.headerView = [[TestChatHeaderView alloc] init];
重写事宜代理回调,也可以添加自定义操作,如列表的代理事宜:
/// 列表事宜代理
@protocol RCMessageListViewDelegate <NSObject>
...
/// 列表中的 Cell 将要加载
/// - Parameters:
/// - listView: 列表页面
/// - messageModel: 工具
- (void)listView:(RCMessageListView )listView willLoadCell:(UITableViewCell )cell forMessageModel:(RCMessageModel )messageModel;
/// 列表中的 Cell 加载完成
/// - Parameters:
/// - listView: 列表页面
/// - messageModel: 工具
- (void)listView:(RCMessageListView )listView didLoadCell:(UITableViewCell )cell forMessageModel:(RCMessageModel )messageModel;
/// 列表中的 Cell 将要展示
/// - Parameters:
/// - listView: 列表页面
/// - messageModel: 工具
- (void)listView:(RCMessageListView )listView willDisplayCell:(UITableViewCell )cell forMessageModel:(RCMessageModel )messageModel;
/// 更多回调接口,请参考 SDK 接口
...
@end
布局
在各组件中,会利用 StackView 容器承载 View,可以在 StackView 中直接添加更多的 View。
这种办法紧张用在列表的 Cell 中,Cell 利用 Estimate 高度,充分运用 iOS 的 Auto Layout 功能特性,Cell 实际高度会由内容自动撑开。
@interface RCChatCell : RCTableViewCell
/// 会话容器,头像、会话内容
@property (nonatomic, readonly) UIStackView containerStackView;
...
/// 会话内容,在 containerStackView 中
@property (nonatomic, readonly) UIStackView contentStackView;
/// 会话上部分内容,在 containerStackView 中,用户信息、韶光
@property (nonatomic, readonly) UIStackView contentTopStackView;
...
/// 会话下部分内容,在 containerStackView 中,预览、已读
@property (nonatomic, readonly) UIStackView contentBotStackView;
...
@end
开拓者须要熟习 StackView 的根本利用方法,把稳 StackView 的布局方向。
配置
通过配置的办法增编削现有的功能逻辑,紧张适用基于 RCBarItem 的按钮事宜,例如:导航条旁边按钮是可变数组,可以直接修正该数组,并刷新 UI 即可:
@implementation TestChatViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
RCBarItem item = [RCBarItem itemWithTitle:nil image:CustomImage action:^(RCBarItem item) {
// TODO custom
}];
[self.headerView.leftItems addObject:item];
[self reloadHeaderView];
}
@end
会话和列表的展示样式是需求最多的自定义组件,开拓者可以通过 List 供应的注册方法,完备自定义 Cell 的展示样式。
支持 Module 和 Component 的二级重写之外,如文章开头所述,融云 Global IM UIKit 亦对某些通用功能支持一键切换修正。
在支持开拓者环球化业务的多样实践中,融云创造有些通用功能也须要跟随业务实现多样化。例如,单群聊自己的头像和昵称是否展示等。
针对这些功能,开拓者通过重写组件来实现多样化的本钱比较高,融云 Global IM UIKit 直接将此类功能直接封装成开关(与多措辞、主题切换类似),开拓者可根据业务场景一键切换利用。
数据处理除了用户可以直不雅观看到的 UI 组件外,数据流处理也是 Global IM UIKit 中的主要环节。
数据处理的紧张难点在于:须要将繁芜的业务状态打算放入串行行列步队中异步处理。通过节流优化,批量处理吸收或同步已读和回执。
紧张的数据处理有吸收、加载、未读数、已读回执、已读同步等,须要考虑的时序、IO 访问的频率、属性的缓存、UI 和数据逻辑的交互。
数据行列步队
的吸收、发送、加载和操作等都是并发事宜,为了能够将这些事宜有序、安全地通报给 UI 组件显示,须要有一个统一可调度的串行行列步队,确保数据和 UI 的之间的相互映射。
在 ViewModel 中,有专门处理数据的串行行列步队 dataQueue,所有事宜都会放入到串行行列步队中处理,ViewModel 会将处理结果通过回调接口,分发给对应组件刷新。
/// ViewModel 根本类
@interface RCBaseViewModel : NSObject
/// 操作数据的线程,所有对数据的操作必须在该线程中处理
@property (nonatomic, readonly) dispatch_queue_t dataQueue;
#pragma mark - Perform -
- (void)asyncPerformBlock:(dispatch_block_t)block;
- (void)syncPerformBlock:(dispatch_block_t)block;
@end
/// 会话数据回调接口
@protocol RCChatViewModelDelegate <NSObject>
...
/// 列表数据刷新
/// - Parameters:
/// - viewModel: ViewModel
/// - messages: 工具数组
- (void)viewModel:(RCChatViewModel )viewModel
didReloadMessages:(NSArray<RCMessageModel > )messages;
/// 插入列表数据
/// - Parameters:
/// - viewModel: ViewModel
/// - messages: 工具数组
/// - indexSet: 工具数组对应的位置
- (void)viewModel:(RCChatViewModel )viewModel
didInsertMessages:(NSArray<RCMessageModel > )messages
atIndexSet:(NSIndexSet )indexSet;
/// 列表数据更新
/// - Parameters:
/// - viewModel: ViewModel
/// - messages: 工具数组
/// - indexSet: 工具数组对应的位置
- (void)viewModel:(RCChatViewModel )viewModel
didUpdateMessages:(NSArray<RCMessageModel > )messages
atIndexSet:(NSIndexSet )indexSet;
/// 更多回调接口,请参考 SDK 接口
...
@end
数据加载
在列表中,为了流畅地滚动预加载体验,ViewModel 中除了 reload 方法外,也供应了加载上一页和下一页的方法。
随列表滚动,当达到加载下一页的条件时,会调用 loadNextPage 方法加载下一页数据。ViewModel 会通过代理回调的方法,将下一页的数据通报给 List 显示出来。
@interface RCChatListViewModel : RCBaseViewModel
...
/// 加载会话数据
- (void)reloadData;
/// 拉取下一页数据
- (void)loadNextPage;
...
@end
@protocol RCChatListViewModelDelegate <NSObject>
/// 刷新会话列表
/// - Parameter viewModel: ViewModel
/// - Parameter chatModels: 会话数据
- (void)viewModel:(RCChatListViewModel )viewModel chatListDidReload:(NSArray<RCChatModel > )chatModels;
/// 插入会话
/// - Parameter viewModel: ViewModel
/// - Parameter indexSet: 会话 index list
- (void)viewModel:(RCChatListViewModel )viewModel chatListDidInsertAtIndexSet:(NSIndexSet )indexSet;
...
@end
数据节流
虽然数据线程能够确保有序展示到 UI 中,但在吸收时,须要考虑风暴的情形。即,吸收大量直接刷新 UI,会导致 UI 卡顿,乃至崩溃。
因此,须要添加节流操作,在延时一定韶光后,进行批量处理。
- (void)onReceived:(RCMessage )message left:(int)nLeft object:(id)object {
self.cachedMessages[@(message.messageId)] = message;
if (nLeft == 0) {
__weak typeof(self) weakSelf = self;
[self.throttle scheduleThrottleWithBlock:^{
[weakSelf rc_throttleUpdateChatList];
}];
}
}
在吸收和发送时,还会触发已读回执、未读数清理、已读多端同步。已读回执和已读同步在接口调用时,须要通过节流汇总,在一定韶光后统一上报。
__weak typeof(self) weakSelf = self;
[self.readReceiptThrottle scheduleThrottleWithBlock:^{
[weakSelf rc_sendReadReceipt];
}];
__weak typeof(self) weakSelf = self;
[self.readSyncThrottle scheduleThrottleWithBlock:^{
[weakSelf rc_syncReadTimeThrottle];
}];
处理
会话支持置顶、免打扰、标记未读和删除,当 ViewModel 中调用接口后,会触发远端和数据库状态更新,加载到内存的数据也须要掩护更新。UI 的刷新直接依赖于内存数据,内存数据须要确保和本地数据库同等,本地数据库频繁触发 IO 访问。
可以将 ViewModel 中部分数据操作分配到内存中,同时内存根据处理结果和 UI 合营刷新展示。例如:置顶操作,调用设置接口后,在内存中更新状态和重新排序,并将结果反馈给 List 进行 UI 刷新和动画。
@interface RCChatListViewModel : RCBaseViewModel
...
/// 移除数据,根据 models 批量移除对应会话,同时删除会话中的
/// 数据添加完成后,会触发 viewModel:chatListDidRemoveAtIndexSet: 回调
- (void)deleteChatModels:(NSArray<RCChatModel > )models;
/// 标记已读未读
/// 标记完成后,会触发 viewModel:chatListDidUpdateAtIndexSet: 回调
/// - Parameter model: 会话工具
- (void)toggleRead:(RCChatModel )model;
...
/// 设置/取消置顶
- (void)toggleTop:(RCChatModel )model;
...
/// 设置/取消免打扰
- (void)toggleNotification:(RCChatModel )model;
@end
接入指南Global IM UIKit SDK 的核心集中于即时通讯的业务功能界面的实现,基于融云 RongCloudIM 供应的高并发、高可用通讯能力,以适应外洋用户利用习气的交互设计完成封装。开拓者采取 Global IM UIKit SDK,在快速上手文档支持下,“分钟级”接入即可实现单群聊完全功能。
集成准备
创建融云开拓者账号,获取App Key。
开始之前,需创建融云开拓者账号并获取 App Key。在开拓者后台,系统会自动为新账号创建一个运用。默认利用海内数据中央,并供应开拓环境。如果您已经有融云开拓者账号,可以直接创建新运用。
集成 SDK
推举利用 Pod 集成,如需手动集成,请移步官网下载 Framework,将 Framework 直接导入 App 即可。
☑ 在 podfile 中添加如下内容:
pod 'RongCloudGlobal/IMUIKit'
☑ 请在终端中运行以下命令:
pod install
如果涌现找不到干系版本的问题,可先实行 pod repo update,再实行 pod install。
☑ 上一步完成后,CocoaPods 会在您的工程根目录下天生一个 xcworkspace 文件,只需通过 XCode 打开该文件即可加载工程。
实现谈天功能
☑ 初始化
Global IM UIKit SDK 依赖于 IMLibCore 的即时通讯能力,利用前须要对 IMLibCore 进行初始化。IMLibCore 核心类为 RCCoreClient,初始化时,须要传入生产或开拓环境的 App Key。
#import <RongIMLibCore/RongIMLibCore.h>
NSString appKey = @"Your_AppKey"; // example: bos9p5rlcm2ba
RCInitOption initOption = nil;
[[RCCoreClient sharedCoreClient] initWithAppKey:appKey option:initOption];
☑ 连接
用户 Token 是与用户 ID 对应的身份验证令牌,是运用程序的用户在融云的唯一身份标识。运用客户端在利用融云即时通讯功能前必须与融云建立 IM 连接,连接时必须传入 Token。
用户可以在开拓者后台快速创建一个用户 John Doe,创建后返回 Token,利用该 Token 连接。
[[RCCoreClient sharedCoreClient] connectWithToken:@"John Doe token" dbOpened:^(RCDBErrorCode code) {
//数据库打开,可以进入到主页面
} success:^(NSString userId) {
//连接成功
} error:^(RCConnectErrorCode errorCode) {
if (status == RC_CONN_TOKEN_INCORRECT) {
//从 APP 做事获取新 token,并重连
} else {
//无法连接到 IM 做事器,请根据相应的缺点码作出对应处理
}
}];
☑ 用户信息
要在 Global IM UIKit 上展示用户、群组的头像、名称等,须要运用层(App)主动向 IMKit SDK 供应用户信息。为了完全部验 Global IM UIKit 的 UI,我们将直接在用户信息数据库中写入对运用户 ID 的头像、名称信息。
下面设置了本地登任命户 John Doe 和另一个用户 Jane Smith 的头像、昵称。
[RCIMKitClient shared].enableUserInfoPersistence = YES;
RCUserInfo currentUserJohnDoe = [[RCUserInfo alloc] initWithUserId:@"userIdJohnDoe" name:@"John Doe" portrait:userPortraitUri];
[RCIMKitClient shared].currentUserInfo = currentUserJohnDoe;
RCUserInfo JaneSmith = [[RCUserInfo alloc] initWithUserId:@"userIdJaneSmith" name:@"Jane Smith" portrait:@"http://portrait"];
[[RCIMKitClient shared] refreshUserInfoCache:JaneSmith];
☑ 会话列表
Global IM UIKit 已默认供应会话列表页面和会话页面。会话列表页面展示当前用户参与的所有单聊、群聊、系统会话,在会话页面可进行编辑、查看、回答、发送等活动。
连接成功后即可跳转到默认会话列表界面。推举继续 SDK 中的 RCChatListViewController,例如 TestChatListViewController 类,示例如下:
@interface TestChatListViewController : RCChatListViewController
@end
初始化自定义的会话列表页面 TestChatListViewController,会话列表支持显示单聊、群聊、系统会话。会话列表详细利用方法,拜会会话列表页面。
TestChatListViewController listVC = [[TestChatListViewController alloc] init];
[self.navigationController pushViewController:listVC animated:YES];
☑ 会话
为了快速体验,可以从开拓者后台【北极星】开拓者工具箱【IM Server API 调试】页面仿照用户 Jane Smith 向当前登录的用户 John 发送一条文本。
只要供应对方的 userId,融云就可支持跟对方发起谈天。运用客户端可以通过用户 ID、群聊会话 ID 吸收。
以上便是 Global IM UIKit SDK 的全部集成步骤,“分钟级”接入即可感想熏染最具灵巧性的 IM UI 技能架构,以及与外洋用户利用体验对齐的交互设计。