基于环信SDK的仿微信聊天app【每日更新】

研究环信SDK也有一段时间了,之前看了下环信的官方文档,写的很详细,我们只需要照葫芦画瓢就可以了,但是要独立的完成以款即时通讯的app难度还是很大的,因为需要考虑的地方太多,工作量也很大,楼主上个月辞职了,正好可以静下心来好好研究一下技术,当然做完这个项目还是要继续找工作的(说到这里 不得不吐槽一下,找工作的人真多呀),话不多说,开干,我会每天在简书上更新,希望在成长自己的同时能帮到一些朋友~
  • 集成环信SDK,集成这一步官方文档都写的很清楚了,我就不再赘述:
  • SDK下载地址http://www.easemob.com/download/im
  • 官方文档:http://docs.easemob.com/start/300iosclientintegration/20iossdkimport
  • 集成完后我们需要删除两个东西,这两个是轻量级的SDK,里面功能有限,适合简单的测试,我们这里用完整版本的,所以我们进入项目原文件把这两个东西干掉:


  • 这里要注意一下,如果删除了轻量级的SDK,前面的配置就不能用这个轻量级的,必须用完整版的


  • 万事俱备,可以开干!,首先导入头文件然后再初始化SDK,这些操作都在AppDelegate.m中:
  • 导入头文件
#import "EaseMob.h"
  • 初始化
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    /**
     *  @prama registerSDKWithAppKey 环信官网的appKey
     *  @prama apnsCertName 苹果官网注册的推送证书,楼主没有申请99刀,嘿嘿,这里就不用啦
     *  @prama otherConfig
     */
    [[EaseMob sharedInstance] registerSDKWithAppKey:kEaseMobAppKey
                              apnsCertName:nil
                              otherConfig:nil];
    return YES;
}
  • 集成完SDK后,先做生命周期的跟踪
  • 启动
  • 进入后台
  • 从后台进入前台
  • 销毁
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    /**
     *  @prama registerSDKWithAppKey 环信官网的appKey
     *  @prama apnsCertName 苹果官网注册的推送证书,楼主没有申请99刀,嘿嘿,这里就不用啦
     *  @prama otherConfig
     */
    [[EaseMob sharedInstance] registerSDKWithAppKey:kEaseMobAppKey
                              apnsCertName:nil
                              otherConfig:nil];
    // 启动
    [[EaseMob sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions];
    return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
    // 进入后台
    [[EaseMob sharedInstance] applicationDidEnterBackground:application];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
    // 从后台进入前台
    [[EaseMob sharedInstance] applicationWillEnterForeground:application];
}
- (void)applicationWillTerminate:(UIApplication *)application {
    // 销毁
    [[EaseMob sharedInstance] applicationWillTerminate:application];
}
  • 搭建登录注册界面,完成拖线:


注册

  • 注册模式分两种,开放注册和授权注册。只有开放注册时,才可以客户端注册。
  • 开放注册是为了测试使用,正式环境中不推荐使用该方式注册环信账号,授权注册的流程应该是您服务器通过环信提供的 REST API 注册,之后保存到您的服务器或返回给客户端。
  • 注册提供了三种方法,在这里我们选择第三种(IChatManagerDelegate 回调方法).
  • 注册步骤: 调用注册接口 -> 添加代理 -> 遵守协议 -> 监听回调方法,ViewController.m中代码如下:
//
//  ViewController.m
//  EMWeiChat
//
//  Created by admin on 16/11/4.
//  Copyright © 2016年 冷洪林. All rights reserved.
//

#import "ViewController.h"
#import "EaseMob.h"

@interface ViewController () <EMChatManagerDelegate>
@property (weak, nonatomic) IBOutlet UITextField *usernameLabel;
@property (weak, nonatomic) IBOutlet UITextField *pwdLabel;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 添加代理
    [[EaseMob sharedInstance].chatManager addDelegate:self delegateQueue:nil];
}

// 登录
- (IBAction)loginBtn:(id)sender {
    
}

// 注册(代理回调方法注册)
- (IBAction)registerBtn:(id)sender {
    
    // 接口调用
    [[EaseMob sharedInstance].chatManager asyncRegisterNewAccount:self.usernameLabel.text password:self.pwdLabel.text];

}

