ReactNative集成網(wǎng)易云信IM Demo(iOS版)

版本

本文已 ReactNative 集成 NIM_iOS_Demo_v4.2.0 為例。Xcode版本為9.0
本人是在已有的ReactNative(以下簡稱RN)工程下集成云信IM(其他各版本集成方式大同小異)

1. 使用 cocoapods 來安裝網(wǎng)易云信依賴

1.首先在 terminal 里進(jìn)入到自己RN項目ios目錄下喝滞, 運行 pod init ,(如果電腦沒安裝cocoapods的坛缕,請先安裝 cocoapods )推励,運行完成后,會在當(dāng)前所在目錄下生成 Podfile 文件夸溶。

  1. 下載云信 IM demo 源碼
    前往 網(wǎng)易云信 下載iOS版 云信IM demo

2. 在 Podfile 里添加網(wǎng)易云信的依賴

  1. terminal 進(jìn)入到 RN ios目錄下執(zhí)行 pod init吴旋,執(zhí)行后在 ios 目錄下生成 Podfile 文件损肛。

  2. 在下載好的云信demo里 NIMDemo 目錄下找到 Podfile 文件,復(fù)制文件里面的內(nèi)容到自己的 Podfile 文件里邮府。此部完成后的結(jié)果如下

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'
# Mall為 RN 工程名字
workspace 'Mall.xcworkspace'

target 'Mall' do
  pod 'yoga', :path => '../node_modules/react-native/ReactCommon/yoga'
  pod 'React', :path => '../node_modules/react-native'
  pod 'SDWebImage', '4.0.0'
  pod 'Toast', '~> 3.1.0'
  pod 'M80AttributedLabel', '~> 1.6.3'
  pod 'TZImagePickerController', '~> 1.9.0'
  pod 'FMDB', '~> 2.7.2'
  pod 'Reachability', '~> 3.2'
  pod 'CocoaLumberjack', '~> 3.2.1'
  pod 'SSZipArchive', '~> 1.8.1'
  pod 'SVProgressHUD', '~> 2.0.3'
  # 網(wǎng)易云信的 NIMKit 包
  pod 'NIMKit/Full', '~> 1.9.1'
end

此處和源碼里的podfile內(nèi)容有點不一致荧关,如果不太清楚各項配置的意思,按照我的配置來就行了褂傀。

  1. 添加完成后在 terminal 窗口里(Podfile 目錄中)執(zhí)行 pod install,執(zhí)行完成后會在 ios 目錄下生成對應(yīng)的 Mall.xcworkspace 文件加勤,以后使用Xcode打開項目就是雙擊此文件即可仙辟。

3. 拷貝IM源碼到RN里

  • 將demo中的 NIMDemo 目錄下的 Classes 和 Supporting Files 拷貝到項目的 ios/Mall 目錄下 (與Images.xcassets文件夾同級)
拷貝文件
  • 將 Supporting Files 目錄下的 Info.plist nim_debug.xcconfig nim_release.xcconfigmain.m 文件刪除

  • NTESAppDelegate.m 內(nèi)容和自己工程中的 AppDelegate.m 文件中的代碼進(jìn)行合并,并修改和去掉部分不需要的代碼鳄梅。

如下代碼是我的AppDelegate.m文件的內(nèi)容

/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

#import "AppDelegate.h"

#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>

#import "NTESLoginViewController.h"
#import "UIView+Toast.h"
#import "NTESService.h"
#import "NTESNotificationCenter.h"
#import "NTESLogManager.h"
#import "NTESDemoConfig.h"
#import "NTESSessionUtil.h"
#import "NTESMainTabController.h"
#import "NTESLoginManager.h"
#import "NTESCustomAttachmentDecoder.h"
#import "NTESClientUtil.h"
#import "NTESNotificationCenter.h"
#import "NIMKit.h"
#import "NTESSDKConfigDelegate.h"
#import "NTESCellLayoutConfig.h"
#import "NTESSubscribeManager.h"
#import "NTESRedPacketManager.h"
#import "NTESBundleSetting.h"



@import PushKit;

NSString *NTESNotificationLogout = @"NTESNotificationLogout";
@interface AppDelegate ()<NIMLoginManagerDelegate,PKPushRegistryDelegate>

@property (nonatomic,strong) NTESSDKConfigDelegate *sdkConfigDelegate;

@end


