版本
本文已 ReactNative 集成 NIM_iOS_Demo_v4.2.0 为例。Xcode
版本为9.0
本人是在已有的ReactNative(以下简称RN)工程下集成云信IM(其他各版本集成方式大同小异)
1. 使用 cocoapods 来安装网易云信依赖
1.首先在 terminal 里进入到自己RN项目ios目录下, 运行 pod init
,(如果电脑没安装cocoapods的,请先安装 cocoapods ),运行完成后,会在当前所在目录下生成 Podfile 文件。
2. 在 Podfile 里添加网易云信的依赖
terminal 进入到 RN ios目录下执行 pod init,执行后在 ios 目录下生成 Podfile 文件。
在下载好的云信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内容有点不一致,如果不太清楚各项配置的意思,按照我的配置来就行了。
- 添加完成后在 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.xcconfig
和main.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
- 打开云信demo NIM.xcworkspace 查看云信的 Header Search Paths 配置,照猫画虎的搬到自己项目里来。
记得把
$(SRCROOT)/../NIMKit/NIMKit
这项给去掉,因为在前面我们把NIMKit使用 cocoapods 安装好了,不用像demo里一样把 NIMKit 这个文件夹复制下来。使用 cocoapods 来安装,非常方便日后的升级和版本变更。
- 将demo中的
Images.xcassets
与自己工程中的Images.xcassets
合并
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.lock
和Mall.xcworkspace
,然后在terminal
中的项目ios
目录下运行pod install
重新安装下。如出现其他错误,根据错误提示修改或Google解决。
此步完成后,基本上可以正常运行起来了。集成配置也基本完成,后续的步骤主要是对接 RN 界面和 原生界面互相跳转。
6. 原生添加 navigation 方便后面RN界面和原生界面跳转
- 项目正常运行后需要修改几个地方,比如从 ReactNative 页面跳转到 原生页面的适配问题等。
- 在
AppDelegate.h
文件中声明变量 navigation
@property (nonatomic, strong) UINavigationController *navigation;
- 修改
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 后,表示将云信用户管理,消息记录管理等接入到自己的后台,还需修改一个地方,(如果你的项目中云信登录密码没使用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界面的按钮
分析:
此界面是从 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界面时,界面如下
情形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}];
}
本文章会持续更新,有需要的可以关注下。^ - ^