// 监听回调方法
- (void)didRegisterNewAccount:(NSString *)username password:(NSString *)password error:(EMError *)error
{
    NSLog(@"%s", __func__);
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end
  • 运行程序,输入注册的账号和密码:
  • 我们再去环信后台看看是否注册成功


登录

  • 登录:调用 SDK 的登录接口进行的操作。
  • 提供了三种方法,这里我们采用第二种方法(block 异步方法):
// 登录
- (IBAction)loginBtn:(id)sender {
    [[EaseMob sharedInstance].chatManager asyncLoginWithUsername:self.usernameLabel.text password:self.pwdLabel.text completion:^(NSDictionary *loginInfo, EMError *error) {
        if (!error && loginInfo) {
            NSLog(@"登录成功");
        }
    } onQueue:nil];
}
  • 登录过后而已看到登录成功,但是这里也打印了一些xmpp控制台的输出信息,我们可以把它屏蔽掉
  • 屏蔽方法:来到AppDelegate.m:我们初始化SDK的时候有个参数是otherConfig,点击该方法可以看到右边的解释:
  • 把该参数改为@NO,就可以屏蔽啦~
清除控制台输出
登录成功

退出登录

退出登录分两种类型:主动退出登录和被动退出登录。

主动退出登录:调用SDK的退出接口;
被动退出登录:1. 正在登录的账号在另一台设备上登录;2. 正在登录的账号被从服务器端删除。
  • 退出登录提供了三种方法,这里用第二种(block 异步方法)

  • logoffWithUnbindDeviceToken:是否解除 device token 的绑定,在被动退出时传 NO,在主动退出时传 YES。

#pragma mark - 退出登录
- (IBAction)loginOut:(id)sender {
    
    /*
     asyncLogoffWithUnbindDeviceToken:
     主动退出的时候,传YES
     被动退出的时候,传NO
     
     被动退出:
     其他设备登录
     被服务器移除
     // 退出,传入YES,会解除device token绑定,不再收到群消息;传NO,不解除device token
    */
    [[EaseMob sharedInstance].chatManager asyncLogoffWithUnbindDeviceToken:YES completion:^(NSDictionary *info, EMError *error) {
        if (!error) {
            NSLog(@"退出成功");
        }
    } onQueue:nil];
}
// 从其他设备登录
- (void)didLoginFromOtherDevice
{
    [[EaseMob sharedInstance].chatManager asyncLogoffWithUnbindDeviceToken:NO completion:^(NSDictionary *info, EMError *error) {
        if (!error) {
            NSLog(@"从其他设备登录");
        }
    } onQueue:nil];
}

// 被服务器移除
- (void)didRemovedFromServer
{
    [[EaseMob sharedInstance].chatManager asyncLogoffWithUnbindDeviceToken:NO completion:^(NSDictionary *info, EMError *error) {
        if (!error) {
            NSLog(@"被服务器移除");
        }
    } onQueue:nil];
}

自动重连

  • 当掉线时,iOS SDK 会自动重连,只需要监听重连相关的回调,无需进行任何操作。
#pragma mark - 自动重连
/*!
 @method
 @brief 将要发起自动重连操作时发送该回调
 @discussion
 @result
 */
- (void)willAutoReconnect
{
    NSLog(@"将要自动重连");
}

/*!
 @method
 @brief 自动重连操作完成后的回调(成功的话,error为nil,失败的话,查看error的错误信息)
 @discussion
 @result
 */
- (void)didAutoReconnectFinishedWithError:(NSError *)error
{
    if (!error) {
        NSLog(@"自动重连成功");
    }
}
  • 在这里呢,我连接成功后就拔掉电脑网线模拟掉线的情况,然后再插上网线,可以看到控制台打印:

自动登录

  • 自动登录:即首次登录成功后,不需要再次调用登录方法,在下次 APP 启动时,SDK 会自动为您登录。并且如果您自动登录失败,也可以读取到之前的会话信息。
  • SDK 中自动登录属性默认是关闭的,需要您在登录成功后设置,以便您在下次 APP 启动时不需要再次调用环信登录,并且能在没有网的情况下得到会话列表。
  • 在登录成功后设置自动登录
// 登录
- (IBAction)loginBtn:(id)sender {
    [[EaseMob sharedInstance].chatManager asyncLoginWithUsername:self.usernameLabel.text password:self.pwdLabel.text completion:^(NSDictionary *loginInfo, EMError *error) {
        if (!error && loginInfo) {
            NSLog(@"登录成功");
            // 设置自动登录
            [[EaseMob sharedInstance].chatManager setIsAutoLoginEnabled:YES];
        }
    } onQueue:nil];
}
  • 自动登录在以下几种情况下会被取消:

    用户调用了 SDK 的登出动作;
    用户在别的设备上更改了密码,导致此设备上自动登录失败;
    用户的账号被从服务器端删除;
    用户从另一个设备登录,把当前设备上登录的用户踢出。

  • 所以,在您调用登录方法前,应该先判断是否设置了自动登录,如果设置了,则不需要您再调用。

  • 我们在AppDelegate.m- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法中判断:

BOOL isAutoLogin = [[EaseMob sharedInstance].chatManager isAutoLoginEnabled];
    if (isAutoLogin) {
        NSLog(@"切换根控制器");
    }
  • SDK中,如果发生自动登录,会有以下回调:
#pragma mark - 自动登录
- (void)willAutoLoginWithInfo:(NSDictionary *)loginInfo error:(EMError *)error
{
    NSLog(@"将要自动登录");
}

- (void)didAutoLoginWithInfo:(NSDictionary *)loginInfo error:(EMError *)error
{
    NSLog(@"已经自动登录");
}

接下来我们就要进行微信框架的搭建了

  • 首先导入素材,设置AppIcon和LaunchImage, 这两步比较简单,我就不细讲了,如果有不清楚的朋友可以留言.如果大家有需要,后面我会讲一下怎样获取到微信里面的图片素材,之前有很多网友说Assets.car里面的素材拿不到,别担心,总是有方法的,后面再一一讲解,我们现在主要把精力放在即时通讯上面,素材我也会贴上来的~
  • 微信架构搭建,这里我采用纯代码,storyboard适合页面多的时候,像这种复杂的界面建议用纯代码加xib.

  • 进入文件夹,重构一下文件,大家个人重构习惯自由发挥


  • 把文件拖入到工程中,注意我们之前在build setting中设置了Other Linker Flags,在我们重构文件夹的时候改变了路径,所以运行会报错:找不到libEaseMobClientSDK.a文件,所以我们需要再来到Other Linker Flags重新指定一下路径:

  • 创建文件:
(tabBar)LHLTabBarController->UITabBarController
(导航条)LHLNavViewController->UINavigationController
(微信模块)LHLChatViewController->UITableViewController
(联系人模块)LHLContactViewController->UITableViewController
(发现模块)LHLDiscoverViewController->UITableViewController
(我模块)LHLMeViewController->UITableViewController
(登录)LHLLoginViewController->UIViewController
  • 之前测试的功能我们全部重新写一遍(比如登录注册...):
    AppDelegate.m中:
//
//  AppDelegate.m
//  EMWeiChat
//
//  Created by admin on 16/11/4.
//  Copyright © 2016年 冷洪林. All rights reserved.
//

#import "AppDelegate.h"

#define kEaseMobAppKey @"lengleng#lengleng"
#import "LHLTabBarController.h"
#import "LHLLoginViewController.h"

@interface AppDelegate () <EMChatManagerDelegate>

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    /**
     *  @prama registerSDKWithAppKey 环信官网的appKey
     *  @prama apnsCertName 苹果官网注册的推送证书,楼主没有申请99刀,嘿嘿,这里就不用啦
     *  @prama otherConfig
     */
    // 1.注册APPKey
    [[EaseMob sharedInstance] registerSDKWithAppKey:kEaseMobAppKey
                              apnsCertName:nil
                                        otherConfig:@{kSDKConfigEnableConsoleLogger : @NO}];
    // 2.跟踪app生命周期
    [[EaseMob sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions];
    
    // 3.添加监听代理
    [[EaseMob sharedInstance].chatManager addDelegate:self delegateQueue:nil];
    
    // 4.判断是否是自动登录
    BOOL isAutoLogin = [[EaseMob sharedInstance].chatManager isAutoLoginEnabled];
    if (isAutoLogin) {
        NSLog(@"已经设置自动登录,切换根控制器");
        // 1.显示正在自动登录
        [SVProgressHUD showWithStatus:@"正在自动登录中..."];
        // 2.在 didAutoLoginWithInfo 方法中切换至主页面
    }
    
    return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application {
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    // 进入后台
    [[EaseMob sharedInstance] applicationDidEnterBackground:application];
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
    // 从后台进入前台
    [[EaseMob sharedInstance] applicationWillEnterForeground:application];
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
}

- (void)applicationWillTerminate:(UIApplication *)application {
    // 销毁
    [[EaseMob sharedInstance] applicationWillTerminate:application];
    // 移除代理
    [[EaseMob sharedInstance].chatManager removeDelegate:self];
}

#pragma mark - 自动登录
- (void)didAutoLoginWithInfo:(NSDictionary *)loginInfo error:(EMError *)error
{
    [SVProgressHUD dismiss];
    if (error) { // 显示错误信息,不登录
        [JDStatusBarNotification showWithStatus:error.description dismissAfter:2.0];
    }else // 切换窗口根控制器
    {
        LHLTabBarController *tabBarVc = [[LHLTabBarController alloc] init];
        self.window.rootViewController = tabBarVc;
        [self.window makeKeyAndVisible];
    }
}

#pragma mark - 监听被动退出
- (void)didRemovedFromServer
{
    NSLog(@"账号被服务器删除");
    [self lhl_LogOffPassively];
}

- (void)didLoginFromOtherDevice
{
    NSLog(@"从其他设备登录");
    [self lhl_LogOffPassively];
}
#pragma mark - 被动LogOff
- (void)lhl_LogOffPassively
{
    [[EaseMob sharedInstance].chatManager asyncLogoffWithUnbindDeviceToken:NO completion:^(NSDictionary *info, EMError *error) {
        
        // 被动退出后回调, 切换根控制器
        LHLLoginViewController *loginVC = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"LHLLoginViewController"];
        self.window.rootViewController = loginVC;
        
    } onQueue:nil];
}
@end

LHLTabBarController.m 中:

//
//  LHLTabBarController.m
//  EMWeiChat
//
//  Created by admin on 16/11/6.
//  Copyright © 2016年 冷洪林. All rights reserved.
//

#import "LHLTabBarController.h"
#import "LHLChatViewController.h"
#import "LHLContactViewController.h"
#import "LHLDiscoverViewController.h"
#import "LHLMeViewController.h"
#import "LHLNavViewController.h"

@interface LHLTabBarController ()

@end

@implementation LHLTabBarController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 创建所有子控制器
    [self setUpChildViewControllers];
    
    // 设置tabBar按钮和标题
    [self setUpAllTitles];
}

