ReactNative集成网易云信IM Demo(iOS版)

版本

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

1. 使用 cocoapods 来安装网易云信依赖

1.首先在 terminal 里进入到自己RN项目ios目录下, 运行 pod init ,(如果电脑没安装cocoapods的,请先安装 cocoapods ),运行完成后,会在当前所在目录下生成 Podfile 文件。

  1. 下载云信 IM demo 源码
    前往 网易云信 下载iOS版 云信IM demo

2. 在 Podfile 里添加网易云信的依赖

  1. terminal 进入到 RN ios目录下执行 pod init,执行后在 ios 目录下生成 Podfile 文件。

  2. 在下载好的云信demo里 NIMDemo 目录下找到 Podfile 文件,复制文件里面的内容到自己的 Podfile 文件里。此部完成后的结果如下

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'
  # 网易云信的 NIMKit 包
  pod 'NIMKit/Full', '~> 1.9.1'
end

此处和源码里的podfile内容有点不一致,如果不太清楚各项配置的意思,按照我的配置来就行了。

  1. 添加完成后在 terminal 窗口里(Podfile 目录中)执行 pod install,执行完成后会在 ios 目录下生成对应的 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 内容和自己工程中的 AppDelegate.m 文件中的代码进行合并,并修改和去掉部分不需要的代码。

如下代码是我的AppDelegate.m文件的内容

