基于環信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閱讀 230,002評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,825評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,722評論 25 708
  • 1)項目里面不需要環信SDK的太多功能,只是想要聊天和好友功能,其他都不用,那SDK一定要總是跟著更新么? a.環...
    DefaultYuan閱讀 26,612評論 17 59
  • 點擊查看原文 Web SDK 開發手冊 SDK 概述 網易云信 SDK 為 Web 應用提供一個完善的 IM 系統...
    layjoy閱讀 13,847評論 0 15
  • 有對象自然是好,被寵壞又是另一回事,你是對方不懂浪漫,但你本身也沒說出來,到底是哪一方的不對在先,到底還是自己矯情...
    但求一睡藍忘機閱讀 342評論 0 0