@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  NSURL *jsCodeLocation;

  jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];

  RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                      moduleName:@"Mall"
                                               initialProperties:nil
                                                   launchOptions:launchOptions];
  rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];

  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  UIViewController *rootViewController = [UIViewController new];
  rootViewController.view = rootView;
  self.window.rootViewController = rootViewController;
  [self.window makeKeyAndVisible];
  
  [self initYunXin];
  
  return YES;
}

- (void)initYunXin
{
  [self setupNIMSDK];
  [self setupServices];
  [self registerPushService];
  [self commonInitListenEvents];
  
  // 因為本項目想實現(xiàn)的功能是啟動進(jìn)入的是 RN 界面叠国,在需要的時候,從 RN 頁面跳轉(zhuǎn)到原生 iOS 界面
  //self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
  //self.window.backgroundColor = [UIColor grayColor];
  //[self.window makeKeyAndVisible];
  //[application setStatusBarStyle:UIStatusBarStyleLightContent];
  
  //[self setupMainViewController];
}

- (void)dealloc
{
  [[NSNotificationCenter defaultCenter] removeObserver:self];
  [[[NIMSDK sharedSDK] loginManager] removeDelegate:self];
}


#pragma mark - ApplicationDelegate
- (void)applicationWillResignActive:(UIApplication *)application {
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
  NSInteger count = [[[NIMSDK sharedSDK] conversationManager] allUnreadCount];
  [[UIApplication sharedApplication] setApplicationIconBadgeNumber:count];
}

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

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

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

- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
  [[NIMSDK sharedSDK] updateApnsToken:deviceToken];
  DDLogInfo(@"didRegisterForRemoteNotificationsWithDeviceToken:  %@", deviceToken);
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{
  DDLogInfo(@"receive remote notification:  %@", userInfo);
}

- (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
  DDLogError(@"fail to get apns token :%@",error);
}

#pragma mark PKPushRegistryDelegate
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type
{
  if ([type isEqualToString:PKPushTypeVoIP])
  {
    [[NIMSDK sharedSDK] updatePushKitToken:credentials.token];
  }
}

- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type
{
  DDLogInfo(@"receive payload %@ type %@",payload.dictionaryPayload,type);
  NSNumber *badge = payload.dictionaryPayload[@"aps"][@"badge"];
  if ([badge isKindOfClass:[NSNumber class]])
  {
    [UIApplication sharedApplication].applicationIconBadgeNumber = [badge integerValue];
  }
}

- (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(NSString *)type
{
  DDLogInfo(@"registry %@ invalidate %@",registry,type);
}


#pragma mark - openURL

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
  [[NTESRedPacketManager sharedManager] application:application openURL:url sourceApplication:sourceApplication annotation:annotation];
  return YES;
}

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString*, id> *)options
{
  [[NTESRedPacketManager sharedManager] application:app openURL:url options:options];
  return YES;
}

- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url
{
  //目前只有紅包跳轉(zhuǎn)
  return [[NTESRedPacketManager sharedManager] application:application handleOpenURL:url];
}


#pragma mark - misc
- (void)registerPushService
{
  //apns
  [[UIApplication sharedApplication] registerForRemoteNotifications];
  
  UIUserNotificationType types = UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert;
  UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:types
                                                                           categories:nil];
  [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
  
  //pushkit
  PKPushRegistry *pushRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
  pushRegistry.delegate = self;
  pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
  
}

//- (void)setupMainViewController
//{
//  LoginData *data = [[NTESLoginManager sharedManager] currentLoginData];
//  NSString *account = [data account];
//  NSString *token = [data token];
//
//  //如果有緩存用戶名密碼推薦使用自動登錄
//  if ([account length] && [token length])
//  {
//    NIMAutoLoginData *loginData = [[NIMAutoLoginData alloc] init];
//    loginData.account = account;
//    loginData.token = token;
//
//    [[[NIMSDK sharedSDK] loginManager] autoLogin:loginData];
//    [[NTESServiceManager sharedManager] start];
//    NTESMainTabController *mainTab = [[NTESMainTabController alloc] initWithNibName:nil bundle:nil];
//    self.window.rootViewController = mainTab;
//  }
//  else
//  {
//    [self setupLoginViewController];
//  }
//}

- (void)commonInitListenEvents
{
  [[NSNotificationCenter defaultCenter] addObserver:self
                                           selector:@selector(logout:)
                                               name:NTESNotificationLogout
                                             object:nil];
  
  [[[NIMSDK sharedSDK] loginManager] addDelegate:self];
}

