基于環(huán)信SDK的仿微信聊天app【每日更新】

研究環(huán)信SDK也有一段時(shí)間了,之前看了下環(huán)信的官方文檔,寫(xiě)的很詳細(xì),我們只需要照葫蘆畫(huà)瓢就可以了,但是要獨(dú)立的完成以款即時(shí)通訊的app難度還是很大的,因?yàn)樾枰紤]的地方太多,工作量也很大,樓主上個(gè)月辭職了,正好可以靜下心來(lái)好好研究一下技術(shù),當(dāng)然做完這個(gè)項(xiàng)目還是要繼續(xù)找工作的(說(shuō)到這里 不得不吐槽一下,找工作的人真多呀),話不多說(shuō),開(kāi)干,我會(huì)每天在簡(jiǎn)書(shū)上更新,希望在成長(zhǎng)自己的同時(shí)能幫到一些朋友~
  • 集成環(huán)信SDK,集成這一步官方文檔都寫(xiě)的很清楚了,我就不再贅述:
  • SDK下載地址http://www.easemob.com/download/im
  • 官方文檔:http://docs.easemob.com/start/300iosclientintegration/20iossdkimport
  • 集成完后我們需要?jiǎng)h除兩個(gè)東西,這兩個(gè)是輕量級(jí)的SDK,里面功能有限,適合簡(jiǎn)單的測(cè)試,我們這里用完整版本的,所以我們進(jìn)入項(xiàng)目原文件把這兩個(gè)東西干掉:


  • 這里要注意一下,如果刪除了輕量級(jí)的SDK,前面的配置就不能用這個(gè)輕量級(jí)的,必須用完整版的


  • 萬(wàn)事俱備,可以開(kāi)干!,首先導(dǎo)入頭文件然后再初始化SDK,這些操作都在AppDelegate.m中:
  • 導(dǎo)入頭文件
#import "EaseMob.h"
  • 初始化
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    /**
     *  @prama registerSDKWithAppKey 環(huán)信官網(wǎng)的appKey
     *  @prama apnsCertName 蘋(píng)果官網(wǎng)注冊(cè)的推送證書(shū),樓主沒(méi)有申請(qǐng)99刀,嘿嘿,這里就不用啦
     *  @prama otherConfig
     */
    [[EaseMob sharedInstance] registerSDKWithAppKey:kEaseMobAppKey
                              apnsCertName:nil
                              otherConfig:nil];
    return YES;
}
  • 集成完SDK后,先做生命周期的跟蹤
  • 啟動(dòng)
  • 進(jìn)入后臺(tái)
  • 從后臺(tái)進(jìn)入前臺(tái)
  • 銷毀
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    /**
     *  @prama registerSDKWithAppKey 環(huán)信官網(wǎng)的appKey
     *  @prama apnsCertName 蘋(píng)果官網(wǎng)注冊(cè)的推送證書(shū),樓主沒(méi)有申請(qǐng)99刀,嘿嘿,這里就不用啦
     *  @prama otherConfig
     */
    [[EaseMob sharedInstance] registerSDKWithAppKey:kEaseMobAppKey
                              apnsCertName:nil
                              otherConfig:nil];
    // 啟動(dòng)
    [[EaseMob sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions];
    return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
    // 進(jìn)入后臺(tái)
    [[EaseMob sharedInstance] applicationDidEnterBackground:application];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
    // 從后臺(tái)進(jìn)入前臺(tái)
    [[EaseMob sharedInstance] applicationWillEnterForeground:application];
}
- (void)applicationWillTerminate:(UIApplication *)application {
    // 銷毀
    [[EaseMob sharedInstance] applicationWillTerminate:application];
}
  • 搭建登錄注冊(cè)界面,完成拖線:


注冊(cè)

  • 注冊(cè)模式分兩種,開(kāi)放注冊(cè)和授權(quán)注冊(cè)。只有開(kāi)放注冊(cè)時(shí),才可以客戶端注冊(cè)逮矛。
  • 開(kāi)放注冊(cè)是為了測(cè)試使用,正式環(huán)境中不推薦使用該方式注冊(cè)環(huán)信賬號(hào)揽咕,授權(quán)注冊(cè)的流程應(yīng)該是您服務(wù)器通過(guò)環(huán)信提供的 REST API 注冊(cè)蟋座,之后保存到您的服務(wù)器或返回給客戶端勉失。
  • 注冊(cè)提供了三種方法,在這里我們選擇第三種(IChatManagerDelegate 回調(diào)方法).
  • 注冊(cè)步驟: 調(diào)用注冊(cè)接口 -> 添加代理 -> 遵守協(xié)議 -> 監(jiān)聽(tīng)回調(diào)方法,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 {
    
}

// 注冊(cè)(代理回調(diào)方法注冊(cè))
- (IBAction)registerBtn:(id)sender {
    
    // 接口調(diào)用
    [[EaseMob sharedInstance].chatManager asyncRegisterNewAccount:self.usernameLabel.text password:self.pwdLabel.text];

}

// 監(jiān)聽(tīng)回調(diào)方法
- (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
  • 運(yùn)行程序,輸入注冊(cè)的賬號(hào)和密碼:
  • 我們?cè)偃キh(huán)信后臺(tái)看看是否注冊(cè)成功


登錄

  • 登錄:調(diào)用 SDK 的登錄接口進(jìn)行的操作。
  • 提供了三種方法,這里我們采用第二種方法(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];
}
  • 登錄過(guò)后而已看到登錄成功,但是這里也打印了一些xmpp控制臺(tái)的輸出信息,我們可以把它屏蔽掉
  • 屏蔽方法:來(lái)到AppDelegate.m:我們初始化SDK的時(shí)候有個(gè)參數(shù)是otherConfig,點(diǎn)擊該方法可以看到右邊的解釋:
  • 把該參數(shù)改為@NO,就可以屏蔽啦~
清除控制臺(tái)輸出
登錄成功

退出登錄

退出登錄分兩種類型:主動(dòng)退出登錄和被動(dòng)退出登錄皂林。

主動(dòng)退出登錄:調(diào)用SDK的退出接口朗鸠;
被動(dòng)退出登錄:1. 正在登錄的賬號(hào)在另一臺(tái)設(shè)備上登錄蚯撩;2. 正在登錄的賬號(hào)被從服務(wù)器端刪除础倍。
  • 退出登錄提供了三種方法,這里用第二種(block 異步方法)

  • logoffWithUnbindDeviceToken:是否解除 device token 的綁定,在被動(dòng)退出時(shí)傳 NO胎挎,在主動(dòng)退出時(shí)傳 YES沟启。

#pragma mark - 退出登錄
- (IBAction)loginOut:(id)sender {
    
    /*
     asyncLogoffWithUnbindDeviceToken:
     主動(dòng)退出的時(shí)候,傳YES
     被動(dòng)退出的時(shí)候,傳NO
     
     被動(dòng)退出:
     其他設(shè)備登錄
     被服務(wù)器移除
     // 退出,傳入YES犹菇,會(huì)解除device token綁定德迹,不再收到群消息;傳NO揭芍,不解除device token
    */
    [[EaseMob sharedInstance].chatManager asyncLogoffWithUnbindDeviceToken:YES completion:^(NSDictionary *info, EMError *error) {
        if (!error) {
            NSLog(@"退出成功");
        }
    } onQueue:nil];
}
// 從其他設(shè)備登錄
- (void)didLoginFromOtherDevice
{
    [[EaseMob sharedInstance].chatManager asyncLogoffWithUnbindDeviceToken:NO completion:^(NSDictionary *info, EMError *error) {
        if (!error) {
            NSLog(@"從其他設(shè)備登錄");
        }
    } onQueue:nil];
}

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

自動(dòng)重連

  • 當(dāng)?shù)艟€時(shí)胳搞,iOS SDK 會(huì)自動(dòng)重連,只需要監(jiān)聽(tīng)重連相關(guān)的回調(diào)称杨,無(wú)需進(jìn)行任何操作肌毅。