- (void)setUpChildViewControllers
{
    // 微信
    LHLChatViewController *chatVC = [[LHLChatViewController alloc] init];
    LHLNavViewController *nav = [[LHLNavViewController alloc] initWithRootViewController:chatVC];
    [self addChildViewController:nav];
    
    // 通讯录
    LHLContactViewController *contactVc = [[LHLContactViewController alloc] init];
    LHLNavViewController *nav1 = [[LHLNavViewController alloc] initWithRootViewController:contactVc];
    [self addChildViewController:nav1];
    
    // 发现
    LHLDiscoverViewController *discoverVC = [[LHLDiscoverViewController alloc] init];
    LHLNavViewController *nav2 = [[LHLNavViewController alloc] initWithRootViewController:discoverVC];
    [self addChildViewController:nav2];
    
    // 我
    LHLMeViewController *meVC = [[UIStoryboard storyboardWithName:@"LHLMeViewController" bundle:nil] instantiateViewControllerWithIdentifier:@"LHLMeViewController"];
    LHLNavViewController *nav3 = [[LHLNavViewController alloc] initWithRootViewController:meVC];
    [self addChildViewController:nav3];
}

- (void)setUpAllTitles
{
    // 设置按钮的标题和图片
    LHLNavViewController *nav = self.childViewControllers[0];
    [nav setTabBarItemImage:@"tabbar_mainframe" selectImage:@"tabbar_mainframeHL" title:@"微信"];
    
    LHLNavViewController *nav1 = self.childViewControllers[1];
    [nav1 setTabBarItemImage:@"tabbar_contacts" selectImage:@"tabbar_contactsHL" title:@"通讯录"];
    
    LHLNavViewController *nav2 = self.childViewControllers[2];
    [nav2 setTabBarItemImage:@"tabbar_discover" selectImage:@"tabbar_discoverHL" title:@"发现"];
    
    LHLNavViewController *nav3 = self.childViewControllers[3];
    [nav3 setTabBarItemImage:@"tabbar_me" selectImage:@"tabbar_meHL" title:@"我"];
}