//- (void)setupLoginViewController
//{
//  [self.window.rootViewController dismissViewControllerAnimated:YES completion:nil];
//  NTESLoginViewController *loginController = [[NTESLoginViewController alloc] init];
//  UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:loginController];
//  self.window.rootViewController = nav;
//}

#pragma mark - 注銷
-(void)logout:(NSNotification *)note
{
  [self doLogout];
}

- (void)doLogout
{
  [[NTESLoginManager sharedManager] setCurrentLoginData:nil];
  [[NTESServiceManager sharedManager] destory];
//  [self setupLoginViewController];
}


#pragma NIMLoginManagerDelegate
-(void)onKick:(NIMKickReason)code clientType:(NIMLoginClientType)clientType
{
  NSString *reason = @"你被踢下線";
  switch (code) {
    case NIMKickReasonByClient:
    case NIMKickReasonByClientManually:{
      NSString *clientName = [NTESClientUtil clientName:clientType];
      reason = clientName.length ? [NSString stringWithFormat:@"你的帳號被%@端踢出下線戴尸,請注意帳號信息安全",clientName] : @"你的帳號被踢出下線粟焊,請注意帳號信息安全";
      break;
    }
    case NIMKickReasonByServer:
      reason = @"你被服務(wù)器踢下線";
      break;
    default:
      break;
  }
  [[[NIMSDK sharedSDK] loginManager] logout:^(NSError *error) {
    [[NSNotificationCenter defaultCenter] postNotificationName:NTESNotificationLogout object:nil];
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"下線通知" message:reason delegate:nil cancelButtonTitle:@"確定" otherButtonTitles:nil, nil];
    [alert show];
  }];
}

- (void)onAutoLoginFailed:(NSError *)error
{
  //只有連接發(fā)生嚴(yán)重錯誤才會走這個回調(diào),在這個回調(diào)里應(yīng)該登出,返回界面等待用戶手動重新登錄项棠。
  DDLogInfo(@"onAutoLoginFailed %zd",error.code);
  [self showAutoLoginErrorAlert:error];
}


#pragma mark - logic impl
- (void)setupServices
{
  [[NTESLogManager sharedManager] start];
  [[NTESNotificationCenter sharedCenter] start];
  [[NTESSubscribeManager sharedManager] start];
  [[NTESRedPacketManager sharedManager] start];
}

- (void)setupNIMSDK
{
  //在注冊 NIMSDK appKey 之前先進(jìn)行配置信息的注冊悲雳,如是否使用新路徑,是否要忽略某些通知,是否需要多端同步未讀數(shù)等
  self.sdkConfigDelegate = [[NTESSDKConfigDelegate alloc] init];
  [[NIMSDKConfig sharedConfig] setDelegate:self.sdkConfigDelegate];
  [[NIMSDKConfig sharedConfig] setShouldSyncUnreadCount:YES];
  [[NIMSDKConfig sharedConfig] setMaxAutoLoginRetryTimes:10];
  [[NIMSDKConfig sharedConfig] setMaximumLogDays:[[NTESBundleSetting sharedConfig] maximumLogDays]];
  [[NIMSDKConfig sharedConfig] setShouldCountTeamNotification:[[NTESBundleSetting sharedConfig] countTeamNotification]];
  
  
  //appkey 是應(yīng)用的標(biāo)識香追,不同應(yīng)用之間的數(shù)據(jù)(用戶合瓢、消息、群組等)是完全隔離的透典。
  //如需打網(wǎng)易云信 Demo 包晴楔,請勿修改 appkey ,開發(fā)自己的應(yīng)用時峭咒,請?zhí)鎿Q為自己的 appkey 税弃。
  //并請對應(yīng)更換 Demo 代碼中的獲取好友列表、個人信息等網(wǎng)易云信 SDK 未提供的接口凑队。
  NSString *appKey        = [[NTESDemoConfig sharedConfig] appKey];
  NIMSDKOption *option    = [NIMSDKOption optionWithAppKey:appKey];
  option.apnsCername      = [[NTESDemoConfig sharedConfig] apnsCername];
  option.pkCername        = [[NTESDemoConfig sharedConfig] pkCername];
  [[NIMSDK sharedSDK] registerWithOption:option];
  
  
  //注冊自定義消息的解析器
  [NIMCustomObject registerCustomDecoder:[NTESCustomAttachmentDecoder new]];
  
  //注冊 NIMKit 自定義排版配置
  [[NIMKit sharedKit] registerLayoutConfig:[NTESCellLayoutConfig new]];
}