#pragma mark - 自動(dòng)重連
/*!
 @method
 @brief 將要發(fā)起自動(dòng)重連操作時(shí)發(fā)送該回調(diào)
 @discussion
 @result
 */
- (void)willAutoReconnect
{
    NSLog(@"將要自動(dòng)重連");
}

/*!
 @method
 @brief 自動(dòng)重連操作完成后的回調(diào)(成功的話,error為nil姑原,失敗的話悬而,查看error的錯(cuò)誤信息)
 @discussion
 @result
 */
- (void)didAutoReconnectFinishedWithError:(NSError *)error
{
    if (!error) {
        NSLog(@"自動(dòng)重連成功");
    }
}
  • 在這里呢,我連接成功后就拔掉電腦網(wǎng)線模擬掉線的情況,然后再插上網(wǎng)線,可以看到控制臺(tái)打印:

自動(dòng)登錄

  • 自動(dòng)登錄:即首次登錄成功后,不需要再次調(diào)用登錄方法锭汛,在下次 APP 啟動(dòng)時(shí)笨奠,SDK 會(huì)自動(dòng)為您登錄。并且如果您自動(dòng)登錄失敗唤殴,也可以讀取到之前的會(huì)話信息般婆。
  • SDK 中自動(dòng)登錄屬性默認(rèn)是關(guān)閉的,需要您在登錄成功后設(shè)置朵逝,以便您在下次 APP 啟動(dòng)時(shí)不需要再次調(diào)用環(huán)信登錄腺兴,并且能在沒(méi)有網(wǎng)的情況下得到會(huì)話列表。
  • 在登錄成功后設(shè)置自動(dòng)登錄
// 登錄
- (IBAction)loginBtn:(id)sender {
    [[EaseMob sharedInstance].chatManager asyncLoginWithUsername:self.usernameLabel.text password:self.pwdLabel.text completion:^(NSDictionary *loginInfo, EMError *error) {
        if (!error && loginInfo) {
            NSLog(@"登錄成功");
            // 設(shè)置自動(dòng)登錄
            [[EaseMob sharedInstance].chatManager setIsAutoLoginEnabled:YES];
        }
    } onQueue:nil];
}
  • 自動(dòng)登錄在以下幾種情況下會(huì)被取消:

    用戶調(diào)用了 SDK 的登出動(dòng)作廉侧;
    用戶在別的設(shè)備上更改了密碼页响,導(dǎo)致此設(shè)備上自動(dòng)登錄失敗段誊;
    用戶的賬號(hào)被從服務(wù)器端刪除闰蚕;
    用戶從另一個(gè)設(shè)備登錄,把當(dāng)前設(shè)備上登錄的用戶踢出连舍。

  • 所以没陡,在您調(diào)用登錄方法前,應(yīng)該先判斷是否設(shè)置了自動(dòng)登錄,如果設(shè)置了盼玄,則不需要您再調(diào)用贴彼。

  • 我們?cè)?code>AppDelegate.m的- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法中判斷:

BOOL isAutoLogin = [[EaseMob sharedInstance].chatManager isAutoLoginEnabled];
    if (isAutoLogin) {
        NSLog(@"切換根控制器");
    }
  • SDK中,如果發(fā)生自動(dòng)登錄埃儿,會(huì)有以下回調(diào):
#pragma mark - 自動(dòng)登錄
- (void)willAutoLoginWithInfo:(NSDictionary *)loginInfo error:(EMError *)error
{
    NSLog(@"將要自動(dòng)登錄");
}

- (void)didAutoLoginWithInfo:(NSDictionary *)loginInfo error:(EMError *)error
{
    NSLog(@"已經(jīng)自動(dòng)登錄");
}