@end

LHLNavViewController.m 中:

//
//  LHLNavViewController.m
//  EMWeiChat
//
//  Created by admin on 16/11/6.
//  Copyright © 2016年 冷洪林. All rights reserved.
//

#import "LHLNavViewController.h"

@interface LHLNavViewController ()

@end

@implementation LHLNavViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 设置导航栏背景颜色
    [self.navigationBar lhl_setBackgroundColor:[UIColor blackColor]];
    // 修改左右UIBarButtonItem主题色
    self.navigationBar.tintColor = [UIColor whiteColor];
    // 修改标题颜色
    [self.navigationBar setTitleTextAttributes:@{NSForegroundColorAttributeName : [UIColor whiteColor]}];
    
}

// 设置状态栏颜色
- (UIStatusBarStyle)preferredStatusBarStyle
{
    return UIStatusBarStyleLightContent;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)setTabBarItemImage:(NSString *)image selectImage:(NSString *)selectImage title:(NSString *)title
{
    self.tabBarItem.image = [UIImage imageOriginalWithName:image];
    self.tabBarItem.selectedImage = [UIImage imageOriginalWithName:selectImage];
    self.title = title;
    [self.tabBarItem setTitleTextAttributes:@{NSForegroundColorAttributeName : [UIColor colorWithRed:9 green:187 blue:7]} forState:UIControlStateSelected];
}

