在实际业务开发过程中,互动白板 (ZegoWhiteboardView SDK)经常与文件共享 (ZegoDocsView SDK) 搭配使用。通常情况下,ZegoWhiteboardView 会覆盖在 ZegoDocsView 之上,然后通过一定的逻辑处理,就能实现在文件上绘制图元的业务需求。
已在项目中集成 ZegoExpress-Video SDK,实现基本的实时音视频功能,详情请参考实时音视频的 快速开始 - 集成。
已在项目中集成 ZegoWhiteboardView SDK,详情请参考 快速开始 - 集成。
已在项目中集成 ZegoDocsView SDK,详情请参考文件共享的 快速开始 - 集成。
已在 ZEGO 控制台 创建项目,并申请有效的 AppID 和 AppSign,详情请参考 控制台 - 项目管理。
2.3.0 或以上
版本的 SDK 支持 Token 鉴权,若您需要升级鉴权方式,可参考 如何从 AppSign 鉴权升级为 Token 鉴权。
开发者在工程中引入头文件。
// 引入 ZegoExpress-Video SDK 头文件
#import <ZegoExpressEngine/ZegoExpressEngine.h>
// 引入 ZegoDocsView SDK 头文件
#import <ZegoDocsView/ZegoDocsView.h>
// 引入 ZegoWhiteboardView SDK 头文件
#import <ZegoWhiteboardView/ZegoWhiteboardView.h
初始化 ZegoExpress-Video SDK,详情可参考实时音视频的 快速开始 - 实现流程。
/**
* appID:ZEGO 为开发者签发的应用 ID,请从 ZEGO 控制台 https://console-express.
* zego.im 申请,取值范围为 0~4294967295。
* appSign: ZEGO 为开发者签发的应用 appSign,请从 ZEGO 控制台 https://console-express. zego.im 申请
* scenario: 所属的应用场景,ZegoScenarioDefault为通用场景
*/
ZegoEngineProfile *profile = [[ZegoEngineProfile alloc] init];
profile.appID = <#appID#>;
profile.appSign = <#appSign#>;
profile.scenario = ZegoScenarioDefault;
// 创建引擎,并注册 self 为 eventHandler 回调。不需要注册回调的话,eventHandler 参数可以传 nil,后续可调用 "-setEventHandler:" 方法设置回调
[ZegoExpressEngine createEngineWithProfile:profile eventHandler:self];
如果您需要切换鉴权方式,请参考 如何从 AppSign 鉴权升级为 Token 鉴权。
如果回调 completeBlock 中的 “errorCode” 为 0,代表初始化成功,可进行更多操作。errorCode 可参考 常见错误码。
[[ZegoWhiteboardManager sharedInstance] initWithCompleteBlock:^(ZegoWhiteboardViewError errorCode) {
//errorCode = 0 代表初始化成功
}];
设置 ZegoWhiteboardManager 的代理,监听常用回调事件:[[ZegoWhiteboardManager sharedInstance] setDelegate:self]
。
建议关注以下事件:
//当房间内其他用户创建新的ZegoWhiteboardView的时,会收到新增ZegoWhiteboardView的回调
//用户可以将新增的 whiteboardView 添加到视图中
- (void)onWhiteboardAdd:(ZegoWhiteboardView *)whiteboardView;
//当房间内其他用户删除 ZegoWhiteboardView 时,会收到 ZegoWhiteboardView 移除的回调
//用户可以根据 whiteboardID 移除相应的 ZegoWhiteboardView
- (void)onWhiteboardRemoved:(ZegoWhiteboardID)whiteboard
使用 ZegoDocsViewManager 的 initWithConfig 接口初始化 ZegoDocsView SDK。
ZegoDocsViewConfig *config = [ZegoDocsViewConfig new];
config.appID = appID;//即构 AppID
config.appSign = appSign;// ZEGO 为开发者签发的应用 appSign,请从 ZEGO 控制台申请
config.dataFolder = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents/ZegoDocs"] stringByAppendingString:@"data"];//SDK 内部数据保存目录
config.cacheFolder = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents/ZegoDocs"] stringByAppendingString:@"doc"];//SDK 缓存目录
config.logFolder = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents/ZegoDocs"] stringByAppendingString:@"log"];//SDK 日志保存目录
[[ZegoDocsViewManager sharedInstance] initWithConfig:config completionBlock:^(ZegoDocsViewError errorCode) {
if (errorCode == ZegoDocsViewSuccess) {
NSLog(@"初始化 SDK 成功");
} else {
NSLog(@"初始化 SDK 失败 %ld",errorCode);
}
}];
调用 ZegoExpressEngine 的 loginRoom 接口登录房间。
错误码详情请参考 登录房间错误码。
- (void)loginRoom {
// roomID 由您本地生成,需保证 “roomID” 全局唯一。不同用户要登录同一个房间才能进行通话
NSString *roomID = @"room1";
// 创建用户对象,ZegoUser 的构造方法 userWithUserID 会将 “userName” 设为与传的参数 “userID” 一样。“userID” 与 “userName” 不能为 “nil”,否则会导致登录房间失败。
// userID 由您本地生成,需保证 “userID” 全局唯一。
ZegoUser *user = [ZegoUser userWithUserID:@"user1"];
// 只有传入 “isUserStatusNotify” 参数取值为 “true” 的 ZegoRoomConfig,才能收到 onRoomUserUpdate 回调。
ZegoRoomConfig *roomConfig = [[ZegoRoomConfig alloc] init];
roomConfig.isUserStatusNotify = YES;
// 登录房间
[[ZegoExpressEngine sharedEngine] loginRoom:roomID user:user config:roomConfig callback:^(int errorCode, NSDictionary * _Nullable extendedData) {
// (可选回调) 登录房间结果,如果仅关注登录结果,关注此回调即可
if (errorCode == 0) {
NSLog(@"房间登录成功");
} else {
// 登录失败,请参考 errorCode 说明 https://doc-zh.zego.im/article/4377
NSLog(@"房间登录失败");
}
}];
}
// 1. init
self.docView = [[ZegoDocsView alloc] init]; //初始化文件View
self.docView.backgroundColor = UIColor.whiteColor; //设置文件View背景颜色
[self.docView setDelegate:self]; //设置文件View 代理通知
[self.view addSubview:self.docView]; //添加到自己需要展示的View 上
// 2.设置 frame
self.docView.frame = CGRectMake(0, 0, 500, 500);
调用 ZegoDocsView 的 loadFileWithFileID 接口,传入上传文件 uploadFile 成功后获取的 fileID 加载文件。
如果遇到加载失败的情况,请参考 常见错误码。常见的原因如下:
// fileID 在 uploadFile 成功之后获取
NSString *fileID = @"";
[docsView loadFileWithFileID:fileID //后台获取到文件ID
authKey:@"" //鉴权key。可以为空。根据你所申请环境是否需要鉴权填入。最好是通过后台获取
completionBlock:^(ZegoDocsViewError errorCode) {
//创建白板模型
ZegoWhiteboardViewModel *model = [[ZegoWhiteboardViewModel alloc] init];
// 当前示例为 创建 宽高比为 16 : 9,横向页数为 5 页的白板
model.aspectWidth = docsView.contentSize.width;
model.aspectHeight = docsView.contentSize.height;
model.pageCount = docsView.pageCount;
model.roomID = @"123456"; //传入房间ID,便于接收端判断,需要保证 roomID 和登录时的 roomID 一致
model.name = @"白板";
model.fileInfo.fileName = docsView.fileName;
model.fileInfo.fileID = docsView.fileID;
model.fileInfo.fileType = docsView.fileType;
[[ZegoWhiteboardManager sharedInstance] createWhiteboardView:model completeBlock:^(ZegoWhiteboardViewError errorCode, ZegoWhiteboardView *whiteboardView) {
//将 ZegoWhiteboardView 添加到界面中
whiteboardView.frame = CGRectMake(0, 0, 300, 300);
//通常将白板覆盖在 docsView 之上,这样就能实现在文件上绘制图元的功能
[self.view addSubView: whiteboardView];
}];
}];
在文件共享和互动白板白板搭配使用时,开发者需要在文件加载成功后,通过如下步骤创建和展示 ZegoWhiteboardView。
通常开发者会将白板覆盖在 ZegoDocsView 之上,这样就能实现在文件上绘制图元的功能。
先调用 ZegoDocsView 的翻页接口 flipPage,再调用 ZegoWhiteboardView 的滚动接口 scrollToHorizontalPercent。如果接口调用 errorCode 为 0 代表滚动成功,非 0 代表滚动同步失败,此时需要再次同步 ZegoDocsView 到正确的页数。
[self.currentDocsView flipPage:page step:step completionBlock:^(BOOL isScrollSuccess) {
float pageNum = (float)MAX((weakSelf.currentDocsView.currentPage - 1), 0);
[weakSelf.currentWhiteboardView scrollToHorizontalPercent:0 verticalPercent: pageNum/ (float)weakSelf.currentDocsView.pageCount pptStep:weakSelf.currentDocsView.currentStep completionBlock:^(ZegoWhiteboardViewError error_code, float horizontalPercent, float verticalPercent, unsigned int pptStep) {
if (error_code != 0) {
//白板滚动同步失败
NSInteger pageNumFianel = round(verticalPercent * weakSelf.docsView.pageCount) + 1;
if (pageNumFianel == weakSelf.docsView.currentPage && pptStep == self.docsView.currentStep) {
return;
}
[weakSelf.docsView flipPage:pageNumFianel step:pptStep completionBlock:^(BOOL isScrollSuccess) {
}];
}
}];
}];
监听到 ZegoWhiteboardView 的 onScrollWithHorizontalPercent 回调时,同步文件到对应的滚动百分比(动态 PPT 或者 H5 课件同步到对应的页码)。
if (self.currentWhiteboardView.whiteboardModel.fileInfo.fileType == ZegoDocsViewFileTypeDynamicPPTH5
|| self.currentWhiteboardView.whiteboardModel.fileInfo.fileType == ZegoDocsViewFileTypeCustomH5) {
//动态ppt或者H5课件
CGFloat yPercent = self.currentWhiteboardView.contentOffset.y / self.currentWhiteboardView.contentSize.height;
NSInteger pageNo = round(yPercent * self.currentDocsView.pageCount) + 1;
//同步文件视图内容
[self.currentDocsView flipPage:pageNo step:MAX(self.currentWhiteboardView.whiteboardModel.pptStep, 1) completionBlock:^(BOOL isScrollSuccess) {
}];
} else {
//普通静态文件
[self.currentDocsView scrollTo:verticalPercent completionBlock:^(BOOL isScrollSuccess) {
}];
}
}
通过调用 ZegoWhiteboardView 的 destroyWhiteboardID 接口可以销毁指定的白板,在收到成功回调 onRemoteWhiteboardRemoved 后再移除对应的 ZegoDocsView。
__weak typeof(self) weakSelf = self;
[[ZegoWhiteboardManager sharedInstance]destroyWhiteboardID:whiteboardID completeBlock:^(ZegoWhiteboardViewError errorCode, ZegoWhiteboardID whiteboardID) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if ([strongSelf.delegate respondsToSelector:@selector(onRemoteWhiteboardRemoved:)]) {
[strongSelf.delegate onRemoteWhiteboardRemoved:whiteboardID];
}
}];
//接收远端白板移除消息
//whiteboardViewArray 存放生成的白板的数组
- (void)onRemoteWhiteboardRemoved:(ZegoWhiteboardID)whiteboardID {
for (int i = 0; i < self.whiteboardViewArray.count; i++) {
//找到对应的需要销毁的白板
ZegoWhiteboardView *whiteboardView = self.whiteboardViewArray[i];
if (whiteboardID == whiteboardView.whiteboardModel.whiteboardID) {
[self onRemoveWhiteboardIdnex:i];
break;
}
}
}
- (void)removeWhiteboardWithID:(ZegoWhiteboardID)whiteboardID {
ZegoWhiteboardView *removeView = [self fetchWhiteboardView:whiteboardID];//找到对应的白板view
[self.whiteboardViewArray removeObject:removeView];//从白板列表中移除对应的白板视图
if ([self.currentWhiteboardView isEqual:removeView]) {
[removeView removeFromSuperview];
self.currentWhiteboardView.whiteboardViewDelegate = nil;
self.currentDocsView.delegate = nil;
self.currentWhiteboardView = nil;
self.currentDocsView = nil;
}
for (ZegoDocsView *docsView in self.docsViewArray) {
//找销毁白板对应的文件view,将其移除
if (docsView.fileID == removeView.whiteboardModel.fileInfo.fileID) {
[docsView removeFromSuperview];
[self.docsViewArray removeObject:docsView];
break;
}
}
}
如果 ZegoDocsView 的大小发生了变更(比如横竖屏切换),需要重新设置 ZegoWhiteboardView 的可见区域。
docsView.frame = newFrame;
[docsView layoutIfNeeded]; //更新 visibleSize
CGSize visibleSize = docsView.visibleSize;
whiteboardView.frame = [self frameWithSize:visibleSize docsViewFrame:docsViewFrame];
whiteboardView.contentSize = docsView.contentSize;
// 根据 docsView 的 frame 计算白板的 frame
- (CGRect)frameWithSize:(CGSize)visibleSize docsViewFrame:(CGRect)frame {
CGFloat x = frame.origin.x + (frame.size.width - visibleSize.width) / 2;
CGFloat y = frame.origin.y + (frame.size.height - visibleSize.height) / 2;
return CGRectMake(x, y, visibleSize.width, visibleSize.height);
}
监听 Token 过期通知,如果 Token 过期,需要主动更新 Token。
- (void)onRoomTokenWillExpire:(int)remainTimeInSecond roomID:(NSString *)roomID {
NSString *token = [MyToken getToken]; // 重新请求开发者服务端获取 Token
[[ZegoExpressEngine sharedEngine] renewToken:token roomID:roomID];
[[ZegoDocsViewManager sharedInstance] renewToken:token];
}
以上介绍了互动白板和文件共享搭配使用的基本场景,更复杂的交互场景请查看完整的示例源码,详情请参考 下载示例源码。
联系我们
文档反馈