接下來(lái)我們就要進(jìn)行微信框架的搭建了

  • 首先導(dǎo)入素材,設(shè)置AppIcon和LaunchImage, 這兩步比較簡(jiǎn)單,我就不細(xì)講了,如果有不清楚的朋友可以留言.如果大家有需要,后面我會(huì)講一下怎樣獲取到微信里面的圖片素材,之前有很多網(wǎng)友說(shuō)Assets.car里面的素材拿不到,別擔(dān)心,總是有方法的,后面再一一講解,我們現(xiàn)在主要把精力放在即時(shí)通訊上面,素材我也會(huì)貼上來(lái)的~
  • 微信架構(gòu)搭建,這里我采用純代碼,storyboard適合頁(yè)面多的時(shí)候,像這種復(fù)雜的界面建議用純代碼加xib.

  • 進(jìn)入文件夾,重構(gòu)一下文件,大家個(gè)人重構(gòu)習(xí)慣自由發(fā)揮


  • 把文件拖入到工程中,注意我們之前在build setting中設(shè)置了Other Linker Flags,在我們重構(gòu)文件夾的時(shí)候改變了路徑,所以運(yùn)行會(huì)報(bào)錯(cuò):找不到libEaseMobClientSDK.a文件,所以我們需要再來(lái)到Other Linker Flags重新指定一下路徑:

  • 創(chuàng)建文件:
(tabBar)LHLTabBarController->UITabBarController
(導(dǎo)航條)LHLNavViewController->UINavigationController
(微信模塊)LHLChatViewController->UITableViewController
(聯(lián)系人模塊)LHLContactViewController->UITableViewController
(發(fā)現(xiàn)模塊)LHLDiscoverViewController->UITableViewController
(我模塊)LHLMeViewController->UITableViewController
(登錄)LHLLoginViewController->UIViewController
  • 之前測(cè)試的功能我們?nèi)恐匦聦?xiě)一遍(比如登錄注冊(cè)...):
    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 環(huán)信官網(wǎng)的appKey
     *  @prama apnsCertName 蘋(píng)果官網(wǎng)注冊(cè)的推送證書(shū),樓主沒(méi)有申請(qǐng)99刀,嘿嘿,這里就不用啦
     *  @prama otherConfig
     */
    // 1.注冊(cè)APPKey
    [[EaseMob sharedInstance] registerSDKWithAppKey:kEaseMobAppKey
                              apnsCertName:nil
                                        otherConfig:@{kSDKConfigEnableConsoleLogger : @NO}];
    // 2.跟蹤app生命周期
    [[EaseMob sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions];
    
    // 3.添加監(jiān)聽(tīng)代理
    [[EaseMob sharedInstance].chatManager addDelegate:self delegateQueue:nil];
    
    // 4.判斷是否是自動(dòng)登錄
    BOOL isAutoLogin = [[EaseMob sharedInstance].chatManager isAutoLoginEnabled];
    if (isAutoLogin) {
        NSLog(@"已經(jīng)設(shè)置自動(dòng)登錄,切換根控制器");
        // 1.顯示正在自動(dòng)登錄
        [SVProgressHUD showWithStatus:@"正在自動(dòng)登錄中..."];
        // 2.在 didAutoLoginWithInfo 方法中切換至主頁(yè)面
    }
    
    return YES;
}

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

- (void)applicationDidEnterBackground:(UIApplication *)application {
    // 進(jìn)入后臺(tái)
    [[EaseMob sharedInstance] applicationDidEnterBackground:application];
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
    // 從后臺(tái)進(jìn)入前臺(tái)
    [[EaseMob sharedInstance] applicationWillEnterForeground:application];
}

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

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

#pragma mark - 自動(dòng)登錄
- (void)didAutoLoginWithInfo:(NSDictionary *)loginInfo error:(EMError *)error
{
    [SVProgressHUD dismiss];
    if (error) { // 顯示錯(cuò)誤信息,不登錄
        [JDStatusBarNotification showWithStatus:error.description dismissAfter:2.0];
    }else // 切換窗口根控制器
    {
        LHLTabBarController *tabBarVc = [[LHLTabBarController alloc] init];
        self.window.rootViewController = tabBarVc;
        [self.window makeKeyAndVisible];
    }
}

#pragma mark - 監(jiān)聽(tīng)被動(dòng)退出
- (void)didRemovedFromServer
{
    NSLog(@"賬號(hào)被服務(wù)器刪除");
    [self lhl_LogOffPassively];
}