@end

  • 到这里,基本架构已经搭建完毕我们现在做登录模块,这里直接用了系统的storyboard搭建登录界面


LHLLoginViewController.m 中:

//
//  ViewController.m
//  EMWeiChat
//
//  Created by admin on 16/11/4.
//  Copyright © 2016年 冷洪林. All rights reserved.
//

#import "LHLLoginViewController.h"
#import "LHLTabBarController.h"

@interface LHLLoginViewController ()
@property (weak, nonatomic) IBOutlet UITextField *usernameLabel;
@property (weak, nonatomic) IBOutlet UITextField *pwdLabel;

@end

@implementation LHLLoginViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 获取到保存的用户名
    NSString *lastUser = [[NSUserDefaults standardUserDefaults] valueForKeyPath:@"username"];
    if (lastUser) {
        self.usernameLabel.text = lastUser;
    }
}

// 登录
- (IBAction)loginBtn:(id)sender {
    
    [SVProgressHUD showWithStatus:@"登录中..."];
    [[EaseMob sharedInstance].chatManager asyncLoginWithUsername:self.usernameLabel.text password:self.pwdLabel.text completion:^(NSDictionary *loginInfo, EMError *error) {
        
        [SVProgressHUD dismiss];
        if (!error) {
            NSLog(@"登录成功");
            // 1.设置自动登录
            [[EaseMob sharedInstance].chatManager setIsAutoLoginEnabled:YES];
            [JDStatusBarNotification showWithStatus:@"登录成功!" dismissAfter:2.0 styleName:JDStatusBarStyleSuccess];
            // 2.切换至主页面
            LHLTabBarController *tabBarVc = [[LHLTabBarController alloc] init];
            [UIApplication sharedApplication].keyWindow.rootViewController = tabBarVc;
        }else
        {
            NSLog(@"error: %@", error);
            [JDStatusBarNotification showWithStatus:[NSString stringWithFormat:@"登录失败!"] dismissAfter:2.0 styleName:JDStatusBarStyleError];
        }
    } onQueue:nil];
}

// 注册(代理回调方法注册)
- (IBAction)registerBtn:(id)sender {
    
    [SVProgressHUD showWithStatus:@"注册中..."];
    // 接口调用
    [[EaseMob sharedInstance].chatManager asyncRegisterNewAccount:self.usernameLabel.text password:self.pwdLabel.text withCompletion:^(NSString *username, NSString *password, EMError *error) {
        [SVProgressHUD dismiss];
        if (!error) {
            NSLog(@"注册成功 : username = %@ password = %@", error, password);
            [JDStatusBarNotification showWithStatus:@"注册成功,请登录!" dismissAfter:2.0 styleName:JDStatusBarStyleSuccess];
        }else
        {
            NSLog(@"error : %@", error);
            [JDStatusBarNotification showWithStatus:[NSString stringWithFormat:@"注册失败!"] dismissAfter:2.0 styleName:JDStatusBarStyleError];
        }
    } onQueue:nil];

}

