1、通过注册账号添加好友的方式可以与好友进行即时通讯
2、可以看到正在与你进行通讯的好友的实时位置,并在地图上以大头针的方式显示出来,并直观看到你与好友之间的距离。
1、项目中具体开发功能前的基本操作实现(注册、登录);
(1)预准备开发工作
<1>在注册环信开发者账号并创建后台应用,而后注册推送证书,并配置好相关的SDK,根据自己的需求(需不需要音频,视频通讯)选择相应的SDK(环信开发文档 http://docs.easemob.com/doku.php?id=start:300iosclientintegration:10prepareforsdkimport)
<2>根据自己拿到的APPKey和上传的证书名初始化SDK(具体操作看文档)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//registerSDKWithAppKey:注册的appKey,详细见下面注释。
//apnsCertName:推送证书名(不需要加后缀),详细见下面注释。
[[EaseMob sharedInstance] registerSDKWithAppKey:@"douser#istore" apnsCertName:@"istore_dev"];
[[EaseMob sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions];
return YES;
}
<3>在进行注册登录之前,先检测当前设备是否联网(非文档所有),以下方法:返回值为YES即为联网,NO则反之;
// 加上以下头文件
#import
#import
#import
#import
#import
#pragma mark -----判断是否联网方法
-(BOOL) connectedToNetwork
{
// Create zero addy
struct sockaddr_in zeroAddress;
bzero(&zeroAddress, sizeof(zeroAddress));
zeroAddress.sin_len = sizeof(zeroAddress);
zeroAddress.sin_family = AF_INET;
// Recover reachability flags
SCNetworkReachabilityRef defaultRouteReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress);
SCNetworkReachabilityFlags flags;
BOOL didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags);
CFRelease(defaultRouteReachability);
if (!didRetrieveFlags)
{
printf("Error. Could not recover network reachability flags\n");
return NO;
}
BOOL isReachable = ((flags & kSCNetworkFlagsReachable) != 0);
BOOL needsConnection = ((flags &kSCNetworkFlagsConnectionRequired) != 0);
return (isReachable && !needsConnection) ? YES : NO;
}
<4>在进行登录和注册的过程中,中间进行服务器连接时会有一段验证的时间,为了更好的用户体验,这个过渡期加个菊花器提示正在加载(这里用MBProgressHUD,可用cocoPods导,网上百度一下你就知道)
引入头文件:
// 菊花器
#import “MBProgressHUD.h”
遵循协议:
MBProgressHUDDelegate
全局变量:
MBProgressHUD *HUD;
#pragma mark ---菊花器方法
- (void)hudWasHidden:(MBProgressHUD *)hud {
// Remove HUD from screen when the HUD was hidded
[HUD removeFromSuperview];
HUD = nil;
}
联网成功先显示菊花器,在菊花器显示过程中完成注册(或登录)
/********************************************/
if ([self connectedToNetwork]) {
NSLog(@"联网成功。。。。。。。。");
/*
联网成功后执行的操作
*/
HUD = [[MBProgressHUD alloc] initWithView:self.view];
HUD.color = [UIColor clearColor];
[self.view addSubview:HUD];
HUD.delegate = self;
HUD.labelText = @"拼了老命注册中...";
HUD.dimBackground = YES;
[HUD showWhileExecuting:@selector(initRegisData) onTarget:self withObject:nil animated:YES];
}else{
UIAlertView *alert=[[UIAlertView alloc] initWithTitle:@"温馨提示" message:@"网络连接失败,请查看网络是否连接正常!" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
[alert show];
}
注册的方法(登录方法大体类似,详见文档,登录时一开始菊花器出现闪现问题,解决方法是通过在主线程对UI进行刷新便可,具体代码在下方)
#pragma mark ---注册成功并返回登录界面
- (void)initRegisData {
[[EaseMob sharedInstance].chatManager asyncRegisterNewAccount:self.regisUserName.text password:self.regisUserPsw.text withCompletion:^(NSString *username, NSString *password, EMError *error) {
if (!error) {
NSLog(@"注册成功");
LoginViewController *loginVC = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"LoginViewController"];
[self presentViewController:loginVC animated:YES completion:nil];
}
} onQueue:nil];
}
闪现请看这段代码
#pragma mark ---登录
- (void)initLoginData {
MainViewController *mainVC = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"NavigationController"];
EMError *error = nil;
NSDictionary *loginInfo = [[EaseMob sharedInstance].chatManager loginWithUsername:self.loginUserName.text password:self.loginUserPsw.text error:&error];
if (!error && loginInfo) {
NSLog(@"登陆成功");
// 没错就是这里
dispatch_sync(dispatch_get_main_queue(), ^(){
// 这里的代码会在主线程执行
[self presentViewController:mainVC animated:YES completion:^{
[self hudWasHidden:HUD];
}];
});
}else{
[[[UIAlertView alloc] initWithTitle:@"警告" message:@"请检查用户名或密码是否输入正确" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil] show];
}
}
<5>因为环信的注册只能用英文字母和数字组成,因此我们要在最开始的时候检测用户注册时输入的字符串是否符合要求,用以下代码:
//判断是否有中文
-(BOOL)IsChinese:(NSString *)str {
for(int i=0; i< [str length];i++){
int a = [str characterAtIndex:i];
if( a > 0x4e00 && a < 0x9fff)
{
return YES;
}
}
return NO;
}
通过以上步骤,基本就可以进行项目功能的开发了
(2)好友体系集成
在需要发送的地方添加以下代码(例如一个添加好友的按钮中),注意引入头文件,设置好代理
// 首先要将代理设置好
[[EaseMob sharedInstance].chatManager removeDelegate:self];
//注册为SDK的ChatManager的delegate
[[EaseMob sharedInstance].chatManager addDelegate:self delegateQueue:nil];
发送好友申请
EMError *error = nil;
BOOL isSuccess = [[EaseMob sharedInstance].chatManager addBuddy:<你的好友名字> message:@"我想加您为好友" error:&error];
if (isSuccess && !error) {
NSLog(@"发送添加成功");
}
// 遵循协议:IChatManagerDelegate
// 以下好友申请回调,我写在登录控制器中,经过自己实践感觉这样才是对的
#pragma mark ----------------好友申请
/*!
@method
@brief 接收到好友请求时的通知
@discussion
@param username 发起好友请求的用户username
@param message 收到好友请求时的say hello消息
*/
- (void)didReceiveBuddyRequest:(NSString *)username
message:(NSString *)message {
_userName = username;
[[[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:@"请求人:%@",username] message:message delegate:self cancelButtonTitle:@"同意" otherButtonTitles:@"拒绝", nil] show];
}
/*!
@method
@brief 好友请求被接受时的回调
@discussion
@param username 之前发出的好友请求被用户username接受了
*/
- (void)didAcceptedByBuddy:(NSString *)username {
NSLog(@"之前发出的好友请求被用户username接受了");
[[[UIAlertView alloc] initWithTitle:@"好友添加提醒" message:[NSString stringWithFormat:@"成功添加好友%@",username] delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil] show];
}
/*!
@method
@brief 好友请求被拒绝时的回调
@discussion
@param username 之前发出的好友请求被用户username拒绝了
*/
- (void)didRejectedByBuddy:(NSString *)username {
NSLog(@"之前发出的好友请求被用户username拒绝了");
[[[UIAlertView alloc] initWithTitle:@"好友添加提醒" message:[NSString stringWithFormat:@"添加好友%@失败",username] delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil] show];
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
switch (buttonIndex) {
case 0:
{
NSLog(@"同意");
EMError *error = nil;
BOOL isSuccess = [[EaseMob sharedInstance].chatManager acceptBuddyRequest:_userName error:&error];
if (isSuccess && !error) {
NSLog(@"发送同意成功");
}
}
break;
case 1:
{
NSLog(@"拒绝");
EMError *error = nil;
BOOL isSuccess = [[EaseMob sharedInstance].chatManager rejectBuddyRequest:_userName reason:@"111111" error:&error];
if (isSuccess && !error) {
NSLog(@"发送拒绝成功");
}
}
break;
default:
break;
}
}
记得移除代理
- (void)dealloc {
// 当离开这个页面的时候,要讲代理取消掉,不然会造成别的页面接收不了消息.
[[EaseMob sharedInstance].chatManager removeDelegate:self];
// [[EaseMob sharedInstance].callManager removeDelegate:self];
}
添加好友完成后,拿到好友列表
[[EaseMob sharedInstance].chatManager asyncFetchBuddyListWithCompletion:^(NSArray *buddyList, EMError *error) {
if (!error) {
NSLog(@"获取成功 -- %@",buddyList);
}
} onQueue:nil];
用一个tableView来显示,点击好友cell即可进入通讯界面(这里跳转用tableViewDelegate中代理方法即可)
(3)即时通讯的实现
遵循此代理:EMChatManagerDelegate
设置代理:
// 首先要将代理设置好
[[EaseMob sharedInstance].chatManager removeDelegate:self];
//注册为SDK的ChatManager的delegate
[[EaseMob sharedInstance].chatManager addDelegate:self delegateQueue:nil];
离开时移除代理:
- (void)dealloc {
// 当离开这个页面的时候,要讲代理取消掉,不然会造成别的页面接收不了消息.
[[EaseMob sharedInstance].chatManager removeDelegate:self];
// [[EaseMob sharedInstance].callManager removeDelegate:self];
}
文本消息(其他消息构造请看环信文档)的构成并发送给好友
- (void)buttonAction:(UIButton *)sender {
[inputTextField resignFirstResponder];
// 文本消息构造
EMChatText *txtChat = [[EMChatText alloc] initWithText:inputTextField.text];
EMTextMessageBody *body = [[EMTextMessageBody alloc] initWithChatObject:txtChat];
// 生成message
EMMessage *message = [[EMMessage alloc] initWithReceiver:self.friendName bodies:@[body]];
message.messageType = eMessageTypeChat;
EMError *error = nil;
id chatManager = [[EaseMob sharedInstance] chatManager];
// [chatManager asyncResendMessage:message progress:nil];
[chatManager sendMessage:message progress:nil error:&error];
if (error) {
UIAlertView * a = [[UIAlertView alloc] initWithTitle:@"error" message:@"发送失败" delegate:nil cancelButtonTitle:@"好" otherButtonTitles:nil, nil];
[a show];
}else {
userContent = [NSString stringWithFormat:@"%@",inputTextField.text];
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:self.userName,@"name",userContent,@"content", nil];
[_resultArray addObject:dict];
[chatTableView reloadData];
}
以下是收到在线消息和离线消息的回调,收到消息后加入消息tableView数据源并刷新
/*!
@method
@brief 收到消息时的回调
@param message 消息对象
@discussion 当EMConversation对象的enableReceiveMessage属性为YES时, 会触发此回调
针对有附件的消息, 此时附件还未被下载.
附件下载过程中的进度回调请参考didFetchingMessageAttachments:progress:,
下载完所有附件后, 回调didMessageAttachmentsStatusChanged:error:会被触发
*/
- (void)didReceiveMessage:(EMMessage *)message{
id msgBody = message.messageBodies.firstObject;
switch (msgBody.messageBodyType) {
case eMessageBodyType_Text:
{
// 收到的文字消息
NSString *txt = ((EMTextMessageBody *)msgBody).text;
NSLog(@"收到的文字是 txt -- %@",txt);
friendContent = [NSString stringWithFormat:@"%@",txt];
NSMutableDictionary *dict1 = [NSMutableDictionary dictionaryWithObjectsAndKeys:self.friendName,@"name",friendContent,@"content", nil];
[_resultArray addObject:dict1];
[chatTableView reloadData];
}
break;
case eMessageBodyType_Location:
{
EMLocationMessageBody *body = (EMLocationMessageBody *)msgBody;
NSLog(@"位置接收成功并成功解析。。。。。。。。。。。。。。。。");
NSLog(@"纬度-- %f",body.latitude);
NSLog(@"经度-- %f",body.longitude);
NSLog(@"地址-- %@",body.address);
}
break;
default:break;
}
}
/*!
@method
@brief 接收到离线非透传消息的回调
@discussion
@param offlineMessages 接收到的离线列表
@result
*/
- (void)didReceiveOfflineMessages:(NSArray *)offlineMessages {
// _offlineArray = [NSMutableArray array];
for (EMMessage * obj in offlineMessages){
EMMessage *message = obj;
id msgBody = message.messageBodies.firstObject;
switch (msgBody.messageBodyType) {
case eMessageBodyType_Text:
{
// 收到的文字消息
// 收到的文字消息
NSString *txt = ((EMTextMessageBody *)msgBody).text;
NSLog(@"接收到的离线文字是 txt ----------------- %@",txt);
friendContent = [NSString stringWithFormat:@"%@",txt];
NSMutableDictionary *dict1 = [NSMutableDictionary dictionaryWithObjectsAndKeys:self.friendName,@"name",friendContent,@"content", nil];
[_resultArray addObject:dict1];
[chatTableView reloadData];
}
break;
case eMessageBodyType_Location:
{
EMLocationMessageBody *body = (EMLocationMessageBody *)msgBody;
NSLog(@"位置接收成功并成功解析。。。。。。。。。。。。。。。。");
NSLog(@"纬度-- %f",body.latitude);
NSLog(@"经度-- %f",body.longitude);
NSLog(@"地址-- %@",body.address);
}
break;
default:break;
}
}
}
聊天界面的实现
这里要知道计算文本高度的方法
CGRect rect = [text boundingRectWithSize:CGSizeMake(180.0f, 20000.0f) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:14]} context:nil];
这里就不详述,根据网上两篇博文进行制作的
http://www.cnblogs.com/songliquan/p/4559051.html?utm_source=tuicool&utm_medium=referral
http://www.cnblogs.com/ios8/p/ios-weixin.html
(4)百度地图的实现
一样的步骤,根据百度地图api的开发文档配置好并申请好key即可开始操作
#import “AppDelegate.h”
@interface AppDelegate (){
BMKMapManager* _mapManager;
}
@end
在环信SDK初始化下方加上以下代码
// 要使用百度地图,请先启动BaiduMapManager
_mapManager = [[BMKMapManager alloc]init];
// 如果要关注网络及授权验证事件,请设定 generalDelegate参数
BOOL ret = [_mapManager start:@"申请的key" generalDelegate:nil];
if (!ret) {
NSLog(@"manager start failed!");
}
遵循代理
BMKMapViewDelegate,BMKLocationServiceDelegate
定义全局变量
BMKMapView* _mapView; // 百度地图
BMKLocationService *_locService; // 设备定位位置
CLLocationCoordinate2D coor2; // 朋友的坐标,通过环信通讯收到
这里要注意一个,为了保护用户隐私,需要询问用户是否允许打开定位
在info.plist中加入:
NSLocationAlwaysUsageDescription 允许定位?
NSLocationWhenInUseUsageDescription 允许定位?
if ([[UIDevice currentDevice].systemVersion floatValue] >= 8) {
//由于IOS8中定位的授权机制改变 需要进行手动授权
CLLocationManager *locationManager = [[CLLocationManager alloc] init];
//获取授权认证
[locationManager requestAlwaysAuthorization];
[locationManager requestWhenInUseAuthorization];
}
以下是一些地图精确范围,定位开始等设置
/**
获取位置信息
*/
// 设置定位精确度
[BMKLocationService setLocationDesiredAccuracy:kCLLocationAccuracyBest];
// 指定最小距离更新
[BMKLocationService setLocationDistanceFilter:100.f];
// 初始化BMKLocationService
_locService = [[BMKLocationService alloc] init];
_locService.delegate = self;
// 启动LocationService,开始定位服务
[_locService startUserLocationService];
自己的位置坐标更新调用的代理方法
// 当前自己的位置坐标更新
- (void)didUpdateBMKUserLocation:(BMKUserLocation *)userLocation {
static int k = 0;
myUserLocation = userLocation;
NSLog(@"当前位置为:didUpdateUserLocation Lat %f,long %f",userLocation.location.coordinate.latitude,userLocation.location.coordinate.longitude);
[_mapView updateLocationData:userLocation];
if (k == 0) {
// 视角移动到当前位置
[_mapView setCenterCoordinate:myUserLocation.location.coordinate animated:YES];
k = 1;
}
}
显示好友的位置,即在地图上添加大头针
#pragma mark -添加大头针
// 标记朋友的位置
- (void)addAnnotation {
// 删除所有大头针
[_mapView removeAnnotations:_mapView.annotations];
// 添加一个针
BMKPointAnnotation *annoptation2 = [[BMKPointAnnotation alloc] init];
[annoptation2 setCoordinate:coor2];
CLLocation *aCllocation = [[CLLocation alloc] initWithLatitude:coor2.latitude longitude:coor2.longitude];
// 发起反向地理编码检索
BMKReverseGeoCodeOption *reverseGeoCodeSearchOption = [[BMKReverseGeoCodeOption alloc] init];
reverseGeoCodeSearchOption.reverseGeoPoint = coor2;
// 计算与好友之间的距离
double distance = [_locService.userLocation.location distanceFromLocation:aCllocation];
NSLog(@"与好友之间的距离为 %d 米",(int)distance);
self.title = [NSString stringWithFormat:@"距好友: %d 米",(int)distance];
[_mapView addAnnotation:annoptation2];
static int m = 0;
if (m < 3) {
[_mapView setCenterCoordinate:coor2 animated:YES];
// // 视图移动到新添加的大头针那里
// BMKCoordinateRegion viewRegion = BMKCoordinateRegionMake(coor2, BMKCoordinateSpanMake(0.2,0.2));
// BMKCoordinateRegion adjustedRegion = [_mapView regionThatFits:viewRegion];
// [_mapView setRegion:adjustedRegion animated:YES];
m++;
}
}
记得调用下面方法,否则大头针是为空的
- (BMKAnnotationView *)mapView:(BMKMapView *)mapView viewForAnnotation:(id)annotation {
if ([annotation isKindOfClass:[BMKPointAnnotation class]]) {
// BMKPinAnnotationView *newAnnotationView = [[BMKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:ReuseAnnotation];
BMKPinAnnotationView *newAnnotationView = (BMKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:ReuseAnnotation];
newAnnotationView.pinColor = BMKPinAnnotationColorRed;
newAnnotationView.animatesDrop = YES;
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 30)];
label.backgroundColor = [UIColor cyanColor];
label.text = self.friendName;
BMKActionPaopaoView *paopaoView = [[BMKActionPaopaoView alloc] initWithCustomView:label];
newAnnotationView.paopaoView = paopaoView;
// newAnnotationView.image = [UIImage imageNamed:@"2"];
return newAnnotationView;
}
return nil;
}
ok,通过上面,项目基本大体功能基本完成,当然还有可以改进的地方,例如地图的导航(由于百度最新的导航API与MapSDK出现冲突,暂不实现),通信也可以加语音,视频等功能,目前工作正在进行。