#pragma mark - 登錄錯誤回調(diào)
- (void)showAutoLoginErrorAlert:(NSError *)error
{
  NSString *message = [NTESSessionUtil formatAutoLoginMessage:error];
  UIAlertController *vc = [UIAlertController alertControllerWithTitle:@"自動登錄失敗"
                                                              message:message
                                                       preferredStyle:UIAlertControllerStyleAlert];
  
  if ([error.domain isEqualToString:NIMLocalErrorDomain] &&
      error.code == NIMLocalErrorCodeAutoLoginRetryLimit)
  {
    UIAlertAction *retryAction = [UIAlertAction actionWithTitle:@"重試"
                                                          style:UIAlertActionStyleCancel
                                                        handler:^(UIAlertAction * _Nonnull action) {
                                                          LoginData *data = [[NTESLoginManager sharedManager] currentLoginData];
                                                          NSString *account = [data account];
                                                          NSString *token = [data token];
                                                          if ([account length] && [token length])
                                                          {
                                                            NIMAutoLoginData *loginData = [[NIMAutoLoginData alloc] init];
                                                            loginData.account = account;
                                                            loginData.token = token;
                                                            
                                                            [[[NIMSDK sharedSDK] loginManager] autoLogin:loginData];
                                                          }
                                                        }];
    [vc addAction:retryAction];
  }
  
  
  
  UIAlertAction *logoutAction = [UIAlertAction actionWithTitle:@"注銷"
                                                         style:UIAlertActionStyleDestructive
                                                       handler:^(UIAlertAction * _Nonnull action) {
                                                         [[[NIMSDK sharedSDK] loginManager] logout:^(NSError *error) {
                                                           [[NSNotificationCenter defaultCenter] postNotificationName:NTESNotificationLogout object:nil];
                                                         }];
                                                       }];
  [vc addAction:logoutAction];
  
  [self.window.rootViewController presentViewController:vc
                                               animated:YES
                                             completion:nil];
}


@end

4. Xcode中設(shè)置文件引用

  • Xcode左邊導(dǎo)航中點擊項目鹅搪,Build Settings --> Prefix Header 的值設(shè)置為 $(PROJECT_DIR)/Mall/NIMDemo-Prefix.pch
設(shè)置Prefix Header項
  • 打開云信demo NIM.xcworkspace 查看云信的 Header Search Paths 配置踱讨,照貓畫虎的搬到自己項目里來。

記得把 $(SRCROOT)/../NIMKit/NIMKit 這項給去掉,因為在前面我們把NIMKit使用 cocoapods 安裝好了溺拱,不用像demo里一樣把 NIMKit 這個文件夾復(fù)制下來。使用 cocoapods 來安裝哥攘,非常方便日后的升級和版本變更针史。

在自己項目里也配置好這幾項,記得去除$(SRCROOT)/../NIMKit/NIMKit這項
  • 將demo中的Images.xcassets與自己工程中的Images.xcassets合并
8.png

5. Link Binary With Libraries

  • libPods-yuexing-NIMKit.a
  • libc++.tbd
  • lib.tbd
  • libsqlite3.0.tbd
  • VideoToolbox.framework
  • CoreMedia.framework
  • AudioToolbox.framework
  • CoreLocation.framework
  • MapKit.framework
  • AVFoundation.framework
  • MobileCoreServices.framework
    ()

上述操作完成后赋访,記得 Clean(快捷鍵 command + shift + k) 一下可都,然后運行試試。出現(xiàn)錯誤可嘗試刪除ios目錄下的 Pods Podfile.lockMall.xcworkspace 蚓耽,然后在 terminal中的項目 ios目錄下運行 pod install 重新安裝下渠牲。如出現(xiàn)其他錯誤,根據(jù)錯誤提示修改或Google解決步悠。


此步完成后签杈,基本上可以正常運行起來了。集成配置也基本完成鼎兽,后續(xù)的步驟主要是對接 RN 界面和 原生界面互相跳轉(zhuǎn)答姥。

6. 原生添加 navigation 方便后面RN界面和原生界面跳轉(zhuǎn)

  1. 項目正常運行后需要修改幾個地方,比如從 ReactNative 頁面跳轉(zhuǎn)到 原生頁面的適配問題等谚咬。
  2. AppDelegate.h文件中聲明變量 navigation