@end
  • 微信模块 LHLChatViewController.m 中:
//
//  LHLChatViewController.m
//  EMWeiChat
//
//  Created by admin on 16/11/6.
//  Copyright © 2016年 冷洪林. All rights reserved.
//

#import "LHLChatViewController.h"

/**
 在微信中,它的微信界面的标题切换的是 titleView
 除了下面4种状态,还有
 听筒模式
 未读消息数量展示
 等等
 
 此处我们通过简单的模仿,来了解 连接状态改变,以及消息接收带来的对标题view的影响
 */
NSString * const LHLWeChatTitleNormal = @"微信";
NSString * const LHLWeChatTitleWillConnect = @"连接中...";
NSString * const LHLWeChatTitleDisconnect = @"微信(未连接)";
NSString * const LHLWeChatTitleWillReceiveMsg = @"收取中...";

@interface LHLChatViewController () <EMChatManagerDelegate>

@end

@implementation LHLChatViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.title = LHLWeChatTitleNormal;
    
    [[EaseMob sharedInstance].chatManager addDelegate:self delegateQueue:nil];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
#warning Incomplete implementation, return the number of sections
    return 0;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
#warning Incomplete implementation, return the number of rows
    return 0;
}

/*
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:<#@"reuseIdentifier"#> forIndexPath:indexPath];
    
    // Configure the cell...
    
    return cell;
}
*/



#pragma mark - 自动重连
/*!
 @method
 @brief 将要发起自动重连操作时发送该回调
 @discussion
 @result
 */
- (void)willAutoReconnect
{
    NSLog(@"将要自动重连");
    self.title = LHLWeChatTitleWillConnect;
}

/*!
 @method
 @brief 自动重连操作完成后的回调(成功的话,error为nil,失败的话,查看error的错误信息)
 @discussion
 @result
 */
- (void)didAutoReconnectFinishedWithError:(NSError *)error
{
    if (!error) {
        NSLog(@"自动重连成功");
        self.title = LHLWeChatTitleNormal;
    }else
    {
        NSLog(@"自动重连失败");
        self.title = LHLWeChatTitleDisconnect;
    }
}

#pragma mark - 连接状态改变
- (void)didConnectionStateChanged:(EMConnectionState)connectionState
{
    switch (connectionState) {
        case eEMConnectionConnected: // 连接成功
        {
            self.title = LHLWeChatTitleNormal;
        }
            break;
        case eEMConnectionDisconnected: // 未连接
        {
            self.title = LHLWeChatTitleDisconnect;
        }
            break;
            
        default:
            break;
    }
}


#pragma mark - 移除代理
- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [[EaseMob sharedInstance].chatManager removeDelegate:self];
}


@end

  • 通讯录模块 LHLContactViewController.m 中:
//
//  LHLContactViewController.m
//  EMWeiChat
//
//  Created by admin on 16/11/6.
//  Copyright © 2016年 冷洪林. All rights reserved.
//

#import "LHLContactViewController.h"

@interface LHLContactViewController ()

/** 本地的好友列表 */
@property (nonatomic, strong) NSMutableArray *friends;
/** 服务器获取的好友列表 */
@property (nonatomic, strong) NSArray *buddies;

@end

@implementation LHLContactViewController

static NSString *cellID = @"UITableViewCell";
- (NSMutableArray *)friends
{
    // 好友列表(由EMBuddy对象组成)
    if (_friends == nil) {
        
        _friends = [NSMutableArray array];
        _buddies = [[EaseMob sharedInstance].chatManager buddyList];
        if (_buddies.count) {
            [_friends addObjectsFromArray:_buddies];
        }
    }
    return _friends;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.navigationItem.title = @"通讯录";
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"contacts_add_friend"] style:UIBarButtonItemStylePlain target:self action:@selector(addFriend)];
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:cellID];
}

