位置消息
IMKit 提供了位置插件,实现了发送位置消息、位置缩略图功能。本文还描述了如何自行实现了应用内位置共享。
- SDK 默认发送的消息包含位置消息内容对象 [RCLocationMessage](类型标识:
RC:LBSMsg
)。 - 实时位置共享也是基于消息实现的。SDK 默认使用类型标识为
RC:RL
、RC:RLStart
、RC:RLJoin
、RC:RLQuit
的消息。
局限
- IMKit 的位置插件基于 MapKit。如需使用其他地图服务,您可以自定义插件,自行构造位置消息并发送。添加自定义插件的方法详见输入区域。
- 位置插件默认仅支持点击发送位置。
用法
IMKit 从 5.2.3 及之后开始支持 LocationKit
插件。如果从低于 5.2.3 的 IMKit 版本升级,请参见下文升级旧版位置插件。
集成位置插件
请根据 IMKit 的导入方式,选择集成 LocationKit
插件的方式。
-
使用 CocoaPods
Framework(要求 SDK >= 5.2.3)
pod 'RongCloudIM/LocationKit',' x.y.z' #位置插件
源码(要求 IMKit 同为源码,要求 SDK >= 5.2.3)
pod 'RongCloudOpenSource/LocationKit', 'x.y.z' #位置插件
-
手动集成。如果项目中 IMKit 源码为手动导入,请通过融云官网 SDK 下载页面下载
LocationKit
插件。由于 IMKit 的LocationKit
插件依赖 IMLib SDK 提供的Location
库,您还需要额外添加Location
库。您可以选择通过 CocoaPods 导入Location
Framework,或使用下载包中的RongLocation.xcframework
文件。pod 'RongCloudIM/Location',' x.y.z' # IMLib SDK 位置基础库
x.y.z 代表具体版本,请通过融云官网 SDK 下载页面或 CocoaPods 仓库等方式查询最新版本。
发送位置消息
位置插件集成完毕后,在扩展面板里会自动生成位置消息入口。用户点击输入栏右侧 +
号按钮可展开扩展面板,点击位置图标,即可发送位置消息。
集成位置插件后,默认仅支持点击发送位置。
使用实时位置共享
IMKit SDK 没有提供实时位置共享功能插件。您可以参考融云示例应用 SealTalk 中的代码,在您应用中实现实时位置共享。
图中参与位置实时共享人员的用户头像、昵称需要 App 提供给 IMKit,具体方法参考设置用户信息。
下面以 SealTalk 示例应用开源代码为例,说明在单聊会话中实现位置共享的步骤。操作完成后,就可以在单聊会话中使用位置实时共享功能。您也可以参考在 SealTalk 的 RCDChatViewController.m。
-
下载 SealTalk 项目源码,将源码中的
Sections/Chat/RealTimeLocation
导入工程。https://github.com/rongcloud/sealtalk-ios
-
在会话页面中导入下面头文件。
#import <objc/runtime.h>
#import "RealTimeLocationEndCell.h"
#import "RealTimeLocationStartCell.h"
#import "RealTimeLocationStatusView.h"
#import "RealTimeLocationViewController.h"
#import "RealTimeLocationDefine.h"
static const char *kRealTimeLocationKey = "kRealTimeLocationKey";
static const char *kRealTimeLocationStatusViewKey = "kRealTimeLocationStatusViewKey"; -
设置相关属性。
@interface RCDChatViewController () < RCRealTimeLocationObserver,
RealTimeLocationStatusViewDelegate>
@property(nonatomic, weak) id<RCRealTimeLocationProxy> realTimeLocation;
@property(nonatomic, strong) RealTimeLocationStatusView *realTimeLocationStatusView;
@end -
获取位置共享服务并注册消息。
- (void)viewDidLoad{
[super viewDidLoad];
[self registerRealTimeLocationCell];
[self getRealTimeLocationProxy];
}- (void)registerRealTimeLocationCell {
[self initRealTimeLocationStatusView];
[self registerClass:[RealTimeLocationStartCell class] forMessageClass:[RCRealTimeLocationStartMessage class]];
[self registerClass:[RealTimeLocationEndCell class] forMessageClass:[RCRealTimeLocationEndMessage class]];
}
- (void)getRealTimeLocationProxy {
__weak typeof(self) weakSelf = self;
[[RCRealTimeLocationManager sharedManager] getRealTimeLocationProxy:self.conversationType
targetId:self.targetId
success:^(id<RCRealTimeLocationProxy> realTimeLocation){
weakSelf.realTimeLocation = realTimeLocation;
[weakSelf.realTimeLocation addRealTimeLocationObserver:weakSelf];
[weakSelf updateRealTimeLocationStatus];
} error:^(RCRealTimeLocationErrorCode status) {
NSLog(@"get location share failure with code %d",(int)status);
}];
}
- (void)initRealTimeLocationStatusView {
self.realTimeLocationStatusView =
[[RealTimeLocationStatusView alloc] initWithFrame:CGRectMake(0, 62, self.view.frame.size.width, 0)];
self.realTimeLocationStatusView.delegate = self;
[self.view addSubview:self.realTimeLocationStatusView];
} -
点击位置时弹出位置实时共享选项。
//RCDChatViewController Class
- (void)pluginBoardView:(RCPluginBoardView *)pluginBoardView clickedItemWithTag:(NSInteger)tag {
switch (tag) {
case PLUGIN_BOARD_ITEM_LOCATION_TAG: {
if (self.realTimeLocation) {
[RCActionSheetView showActionSheetView:nil cellArray:@[RTLLocalizedString(@"send_location"), RTLLocalizedString(@"location_share")]
cancelTitle:RTLLocalizedString(@"cancel")
selectedBlock:^(NSInteger index) {
if (index == 0) {
[super pluginBoardView:self.chatSessionInputBarControl.pluginBoardView
clickedItemWithTag:PLUGIN_BOARD_ITEM_LOCATION_TAG];
}else{
[self showRealTimeLocationViewController];
}
} cancelBlock:^{
}];
} else {
[super pluginBoardView:pluginBoardView clickedItemWithTag:tag];
}
}break;
default:
[super pluginBoardView:pluginBoardView clickedItemWithTag:tag];
break;
}
} -
实时位置共享监听代理方法。
- (void)onRealTimeLocationStatusChange:(RCRealTimeLocationStatus)status {
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf updateRealTimeLocationStatus];
});
}
- (void)onReceiveLocation:(CLLocation *)location type:(RCRealTimeLocationType)type fromUserId:(NSString *)userId {
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf updateRealTimeLocationStatus];
});
}
- (void)onParticipantsJoin:(NSString *)userId {
__weak typeof(self) weakSelf = self;
if ([userId isEqualToString:[RCCoreClient sharedCoreClient].currentUserInfo.userId]) {
[self notifyParticipantChange:RTLLocalizedString(@"you_join_location_share")];
} else {
[[RCIM sharedRCIM]
.userInfoDataSource
getUserInfoWithUserId:userId
completion:^(RCUserInfo *userInfo) {
if (userInfo.name.length) {
[weakSelf notifyParticipantChange:[NSString stringWithFormat:RTLLocalizedString(@"someone_join_share_location"),userInfo.name]];
} else {
[weakSelf notifyParticipantChange:[NSString stringWithFormat:RTLLocalizedString(@"user_join_share_location"),userId]];
}
}];
}
}
- (void)onParticipantsQuit:(NSString *)userId {
__weak typeof(self) weakSelf = self;
if ([userId isEqualToString:[RCCoreClient sharedCoreClient].currentUserInfo.userId]) {
[self notifyParticipantChange:RTLLocalizedString(@"you_quit_location_share")];
} else {
[[RCIM sharedRCIM]
.userInfoDataSource
getUserInfoWithUserId:userId
completion:^(RCUserInfo *userInfo) {
if (userInfo.name.length) {
[weakSelf
notifyParticipantChange:[NSString stringWithFormat:RTLLocalizedString(@"someone_quit_location_share"),userInfo.name]];
} else {
[weakSelf
notifyParticipantChange:[NSString stringWithFormat:RTLLocalizedString(@"user_quit_location_share"),userId]];
}
}];
}
}
- (void)onRealTimeLocationStartFailed:(long)messageId {
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = 0; i < self.conversationDataRepository.count; i++) {
RCMessageModel *model = [self.conversationDataRepository objectAtIndex:i];
if (model.messageId == messageId) {
model.sentStatus = SentStatus_FAILED;
}
}
NSArray *visibleItem = [self.conversationMessageCollectionView indexPathsForVisibleItems];
for (int i = 0; i < visibleItem.count; i++) {
NSIndexPath *indexPath = visibleItem[i];
RCMessageModel *model = [self.conversationDataRepository objectAtIndex:indexPath.row];
if (model.messageId == messageId) {
[self.conversationMessageCollectionView reloadItemsAtIndexPaths:@[ indexPath ]];
}
}
});
}
- (void)notifyParticipantChange:(NSString *)text {
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.realTimeLocationStatusView updateText:text];
[weakSelf performSelector:@selector(updateRealTimeLocationStatus) withObject:nil afterDelay:0.5];
});
}
- (void)onFailUpdateLocation:(NSString *)description {
}
#pragma mark - 实时位置共享状态 view 代理方法
- (void)onJoin {
[self showRealTimeLocationViewController];
}
- (RCRealTimeLocationStatus)getStatus {
return [self.realTimeLocation getStatus];
}
- (void)onShowRealTimeLocationView {
[self showRealTimeLocationViewController];
}
- (void)setRealTimeLocation:(id<RCRealTimeLocationProxy>)realTimeLocation{
objc_setAssociatedObject(self, kRealTimeLocationKey, realTimeLocation, OBJC_ASSOCIATION_ASSIGN);
}
- (id<RCRealTimeLocationProxy>)realTimeLocation {
return objc_getAssociatedObject(self, kRealTimeLocationKey);
}
- (void)setRealTimeLocationStatusView:(RealTimeLocationStatusView *)realTimeLocationStatusView {
objc_setAssociatedObject(self, kRealTimeLocationStatusViewKey, realTimeLocationStatusView,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (RealTimeLocationStatusView *)realTimeLocationStatusView {
return objc_getAssociatedObject(self, kRealTimeLocationStatusViewKey);
}
//弹出实时位置共享页面
- (void)showRealTimeLocationViewController {
RealTimeLocationViewController *lsvc = [[RealTimeLocationViewController alloc] init];
lsvc.realTimeLocationProxy = self.realTimeLocation;
if ([self.realTimeLocation getStatus] == RC_REAL_TIME_LOCATION_STATUS_INCOMING) {
[self.realTimeLocation joinRealTimeLocation];
} else if ([self.realTimeLocation getStatus] == RC_REAL_TIME_LOCATION_STATUS_IDLE) {
[self.realTimeLocation startRealTimeLocation];
}
lsvc.modalPresentationStyle = UIModalPresentationFullScreen;
[self.navigationController presentViewController:lsvc
animated:YES
completion:^{
}];
}
//更新实时位置共享状态
- (void)updateRealTimeLocationStatus {
if (self.realTimeLocation) {
[self.realTimeLocationStatusView updateRealTimeLocationStatus];
__weak typeof(self) weakSelf = self;
NSArray *participants = nil;
switch ([self.realTimeLocation getStatus]) {
case RC_REAL_TIME_LOCATION_STATUS_OUTGOING:
[self.realTimeLocationStatusView updateText:RTLLocalizedString(@"you_location_sharing")];
break;
case RC_REAL_TIME_LOCATION_STATUS_CONNECTED:
case RC_REAL_TIME_LOCATION_STATUS_INCOMING:
participants = [self.realTimeLocation getParticipants];
if (participants.count == 1) {
NSString *userId = participants[0];
[weakSelf.realTimeLocationStatusView
updateText:[NSString stringWithFormat:RTLLocalizedString(@"user_location_sharing"), userId]];
[[RCIM sharedRCIM]
.userInfoDataSource
getUserInfoWithUserId:userId
completion:^(RCUserInfo *userInfo) {
if (userInfo.name.length) {
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.realTimeLocationStatusView updateText:[NSString stringWithFormat:RTLLocalizedString(@"someone_location_sharing"),userInfo.name]];
});
}
}];
} else {
if (participants.count < 1)
[self.realTimeLocationStatusView removeFromSuperview];
else
[self.realTimeLocationStatusView
updateText:[NSString stringWithFormat:@"%d人正在共享地理位置", (int)participants.count]];
}
break;
default:
break;
}
}
} -
(可选)在
IMLibCore
的RCConfig.plist
文件中,添加如下配置,开启群组会话的实时位置共享功能。群聊最多支持5人同时位置共享。<key>RealTimeLocationShare</key>
<dict>
<key>SupportConversationTypes</key>
<array>
<string>1</string>
<string>3</string>
</array>
</dict>
升级旧版位置插件
IMKit 5.2.3 之前版本升级到 5.2.3 时,旧版位置插件即失效。
- 5.2.3 之前,IMKit 内置了位置插件。
- 5.2.3 及之后,IMKit 不再内置位置插件。不需要位置功能时,可不必集成位置插件(LocationKit),避免 App 上架因获取位置权限需要审核的问题。
- IMKit 5.2.3 之前版本升级到 5.2.3 时 ,原发送位置功能即失效。
定制化
调整位置缩略图压缩比例
从客户端发送位置消息时,SDK 会自动生成预览地图图片,自动上传到文件服务器(默认上传到七牛云存储),并将云存储服务返回的图片的远程地址放入消息体对应字段后进行发送。SDK 会以宽度不超过 408 像素、高度不超过 240 进行压缩。
一般情况下不建议修改 SDK 默认压缩配置。如需调整 SDK 压缩质量,详见知识库文档如何修改 SDK 默认的图片与视频压缩配置。
自定义位置消息的 UI
位置消息(非实时位置共享消息)使用 RCLocationMessageCell
展示在消息列表中。如果需要调整内置消息样式,建议自定义消息 Cell,并将该自定义 Cell 提供给 SDK。IMKit 中所有消息模板都继承自 RCMessageCell
,自定义消息 Cell 也需要继承 RCMessageCell
。详见修改消息的展示样式。
您也可以直接替换 RongCloud.bundle
中文件消息展示模板中引用的样式资源、字符串资源和图标资源。详见 IMKit 源码 RCLocationMessageCell.m 中引用的资源。
自定义位置选取页 UI
您可以通过设置 IMKit 全局导航按钮颜色来调整左上、右上角按钮:
RCKitConfigCenter.ui.globalNavigationBarTintColor = [UIColor whiteColor];