@property (nonatomic, strong) UINavigationController *navigation;
  1. 修改 AppDelegate.m文件中didFinishLaunchingWithOptions代碼

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  // 此處省略其他代碼
  
  RootViewController *rootViewController = [RootViewController new];
  //UIViewController *rootViewController = [UIViewController new];
  rootViewController.view = rootView;
  
  // 初始化 navigation
  self.navigation = [[UINavigationController alloc] init];
  // 將 RN 界面 push 進(jìn)來
  [self.navigation pushViewController:rootView.reactViewController animated:YES];
  [self.window addSubview:self.navigation.view];
  // 隱藏界面的 Header
  [self.navigation setNavigationBarHidden:YES];
  [self.window setRootViewController:self.navigation];
  
  [self.window makeKeyAndVisible];
  
  // ...
  
  return YES;
}

7. 創(chuàng)建輔助類鹦付,用來提供給RN調(diào)用原生方法

  • 在Xcode中創(chuàng)建一個名為RN2Native(類名自己定) 的CocoaTouchClass類,類繼承至 UIViewController择卦,創(chuàng)建并添加代碼后如下
    RN2Native.h
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>

@interface RN2Native : RCTEventEmitter<RCTBridgeModule>

/**
 * 接收消息
 */
- (void)receiveMessage:(NSDictionary *)obj;

@end

RN2Native.m

#import "RN2Native.h"
#import <NIMSDK/NIMSDK.h>
#import <SVProgressHUD/SVProgressHUD.h>
#import "NTESLoginManager.h"
#import "NTESService.h"
#import "NTESMainTabController.h"
#import "NTESSessionViewController.h"


@implementation RN2Native

// 檢測 js 端是否有監(jiān)聽事件
// 更多詳情請參考 http://facebook.github.io/react-native/docs/native-modules-ios.html#optimizing-for-zero-listeners
bool hasListeners;

// 將當(dāng)前類設(shè)置為主線程
- (dispatch_queue_t)methodQueue {
  return dispatch_get_main_queue();
}

// 導(dǎo)出模塊給 js
RCT_EXPORT_MODULE()

// 導(dǎo)出要發(fā)送給js端的方法
- (NSArray<NSString *> *) supportedEvents {
  return @[@"receiveMessage"];
}

// 跳到原生IM界面
RCT_EXPORT_METHOD(toYunXinIM) {
  [[NTESServiceManager sharedManager] start];
  NTESMainTabController *mainTab = [[NTESMainTabController alloc] initWithNibName:nil bundle:nil];
  
  UIWindow *window = [[UIApplication sharedApplication] keyWindow];
  UINavigationController *navigation = ((UINavigationController *) window.rootViewController);
  [navigation pushViewController:mainTab animated:YES];
}


// 登錄云信 IM
RCT_EXPORT_METHOD(login:(NSString *)account password:(NSString *)password resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
  [[[NIMSDK sharedSDK] loginManager]
   login:account
   token:password
   completion:^(NSError *error) {
     [SVProgressHUD dismiss];
     if (error == nil) {
       LoginData *sdkData = [[LoginData alloc] init];
       sdkData.account   = account;
       sdkData.token     = password;
       [[NTESLoginManager sharedManager] setCurrentLoginData:sdkData];
       
       NSInteger count = [[NIMSDK sharedSDK].conversationManager allUnreadCount];
       
       NSLog(@"云信ios版[%@]登錄成功敲长,未讀消息數(shù) %ld", account, count);
       NSDictionary *dic = @{
                             @"code": @"200",
                             @"unreadCount": [NSString stringWithFormat:@"%ld", count]
                             };
       resolve(dic);
     } else {
       NSString *errorCode = [NSString stringWithFormat:@"%ld", error.code];
       NSLog(@"登錄失敗郎嫁,錯誤碼 %@", errorCode);
       reject(errorCode, @"", error);
     }
   }
   ];
}

// 發(fā)起一對一聊天
RCT_EXPORT_METHOD(toP2PChat:(NSString *)userId) {
  UIWindow *window = [[UIApplication sharedApplication] keyWindow];
  UINavigationController *navigation = ((UINavigationController *) window.rootViewController);
  
  NIMSession *session = [NIMSession session:userId type:NIMSessionTypeP2P];
  NTESSessionViewController *vc = [[NTESSessionViewController alloc] initWithSession:session];
  [navigation pushViewController:vc animated:YES];
}

