Location Messages
IMKit provides a location plugin that enables sending location messages and displaying location thumbnails. This document also describes how to implement in-app location sharing.
- The SDK sends location messages containing a [RCLocationMessage] object by default (message type identifier:
RC:LBSMsg
). - Real-time location sharing is also message-based. The SDK uses message type identifiers
RC:RL
,RC:RLStart
,RC:RLJoin
, andRC:RLQuit
by default.
Limitations
- IMKit's location plugin is based on MapKit. To use other map services, you can customize the plugin, construct location messages manually, and send them. For details on adding custom plugins, see Input Area.
- The location plugin supports tap-to-send by default.
Usage
IMKit supports the LocationKit
plugin starting from version 5.2.3. If upgrading from an IMKit version below 5.2.3, refer to Upgrading the Legacy Location Plugin.
Integrating the Location Plugin
Choose the integration method for the LocationKit
plugin based on how IMKit is imported.
-
Using CocoaPods
Framework (requires SDK ≥ 5.2.3)
pod 'RongCloudIM/LocationKit',' x.y.z' # Location plugin
Source code (requires IMKit also as source code, SDK ≥ 5.2.3)
pod 'RongCloudOpenSource/LocationKit', 'x.y.z' # Location plugin
-
Manual integration. If IMKit source code is manually imported into your project, download the
LocationKit
plugin from the [RC SDK Download Page]. Since IMKit'sLocationKit
plugin depends on theLocation
library provided by IMLib SDK, you also need to add theLocation
library. You can choose to import theLocation
Framework via CocoaPods or use theRongLocation.xcframework
file from the download package.pod 'RongCloudIM/Location',' x.y.z' # IMLib SDK Location base library
x.y.z represents the specific version. Check the latest version on the [RC SDK Download Page] or CocoaPods repository.
Sending Location Messages
After integrating the location plugin, a location message entry will automatically appear in the extension panel. Users can tap the +
button on the right side of the input bar to expand the panel and tap the location icon to send a location message.
By default, the location plugin only supports tap-to-send.
Using Real-Time Location Sharing
IMKit SDK does not provide a real-time location sharing plugin. You can refer to the code in the RC sample app SealTalk to implement real-time location sharing in your app.
User avatars and nicknames for participants in real-time location sharing must be provided by your app to IMKit. For details, see Setting User Information.
The following steps demonstrate how to implement location sharing in a one-to-one chat session using the SealTalk sample app's open-source code. After completing these steps, real-time location sharing will be available in one-to-one chats. You can also refer to [RCDChatViewController.m] in SealTalk.
-
Download the SealTalk project source code and import the
Sections/Chat/RealTimeLocation
folder into your project.https://github.com/rongcloud/sealtalk-ios
-
Import the following headers in the chat view controller.
#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"; -
Set up relevant properties.
@interface RCDChatViewController () < RCRealTimeLocationObserver,
RealTimeLocationStatusViewDelegate>
@property(nonatomic, weak) id<RCRealTimeLocationProxy> realTimeLocation;
@property(nonatomic, strong) RealTimeLocationStatusView *realTimeLocationStatusView;
@end -
Get the location sharing service and register messages.
- (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];
} -
Show real-time location sharing options when the location icon is tapped.
//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;
}
} -
Real-time location sharing delegate methods.
- (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 - Real-time location sharing status view delegate methods
- (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);
}
// Show the real-time location sharing view
- (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:^{
}];
}
// Update real-time location sharing status
- (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 people sharing locations", (int)participants.count]];
}
break;
default:
break;
}
}
} -
(Optional) Add the following configuration to the
RCConfig.plist
file inIMLibCore
to enable real-time location sharing in group chats. Group chats support up to 5 participants simultaneously.<key>RealTimeLocationShare</key>
<dict>
<key>SupportConversationTypes</key>
<array>
<string>1</string>
<string>3</string>
</array>
</dict>
Upgrading the Legacy Location Plugin
When upgrading from IMKit versions below 5.2.3 to 5.2.3, the legacy location plugin will no longer function.
- Before 5.2.3, IMKit included a built-in location plugin.
- Starting with 5.2.3, IMKit no longer includes the location plugin. If location functionality is not needed, you can avoid integrating the LocationKit plugin to prevent App Store review issues related to location permissions.
- When upgrading from IMKit versions below 5.2.3 to 5.2.3, the original location-sending functionality will cease to work.
Customization
Adjusting Location Thumbnail Compression
When sending location messages from the client, the SDK automatically generates a preview map image, uploads it to the file server (default: Qiniu Cloud Storage), and includes the remote URL of the image in the message body before sending. The SDK compresses the image to a maximum width of 408 pixels and height of 240 pixels.
Generally, it is not recommended to modify the SDK's default compression settings. To adjust compression quality, refer to the knowledge base article How to Modify SDK Default Image and Video Compression Settings.
Customizing Location Message UI
Location messages (non-real-time location sharing messages) are displayed in the message list using RCLocationMessageCell
. To customize the built-in message style, create a custom message Cell and provide it to the SDK. All message templates in IMKit inherit from RCMessageCell
, so custom message Cells must also inherit from RCMessageCell
. For details, see Modifying Message Display Styles.
You can also directly replace the style resources, string resources, and icon resources referenced in the message display template within `RongCloud