- (void)addFriend
{
    UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"添加好友" message:nil preferredStyle:UIAlertControllerStyleAlert];
    [alertVC addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
        textField.placeholder = @"请输入账号";
    }];
    [alertVC addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
        textField.placeholder = @"请输入理由";
    }];
    [alertVC addAction:[UIAlertAction actionWithTitle:@"发送" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        
        EMError *error = nil;
        BOOL isSuccess = [[EaseMob sharedInstance].chatManager addBuddy:alertVC.textFields.firstObject.text message:alertVC.textFields.lastObject.text error:&error];
        if (!error) {
            NSLog(@"发送好友请求成功 - %d", isSuccess);
        }
    }]];
    [alertVC addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        // 取消添加
    }]];
    [self presentViewController:alertVC animated:YES completion:^{
        
    }];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - Table view data source

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

    return self.friends.count;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID forIndexPath:indexPath];
    
    EMBuddy *buddy = self.friends[indexPath.row];
    cell.textLabel.text = buddy.username;
    
    return cell;
}

@end

  • 我的模块也是结合了storyboard,利用静态cell搭建一些固定不变的界面真的很便捷
  • LHLMeViewController.m 中:
//
//  LHLMeViewController.m
//  EMWeiChat
//
//  Created by admin on 16/11/6.
//  Copyright © 2016年 冷洪林. All rights reserved.
//

#import "LHLMeViewController.h"
#import "LHLSettingViewController.h"

@interface LHLMeViewController ()

@property (weak, nonatomic) IBOutlet UILabel *username;

@end

@implementation LHLMeViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.navigationItem.title = @"我";
    self.username.text = [[EaseMob sharedInstance].chatManager loginInfo][@"username"];
    
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.section == 3) {
        LHLSettingViewController *settingVC = [[UIStoryboard storyboardWithName:@"LHLSettingViewController" bundle:nil] instantiateViewControllerWithIdentifier:@"LHLSettingViewController"];
        self.hidesBottomBarWhenPushed = YES;
        [self.navigationController pushViewController:settingVC animated:YES];
        self.hidesBottomBarWhenPushed = NO;
    }
}

@end
  • ```LHLSettingViewController.m`` 中
//
//  LHLSettingViewController.m
//  EMWeiChat
//
//  Created by admin on 16/11/6.
//  Copyright © 2016年 冷洪林. All rights reserved.
//

#import "LHLSettingViewController.h"
#import "LHLLoginViewController.h"

@interface LHLSettingViewController ()

@end

@implementation LHLSettingViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
}

// 退出登录
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.section == 3) {
        
        [[EaseMob sharedInstance].chatManager asyncLogoffWithUnbindDeviceToken:YES completion:^(NSDictionary *info, EMError *error) {
            if (!error) {
                NSLog(@"退出登录");
                // 1.记录退出的用户名(为了用户再次登录的时候不用重新输入用户名.optional)
                [[NSUserDefaults standardUserDefaults] setObject: [[EaseMob sharedInstance].chatManager loginInfo][@"username"] forKey:@"username"];
                
                // 2.切换窗口根控制器
                LHLLoginViewController *loginVC = [[UIStoryboard storyboardWithName:@"Main" bundle:nil] instantiateViewControllerWithIdentifier:@"LHLLoginViewController"];
                [UIApplication sharedApplication].keyWindow.rootViewController = loginVC;
            }else
            {
                NSLog(@"error : %@", error);
            }
        } onQueue:nil];
        
    }
}



@end
  • 有网友提到官方SDK集成的问题,我之前以为大家跟着官方文档一步步来就会没问题的,所以文章开头就直接放了官方文档的连接,鉴于网友们的问题,我接下来就从零开始集成环信SDK 2.x 和 3.x,希望能帮到遇到困难的网友~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,451评论 25 707
  • 1)项目里面不需要环信SDK的太多功能,只是想要聊天和好友功能,其他都不用,那SDK一定要总是跟着更新么? a.环...
    DefaultYuan阅读 26,540评论 17 59
  • 点击查看原文 Web SDK 开发手册 SDK 概述 网易云信 SDK 为 Web 应用提供一个完善的 IM 系统...
    layjoy阅读 13,661评论 0 15
  • 有对象自然是好,被宠坏又是另一回事,你是对方不懂浪漫,但你本身也没说出来,到底是哪一方的不对在先,到底还是自己矫情...
    但求一睡蓝忘机阅读 336评论 0 0