/**
 * 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];
  
  // 因为本项目想实现的功能是启动进入的是 RN 界面,在需要的时候,从 RN 页面跳转到原生 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
{
  //目前只有红包跳转
  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 = @"你被服务器踢下线";
      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
{
  //只有连接发生严重错误才会走这个回调,在这个回调里应该登出,返回界面等待用户手动重新登录。
  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 之前先进行配置信息的注册,如是否使用新路径,是否要忽略某些通知,是否需要多端同步未读数等
  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 是应用的标识,不同应用之间的数据(用户、消息、群组等)是完全隔离的。
  //如需打网易云信 Demo 包,请勿修改 appkey ,开发自己的应用时,请替换为自己的 appkey 。
  //并请对应更换 Demo 代码中的获取好友列表、个人信息等网易云信 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 - 登录错误回调
- (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中设置文件引用

  • Xcode左边导航中点击项目,Build Settings --> Prefix Header 的值设置为 $(PROJECT_DIR)/Mall/NIMDemo-Prefix.pch
设置Prefix Header项
  • 打开云信demo NIM.xcworkspace 查看云信的 Header Search Paths 配置,照猫画虎的搬到自己项目里来。

记得把 $(SRCROOT)/../NIMKit/NIMKit 这项给去掉,因为在前面我们把NIMKit使用 cocoapods 安装好了,不用像demo里一样把 NIMKit 这个文件夹复制下来。使用 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) 一下,然后运行试试。出现错误可尝试删除ios目录下的 Pods Podfile.lockMall.xcworkspace ,然后在 terminal中的项目 ios目录下运行 pod install 重新安装下。如出现其他错误,根据错误提示修改或Google解决。


此步完成后,基本上可以正常运行起来了。集成配置也基本完成,后续的步骤主要是对接 RN 界面和 原生界面互相跳转。

6. 原生添加 navigation 方便后面RN界面和原生界面跳转

  1. 项目正常运行后需要修改几个地方,比如从 ReactNative 页面跳转到 原生页面的适配问题等。
  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 进来
  [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. 创建辅助类,用来提供给RN调用原生方法

  • 在Xcode中创建一个名为RN2Native(类名自己定) 的CocoaTouchClass类,类继承至 UIViewController,创建并添加代码后如下
    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 端是否有监听事件
// 更多详情请参考 http://facebook.github.io/react-native/docs/native-modules-ios.html#optimizing-for-zero-listeners
bool hasListeners;

// 将当前类设置为主线程
- (dispatch_queue_t)methodQueue {
  return dispatch_get_main_queue();
}

// 导出模块给 js
RCT_EXPORT_MODULE()

// 导出要发送给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版[%@]登录成功,未读消息数 %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);
     }
   }
   ];
}

// 发起一对一聊天
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];
}

// 发送消息
RCT_EXPORT_METHOD(chatWithCS:(NSString *)csID message:(NSString *)textMsg) {
  // 构造消息
  NIMMessage *message = [[NIMMessage alloc] init];
  message.text = textMsg;
  
  // 构造会话
  NIMSession *session = [NIMSession session:csID type:NIMSessionTypeP2P];
  
  // 发送消息
  [[NIMSDK sharedSDK].chatManager sendMessage:message toSession:session error:nil];
}

// 发送 tip 提醒给指定用户
RCT_EXPORT_METHOD(p2pTipMsg:(NSString *)userId message:(NSString *)textMsg) {
  //构造消息
  NIMTipObject *tipObject = [[NIMTipObject alloc] init];
  NIMMessage *message     = [[NIMMessage alloc] init];
  message.messageObject   = tipObject;
  message.text            = textMsg;
  
  //构造会话
  NIMSession *session = [NIMSession session:userId type:NIMSessionTypeP2P];
  
  //发送消息
  [[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];
   }
   ];
}

// 发送事件 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 端有监听函数,此处应该 sendEventWithName  %@", obj);
    [self sendEventWithName:@"receiveMessage" body:obj];
  }
}

// 获取未读消息数量
RCT_EXPORT_METHOD(fetchUnreadMessage) {
  // js端有监听函数时才执行下面的代码
  if (hasListeners) {
    NSInteger count = [[NIMSDK sharedSDK].conversationManager allUnreadCount];
    NSString *countStr = [NSString stringWithFormat:@"%ld", count];
    //  NSLog(@"未读消息数 %@", 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

上述代码中导出给js端的方法根据业务需要进行取舍,改动原生代码需要重新 run 一下项目

此时可以在 RN 里写跳转到 原生 IM 界面的方法了。

8. 修改云信 Demo 中的 appKey

修改 Classes/Util/NTESDemoConfig.m 第30行 _appKey,值为你在云信官网注册的,如下图所示

第一步复制 App Key.png
第二步粘贴复制的 App Key

修改成自己的 App Key 后,表示将云信用户管理,消息记录管理等接入到自己的后台,还需修改一个地方,(如果你的项目中云信登录密码没使用md5加密,则不需要进行这一步操作)。

密码使用MD5加密方式

上述操作完成后,然后我们在 RN 里调用我们在 RN2Native.m 文件里暴露给 js 端的方法,示例如下

// 调用原生暴露给js的登录方法
NativeModules.RN2Native.login(yunxinId, yunxingToken)
    .then(data => {
        console.log(`返回结果`, data);
    });

// 跳转到 IM 界面
NativeModules.RN2Native.toYunXinIM();

// 发送 tip 消息给指定用户
NativeModules.RN2Native.p2pTipMsg('p153760', '咨询');

// 发起一对一的聊天窗口
NativeModules.RN2Native.toP2PChat(yunxinId);
// ...

此处有两个要提的就是,当我们从 RN 跳转到原生 IM 的时候,出现的问题,问题如下图所示。

情形1:没有返回到RN界面的按钮

情形1:没有返回到RN界面的按钮

分析:
此界面是从 RN 界面跳转过来的,若此时我们想回到原来的RN界面,怎么办呢?
别着急,下面就是在此界面上添加一个返回的按钮,返回到我们原来的RN界面。

编辑文件Classes/Sections/SessionList/ViewController/NTESSessionListViewController.m

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

- (void)setUpNavItem{
    // 设置左边返回到 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 界面跳转到原生IM界面时,界面如下

点击返回即可返回到原来的 RN 界面

情形2:发起一对一聊天时,无法返回到原界面

情形2:发起一对一聊天时,无法返回到原界面

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

  • 在声明变量的地方加个变量,用于保存当前聊天页面是从哪个界面跳转过来的。
// 记录前一个页面name
@property (nonatomic,strong)    NSString *preViewCtrlName;
  • 新增/编辑 - (void)viewWillAppear:(BOOL)animated 函数,代码如下
- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    [[self navigationController] setNavigationBarHidden:NO animated:animated];
    
  // 获取 UIViewController 总数量
  NSInteger count = [[[self navigationController] viewControllers] count];
  // 根据索引获取前一个 UIViewController
  UIViewController *vc = [self.navigationController.viewControllers objectAtIndex:count - 2];
  // 获取类名
  self.preViewCtrlName = NSStringFromClass([vc class]);
  
  // 隐藏"返回"按钮上显示未读消息数量
  if ([@"UIViewController" isEqualToString:self.preViewCtrlName]) {
    [[self navigationItem] setLeftBarButtonItem:Nil];
  }
}
  • 编辑 - (void)viewWillDisappear:(BOOL)animated 函数,代码如下
- (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];
    }
}

到此处基本已经告一段落了。


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

  • 删除云信里的 关于 页面相关文件,删除目录 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中的

同时记得把他们相应调用的函数也删掉

@{
    Title      :@"查看日志",
    CellAction :@"onTouchShowLog:",
},
@{
    Title      :@"上传日志",
    CellAction :@"onTouchUploadLog:",
},
@{
     Title      :@"音视频网络探测",
      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 文件,修改后大致如下

下面这段代码的用途是,当云信收到消息时,将获取未读消息总数,然后将获取到的未读消息总数发送给js端。js端需要监听事件,根据获取到的未读消息数去更新页面显示的未读消息数。

#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];

    // 获取未读消息总数
    NSInteger count = [[NIMSDK sharedSDK].conversationManager allUnreadCount];
    NSString *countStr = [NSString stringWithFormat:@"%ld", count];
    [[[RN2Native alloc] init] receiveMessage:@{@"unreadCount": countStr}];
}

本文章会持续更新,有需要的可以关注下。^ - ^

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

推荐阅读更多精彩内容