// 發(fā)送消息
RCT_EXPORT_METHOD(chatWithCS:(NSString *)csID message:(NSString *)textMsg) {
  // 構(gòu)造消息
  NIMMessage *message = [[NIMMessage alloc] init];
  message.text = textMsg;
  
  // 構(gòu)造會話
  NIMSession *session = [NIMSession session:csID type:NIMSessionTypeP2P];
  
  // 發(fā)送消息
  [[NIMSDK sharedSDK].chatManager sendMessage:message toSession:session error:nil];
}

// 發(fā)送 tip 提醒給指定用戶
RCT_EXPORT_METHOD(p2pTipMsg:(NSString *)userId message:(NSString *)textMsg) {
  //構(gòu)造消息
  NIMTipObject *tipObject = [[NIMTipObject alloc] init];
  NIMMessage *message     = [[NIMMessage alloc] init];
  message.messageObject   = tipObject;
  message.text            = textMsg;
  
  //構(gòu)造會話
  NIMSession *session = [NIMSession session:userId type:NIMSessionTypeP2P];
  
  //發(fā)送消息
  [[NIMSDK sharedSDK].chatManager sendMessage:message toSession:session error:nil];
}

// 退出云信 IM
RCT_EXPORT_METHOD(logout) {
  [[[NIMSDK sharedSDK] loginManager] logout:^(NSError *error)
   {
     NSLog(@"退出云信ios版");
     //     extern NSString *NTESNotificationLogout;
     //     [[NSNotificationCenter defaultCenter] postNotificationName:NTESNotificationLogout object:nil];
   }
   ];
}

// 發(fā)送事件 receiveMessage 給 js
- (void)receiveMessage:(NSDictionary *)obj {
  if (self.bridge == nil) {
    NSLog(@"bridge is null  %@", self.bridge);
  } else {
    NSLog(@"bridge has value %@", self.bridge);
  }

  if (hasListeners) {
    NSLog(@"React Native 端有監(jiān)聽函數(shù),此處應(yīng)該 sendEventWithName  %@", obj);
    [self sendEventWithName:@"receiveMessage" body:obj];
  }
}

// 獲取未讀消息數(shù)量
RCT_EXPORT_METHOD(fetchUnreadMessage) {
  // js端有監(jiān)聽函數(shù)時才執(zhí)行下面的代碼
  if (hasListeners) {
    NSInteger count = [[NIMSDK sharedSDK].conversationManager allUnreadCount];
    NSString *countStr = [NSString stringWithFormat:@"%ld", count];
    //  NSLog(@"未讀消息數(shù) %@", countStr);
    [self sendEventWithName:@"receiveMessage" body:@{@"unreadCount": countStr}];
  }
}

// Will be called when this module's first listener is added.
-(void)startObserving {
  hasListeners = YES;
  // Set up any upstream listeners or background tasks as necessary
}

// Will be called when this module's last listener is removed, or on dealloc.
-(void)stopObserving {
  hasListeners = NO;
  // Remove upstream listeners, stop unnecessary background tasks
}

@end

上述代碼中導(dǎo)出給js端的方法根據(jù)業(yè)務(wù)需要進(jìn)行取舍祈噪,改動原生代碼需要重新 run 一下項目

此時可以在 RN 里寫跳轉(zhuǎn)到 原生 IM 界面的方法了泽铛。

8. 修改云信 Demo 中的 appKey

修改 Classes/Util/NTESDemoConfig.m 第30行 _appKey,值為你在云信官網(wǎng)注冊的钳降,如下圖所示

第一步復(fù)制 App Key.png
第二步粘貼復(fù)制的 App Key

修改成自己的 App Key 后厚宰,表示將云信用戶管理,消息記錄管理等接入到自己的后臺遂填,還需修改一個地方铲觉,(如果你的項目中云信登錄密碼沒使用md5加密,則不需要進(jìn)行這一步操作)吓坚。

密碼使用MD5加密方式

上述操作完成后撵幽,然后我們在 RN 里調(diào)用我們在 RN2Native.m 文件里暴露給 js 端的方法,示例如下

// 調(diào)用原生暴露給js的登錄方法
NativeModules.RN2Native.login(yunxinId, yunxingToken)
    .then(data => {
        console.log(`返回結(jié)果`, data);
    });

// 跳轉(zhuǎn)到 IM 界面
NativeModules.RN2Native.toYunXinIM();

// 發(fā)送 tip 消息給指定用戶
NativeModules.RN2Native.p2pTipMsg('p153760', '咨詢');

// 發(fā)起一對一的聊天窗口
NativeModules.RN2Native.toP2PChat(yunxinId);
// ...