- (void)didLoginFromOtherDevice
{
    NSLog(@"從其他設(shè)備登錄");
    [self lhl_LogOffPassively];
}
#pragma mark - 被動(dòng)LogOff
- (void)lhl_LogOffPassively
{
    [[EaseMob sharedInstance].chatManager asyncLogoffWithUnbindDeviceToken:NO completion:^(NSDictionary *info, EMError *error) {
        
        // 被動(dòng)退出后回調(diào), 切換根控制器
        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];
    
    // 創(chuàng)建所有子控制器
    [self setUpChildViewControllers];
    
    // 設(shè)置tabBar按鈕和標(biāo)題
    [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];
    
    // 發(fā)現(xiàn)
    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
{
    // 設(shè)置按鈕的標(biāo)題和圖片
    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:@"發(fā)現(xiàn)"];
    
    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];
    // 設(shè)置導(dǎo)航欄背景顏色
    [self.navigationBar lhl_setBackgroundColor:[UIColor blackColor]];
    // 修改左右UIBarButtonItem主題色
    self.navigationBar.tintColor = [UIColor whiteColor];
    // 修改標(biāo)題顏色
    [self.navigationBar setTitleTextAttributes:@{NSForegroundColorAttributeName : [UIColor whiteColor]}];
    
}

// 設(shè)置狀態(tài)欄顏色
- (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

  • 到這里,基本架構(gòu)已經(jīng)搭建完畢我們現(xiàn)在做登錄模塊,這里直接用了系統(tǒng)的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.設(shè)置自動(dòng)登錄
            [[EaseMob sharedInstance].chatManager setIsAutoLoginEnabled:YES];
            [JDStatusBarNotification showWithStatus:@"登錄成功!" dismissAfter:2.0 styleName:JDStatusBarStyleSuccess];
            // 2.切換至主頁(yè)面
            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];
}