此處有兩個要提的就是礁击,當(dāng)我們從 RN 跳轉(zhuǎn)到原生 IM 的時候盐杂,出現(xiàn)的問題,問題如下圖所示哆窿。

情形1:沒有返回到RN界面的按鈕

情形1:沒有返回到RN界面的按鈕

分析:
此界面是從 RN 界面跳轉(zhuǎn)過來的链烈,若此時我們想回到原來的RN界面,怎么辦呢挚躯?
別著急强衡,下面就是在此界面上添加一個返回的按鈕,返回到我們原來的RN界面码荔。

編輯文件Classes/Sections/SessionList/ViewController/NTESSessionListViewController.m

- (void)viewDidLoad{
     // ... 
    [self setUpNavItem];
}

- (void)setUpNavItem{
    // 設(shè)置左邊返回到 react native 界面的返回按鈕(icon_back_normal.png在images.xcassets中)
    UIImage *backImage = [UIImage imageNamed:@"icon_back_normal.png"];
    UIBarButtonItem *barBackButton = [[UIBarButtonItem alloc]
                                      initWithImage :backImage
                                      style         :UIBarButtonItemStylePlain
                                      target        :self
                                      action        :@selector(backAction:)];
    self.navigationItem.leftBarButtonItem = barBackButton;
  
    // 右邊按鈕
    // ...
}


// 返回到上一個視圖
- (void)backAction:(id)sender {
  UIWindow *window = [[UIApplication sharedApplication] keyWindow];
  UINavigationController *navigation = (UINavigationController *)window.rootViewController;
  [navigation popViewControllerAnimated:YES];
}

添加完再次運行之后漩勤,從 RN 界面跳轉(zhuǎn)到原生IM界面時,界面如下

點擊返回即可返回到原來的 RN 界面

情形2:發(fā)起一對一聊天時缩搅,無法返回到原界面

情形2:發(fā)起一對一聊天時越败,無法返回到原界面

上述情況需要修改 Classes/Sections/Session/ViewController/NTESSesionViewController.m 文件。

  • 在聲明變量的地方加個變量硼瓣,用于保存當(dāng)前聊天頁面是從哪個界面跳轉(zhuǎn)過來的究飞。
// 記錄前一個頁面name
@property (nonatomic,strong)    NSString *preViewCtrlName;
  • 新增/編輯 - (void)viewWillAppear:(BOOL)animated 函數(shù),代碼如下
- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    [[self navigationController] setNavigationBarHidden:NO animated:animated];
    
  // 獲取 UIViewController 總數(shù)量
  NSInteger count = [[[self navigationController] viewControllers] count];
  // 根據(jù)索引獲取前一個 UIViewController
  UIViewController *vc = [self.navigationController.viewControllers objectAtIndex:count - 2];
  // 獲取類名
  self.preViewCtrlName = NSStringFromClass([vc class]);
  
  // 隱藏"返回"按鈕上顯示未讀消息數(shù)量
  if ([@"UIViewController" isEqualToString:self.preViewCtrlName]) {
    [[self navigationItem] setLeftBarButtonItem:Nil];
  }
}
  • 編輯 - (void)viewWillDisappear:(BOOL)animated 函數(shù)巨双,代碼如下
- (void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    [[NIMSDK sharedSDK].mediaManager stopRecord];
    [[NIMSDK sharedSDK].mediaManager stopPlay];
  
    if ([@"UIViewController" isEqualToString:self.preViewCtrlName]) {
      [[self navigationController] setNavigationBarHidden:YES animated:animated];
    } else {
      [[self navigationController] setNavigationBarHidden:NO animated:animated];
    }
}

到此處基本已經(jīng)告一段落了噪猾。


屏蔽或刪除掉不需要的功能,減少打包app的大小

  • 刪除云信里的 關(guān)于 頁面相關(guān)文件筑累,刪除目錄 Classes/Sections/Settings 下的 NTESAboutViewController.h , NTESAboutViewController.m, NTESAboutViewController.xib 文件。

  • 刪除文件夾 Classes/Sections/Settings/NetDetect

  • 刪除文件夾 Classes/Sections/Settings/Log

  • 刪除文件夾 Classes/Sections/Login/ViewController

  • 刪除/注銷 Classes/Sections/Setting/NTESSetingViewController.m中的

同時記得把他們相應(yīng)調(diào)用的函數(shù)也刪掉