// 注冊(cè)(代理回調(diào)方法注冊(cè))
- (IBAction)registerBtn:(id)sender {
    
    [SVProgressHUD showWithStatus:@"注冊(cè)中..."];
    // 接口調(diào)用
    [[EaseMob sharedInstance].chatManager asyncRegisterNewAccount:self.usernameLabel.text password:self.pwdLabel.text withCompletion:^(NSString *username, NSString *password, EMError *error) {
        [SVProgressHUD dismiss];
        if (!error) {
            NSLog(@"注冊(cè)成功 : username = %@ password = %@", error, password);
            [JDStatusBarNotification showWithStatus:@"注冊(cè)成功,請(qǐng)登錄!" dismissAfter:2.0 styleName:JDStatusBarStyleSuccess];
        }else
        {
            NSLog(@"error : %@", error);
            [JDStatusBarNotification showWithStatus:[NSString stringWithFormat:@"注冊(cè)失敗!"] 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"

/**
 在微信中,它的微信界面的標(biāo)題切換的是 titleView
 除了下面4種狀態(tài),還有
 聽(tīng)筒模式
 未讀消息數(shù)量展示
 等等
 
 此處我們通過(guò)簡(jiǎn)單的模仿,來(lái)了解 連接狀態(tài)改變,以及消息接收帶來(lái)的對(duì)標(biāo)題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 - 自動(dòng)重連
/*!
 @method
 @brief 將要發(fā)起自動(dòng)重連操作時(shí)發(fā)送該回調(diào)
 @discussion
 @result
 */
- (void)willAutoReconnect
{
    NSLog(@"將要自動(dòng)重連");
    self.title = LHLWeChatTitleWillConnect;
}

/*!
 @method
 @brief 自動(dòng)重連操作完成后的回調(diào)(成功的話器仗,error為nil,失敗的話童番,查看error的錯(cuò)誤信息)
 @discussion
 @result
 */
- (void)didAutoReconnectFinishedWithError:(NSError *)error
{
    if (!error) {
        NSLog(@"自動(dòng)重連成功");
        self.title = LHLWeChatTitleNormal;
    }else
    {
        NSLog(@"自動(dòng)重連失敗");
        self.title = LHLWeChatTitleDisconnect;
    }
}

#pragma mark - 連接狀態(tài)改變
- (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;
/** 服務(wù)器獲取的好友列表 */
@property (nonatomic, strong) NSArray *buddies;

@end

@implementation LHLContactViewController

static NSString *cellID = @"UITableViewCell";
- (NSMutableArray *)friends
{
    // 好友列表(由EMBuddy對(duì)象組成)
    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 = @"請(qǐng)輸入賬號(hào)";
    }];
    [alertVC addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
        textField.placeholder = @"請(qǐng)輸入理由";
    }];
    [alertVC addAction:[UIAlertAction actionWithTitle:@"發(fā)送" 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(@"發(fā)送好友請(qǐng)求成功 - %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

  • 我的模塊也是結(jié)合了storyboard,利用靜態(tài)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.記錄退出的用戶名(為了用戶再次登錄的時(shí)候不用重新輸入用戶名.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
  • 有網(wǎng)友提到官方SDK集成的問(wèn)題,我之前以為大家跟著官方文檔一步步來(lái)就會(huì)沒(méi)問(wèn)題的,所以文章開(kāi)頭就直接放了官方文檔的連接,鑒于網(wǎng)友們的問(wèn)題,我接下來(lái)就從零開(kāi)始集成環(huán)信SDK 2.x 和 3.x,希望能幫到遇到困難的網(wǎng)友~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末精钮,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子剃斧,更是在濱河造成了極大的恐慌轨香,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幼东,死亡現(xiàn)場(chǎng)離奇詭異臂容,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)根蟹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)脓杉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人娜亿,你說(shuō)我怎么就攤上這事丽已。” “怎么了买决?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵沛婴,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我督赤,道長(zhǎng)嘁灯,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任躲舌,我火速辦了婚禮丑婿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘没卸。我一直安慰自己羹奉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布约计。 她就那樣靜靜地躺著诀拭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪煤蚌。 梳的紋絲不亂的頭發(fā)上耕挨,一...
    開(kāi)封第一講書(shū)人閱讀 51,604評(píng)論 1 305
  • 那天细卧,我揣著相機(jī)與錄音,去河邊找鬼筒占。 笑死贪庙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的翰苫。 我是一名探鬼主播止邮,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼革骨!你這毒婦竟也來(lái)了农尖?” 一聲冷哼從身側(cè)響起析恋,我...
    開(kāi)封第一講書(shū)人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤良哲,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后助隧,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體筑凫,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年并村,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了巍实。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡哩牍,死狀恐怖棚潦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情膝昆,我是刑警寧澤丸边,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站荚孵,受9級(jí)特大地震影響妹窖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜收叶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一骄呼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧判没,春花似錦蜓萄、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至摊阀,卻和暖如春耻蛇,著一層夾襖步出監(jiān)牢的瞬間踪蹬,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工臣咖, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留跃捣,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親游桩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子殷费,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)术唬,斷路器,智...
    卡卡羅2017閱讀 134,657評(píng)論 18 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,152評(píng)論 25 707
  • 1)項(xiàng)目里面不需要環(huán)信SDK的太多功能,只是想要聊天和好友功能丸升,其他都不用,那SDK一定要總是跟著更新么牺氨? a.環(huán)...
    DefaultYuan閱讀 26,564評(píng)論 17 59
  • 點(diǎn)擊查看原文 Web SDK 開(kāi)發(fā)手冊(cè) SDK 概述 網(wǎng)易云信 SDK 為 Web 應(yīng)用提供一個(gè)完善的 IM 系統(tǒng)...
    layjoy閱讀 13,766評(píng)論 0 15
  • 有對(duì)象自然是好狡耻,被寵壞又是另一回事,你是對(duì)方不懂浪漫猴凹,但你本身也沒(méi)說(shuō)出來(lái)夷狰,到底是哪一方的不對(duì)在先,到底還是自己矯情...
    但求一睡藍(lán)忘機(jī)閱讀 339評(píng)論 0 0