@{
    Title      :@"查看日志",
    CellAction :@"onTouchShowLog:",
},
@{
    Title      :@"上傳日志",
    CellAction :@"onTouchUploadLog:",
},
@{
     Title      :@"音視頻網(wǎng)絡(luò)探測",
      CellAction :@"onTouchNetDetect:",
},
//...

@{
      HeaderTitle:@"",
      RowContent :@[
          @{
               Title        : @"注銷",
                CellClass    : @"NTESColorButtonCell",
                CellAction   : @"logoutCurrentAccount:",
                 ExtraInfo    : @(ColorButtonCellStyleRed),
                 ForbidSelect : @(YES)
               },
         ],
          FooterTitle:@"",
},

  • 搜索“云信 Demo”替換為你的app名稱丝蹭。

  • Classes/Common/Controller/NTESMainTabController.m 241行慢宗,將"云信"改成"消息"

  • 注釋/刪除掉直播間功能,Classes/Common/Controller/NTESMainTabController.m中的以下代碼


//...

#define TabBarCount 4 // 將4改為3

//...

@(NTESMainTabTypeChatroomList): @{
                             TabbarVC           : @"NTESChatroomListViewController",
                             TabbarTitle        : @"直播間",
                             TabbarImage        : @"icon_chatroom_normal",
                             TabbarSelectedImage: @"icon_chatroom_pressed",
                             },

刪除 Images.xcassets 里一些沒用用到的圖片 (刪除前先搜索下是否還有其他地方引用)

上述刪掉的代碼,在其他地方有引用镜沽,記得也需要刪除/注釋

修改 Classes/Util/NTESNotificationCenter.m 文件敏晤,修改后大致如下

下面這段代碼的用途是,當(dāng)云信收到消息時缅茉,將獲取未讀消息總數(shù)嘴脾,然后將獲取到的未讀消息總數(shù)發(fā)送給js端。js端需要監(jiān)聽事件蔬墩,根據(jù)獲取到的未讀消息數(shù)去更新頁面顯示的未讀消息數(shù)译打。

#import "RN2Native.h"

//... 

#pragma mark - NIMChatManagerDelegate
- (void)onRecvMessages:(NSArray *)messages
{
    static BOOL isPlaying = NO;
    if (isPlaying) {
        return;
    }
    isPlaying = YES;
    [self playMessageAudioTip];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        isPlaying = NO;
    });
    [self checkMessageAt:messages];

    // 獲取未讀消息總數(shù)
    NSInteger count = [[NIMSDK sharedSDK].conversationManager allUnreadCount];
    NSString *countStr = [NSString stringWithFormat:@"%ld", count];
    [[[RN2Native alloc] init] receiveMessage:@{@"unreadCount": countStr}];
}

本文章會持續(xù)更新,有需要的可以關(guān)注下拇颅。^ - ^

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奏司,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子樟插,更是在濱河造成了極大的恐慌韵洋,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件黄锤,死亡現(xiàn)場離奇詭異搪缨,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)鸵熟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進(jìn)店門副编,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人旅赢,你說我怎么就攤上這事齿桃。” “怎么了煮盼?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵短纵,是天一觀的道長。 經(jīng)常有香客問我僵控,道長香到,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任报破,我火速辦了婚禮悠就,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘充易。我一直安慰自己梗脾,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布盹靴。 她就那樣靜靜地躺著炸茧,像睡著了一般瑞妇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上梭冠,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天辕狰,我揣著相機(jī)與錄音,去河邊找鬼控漠。 笑死蔓倍,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的盐捷。 我是一名探鬼主播偶翅,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼毙驯!你這毒婦竟也來了倒堕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤爆价,失蹤者是張志新(化名)和其女友劉穎垦巴,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铭段,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡骤宣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了序愚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片憔披。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖爸吮,靈堂內(nèi)的尸體忽然破棺而出芬膝,到底是詐尸還是另有隱情,我是刑警寧澤形娇,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布锰霜,位于F島的核電站,受9級特大地震影響桐早,放射性物質(zhì)發(fā)生泄漏癣缅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一哄酝、第九天 我趴在偏房一處隱蔽的房頂上張望友存。 院中可真熱鬧,春花似錦陶衅、人聲如沸屡立。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽侠驯。三九已至抡秆,卻和暖如春奕巍,著一層夾襖步出監(jiān)牢的瞬間吟策,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工的止, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留檩坚,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓诅福,卻偏偏與公主長得像匾委,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子氓润,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,440評論 2 348

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