600字范文,内容丰富有趣,生活中的好帮手!
600字范文 > 5页面调用原生相机_React Native与原生通信全梳理(iOS端)

5页面调用原生相机_React Native与原生通信全梳理(iOS端)

时间:2023-11-29 18:32:05

相关推荐

5页面调用原生相机_React Native与原生通信全梳理(iOS端)

emmm…… 先说个题外话,时隔一年,再遇RN,较之以前唯一不同的一点就是遇到的坑终于有人先踩了本文会通过原生与RN页面相互跳转、方法间的相互调用、以及H5页面调用原生页面进而调用RN页面等方面来阐述原生与RN间的通信。不要疑惑为啥子会有这种撒娇三连的操作,我也只能摊手道:存在即合理(无奈╮(╯▽╰)╭.gif)。

一、原生与RN通信

先做点准备工作叭~ 通过`react-native init`创建一个RN的新项目,此后将会得到一个内部带有`ios`和`android`目录的文件夹。把这两个目录下的文件换成自己的项目。位置如下图所示。

修改podfile文件,将RN需要的库引入到自己的项目中。

pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector" pod 'FBReactNativeSpec', :path => "../node_modules/react-native/Libraries/FBReactNativeSpec" pod 'RCTRequired', :path => "../node_modules/react-native/Libraries/RCTRequired" pod 'RCTTypeSafety', :path => "../node_modules/react-native/Libraries/TypeSafety" pod 'React', :path => '../node_modules/react-native/' pod 'React-Core', :path => '../node_modules/react-native/' pod 'React-CoreModules', :path => '../node_modules/react-native/React/CoreModules' pod 'React-Core/DevSupport', :path => '../node_modules/react-native/' pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS' pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation' pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob' pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image' pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS' pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network' pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings' pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text' pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration' pod 'React-Core/RCTWebSocket', :path => '../node_modules/react-native/' pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact' pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi' pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor' pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector' pod 'ReactCommon/jscallinvoker', :path => "../node_modules/react-native/ReactCommon" pod 'ReactCommon/turbomodule/core', :path => "../node_modules/react-native/ReactCommon" pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga' pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec' pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec' pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'

1、原生跳RN页面

`RCTRootView`是一个可以将RN视图封装到原生组件中并且提供联通原生和被托管端接口的UIView容器。`properties`属性用于在React中将信息从父组件传递给子组件。

`RCTRootView`在初始化函数之时,通过类型为`NSDictionary`的`initialProperties`可以将任意属性传递给RN应用。这一字典参数会在RN内部被转化为可供组件调用的JSON对象。

1) 创建RN的桥接管理类(单例)实现`RCTBridgeDelegate`协议

// .h文件#import #import #import #import #import @interface XXXRCTManager : NSObject+ (instancetype)shareInstance;// 全局唯一的bridge@property (nonatomic, readonly, strong) RCTBridge *bridge;@end//.m文件static XXXRCTManager *_instance = nil;+ (instancetype)shareInstance{ if (_instance == nil) { _instance = [[self alloc] init]; } return _instance;}+ (instancetype)allocWithZone:(struct _NSZone *)zone{ if (_instance == nil) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _instance = [super allocWithZone:zone]; }); } return _instance;}-(instancetype)init{ if (self = [super init]) { _bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:nil]; } return self;}

实现`sourceURLForBridge`方法。调试模式下,读取`index`文件资源,打包则读取`jsbundle`中的资源。

#pragma mark - RCTBridgeDelegate- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {# if DEBUG return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];# else return [[NSBundle mainBundle] URLForResource:@"index" withExtension:@"jsbundle"];#endif

2) 创建容纳RN页面的控制器

//.h@interface XXXReactHomeViewController : UIViewController@property(nonatomic,strong)NSString *rnPath; // 传递给RN的数据 页面名称@end

在.m文件中初始化`RCTRootView`,并将其添加到控制器页面上

NSDictionary *props = @{@"path" : self.rnPath};RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:[SZLRCTManager shareInstance].bridge moduleName:@"RN中AppRegistry注册的名字"initialProperties:props];

如此一来,iOS页面就能跳转到RN项目的首页了。轻松加愉快啊。

2、RN页面跳原生页面及调用原生方法

`RCTBridgeModule`是定义好的protocol,实现该协议的类,会自动注册到iOS代码中对应的Bridge中。Object-C Bridge上层负责与Object-C通信,下层负责和JavaScript Bridge通信,而JavaScript Bridge负责和JavaScript通信,如此就能实现RN与iOS原生的相互调用。

需要注意的是:所有实现`RCTBridgeModule`的类都必须包括这条宏:`RCT_EXPORT_MODULE()`。它的作用是自动注册一个Module,当原生的桥加载之时,这个Module可以在JavaScript Bridge中调用。

先来看一下它的定义:

#define RCT_EXPORT_MODULE(js_name) RCT_EXTERN void RCTRegisterModule(Class); + (NSString *)moduleName { return @#js_name; } + (void)load { RCTRegisterModule(self); }

由此可以看出`RCT_EXPORT_MODULE`接受字符串作为其Module的名称,如果不设置名称的话默认就使用类名作为Module的名称。

1)新建类实现`RCTBridgeModule`协议

// .h@interface xxxModule : NSObject@end//.mRCT_EXPORT_METHOD(goBack){ // 用通知的方式返回原生页面 dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:@"configBack" object:nil]; }); }

2) 在XXXReactHomeViewController即承载RN页面的控制器中,接收通知,并实现从RN返回到原生页面的方法

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(navagateBack) name:@"configBack" object:nil];

- (void)navagateBack{ [self.navigationController popViewControllerAnimated:YES];}

3)在RN的界面中,通过`NativeModules`引入原生的module类,并调用返回原生界面的方法。

import { NativeModules,} from 'react-native';

onPressBack={() => { NativeModules.xxxModule.goBack(); }}

以上骚操作已经可以满足RN跳转到原生界面的需求了。

however,在实际项目中,这还远远不够。比如说me正在进行的项目,需要将登录获取到的token传递给RN界面,一旦失效,则立即唤起原生的登录页面。

咳咳,好累ヽ( ̄▽ ̄)و坐直了。

…………………………………………假装我是分割线……………………………………

3、将原生参数传递给RN

将原生的参数传递给RN,或是让RN实现原生的某些操作可以通过`RCT_EXPORT_METHOD`实现。它是用来定义被JavaScript调用的方法的宏。`RCT_EXTERN_METHOD`调用了宏`RCT_EXTERN_REMAP_METHOD`。下面是该宏的定义:

#define RCT_EXTERN_REMAP_METHOD(js_name, method) + (NSArray *)RCT_CONCAT(__rct_export__,RCT_CONCAT(js_name, RCT_CONCAT(__LINE__, __COUNTER__))) {return @[@#js_name, @#method]; }

由此可以看出,它的作用是在`RCT_EXPORT_MODULE`定义的Module下面,定义一个可以被JavaScript调用的方法。

RCT_EXPORT_MODULE的使用,需要写入方法名,参数以及完整的实现。

1) 原生定义方法

// 获取tokenRCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getToken){ NSString *token = [[NSUserDefaults standardUserDefaults]objectForKey:@"token"]; return token;}// 退出登录RCT_EXPORT_METHOD(signOut){ dispatch_async(dispatch_get_main_queue(), ^{ AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; LoginViewController *loginVC = [[LoginViewController alloc]init]; UINavigationController *nav = [[UINavigationController alloc]initWithRootViewController:loginVC]; appDelegate.window.rootViewController = nav; }); }

2) RN方调用

import { NativeModules } from 'react-native';

// 拿token requestObj.headers.Authorization = NativeModules.config.getToken(); // 调用原生的退出登录方法 NativeModules.XXXModule.signOut();

4、 多入口跳转到RN不同的页面

项目中有这样一个需求,要从不同的原生页面进入到不同的RN页面。此时,单纯通过导航跳转就无法解决该问题了。

在初始化`RCTRootView`之时,通过`initWithBridge:(RCTBridge *)bridge`方法将要展示的页面路径通过属性传递给RN。RN方接收到信息,再根据传入的路径决定要跳转到哪个页面。

1) 原生端传入数据

创建RCTRootView的代码在上文中已给出。在需要跳转的类中,传递字段。

XXXReactHomeViewController *reactVC = [[XXXReactHomeViewController alloc]init]; reactVC.rnPath = @"SugarStack"; [self.navigationController pushViewController:reactVC animated:YES];

2) RN端接收属性并跳转页面

在本项目中,采用的是`react-navigation`导航栏控制器。

飞机票:[react-navigation](/zh-Hans/)

(好气哦,不能直接插链接)

import { createAppContainer, createSwitchNavigator } from 'react-navigation';import { createStackNavigator } from 'react-navigation-stack';

每个栈中都存放不同页面。如:

const SugarStack = createStackNavigator({ SugarFriend, SugarFriendDetail, RosterSearch,});

将栈放入到导航中去,一次只显示一个屏幕。通过从原生接收的参数`path`来判断要显示哪个屏幕。

const App = function (props) { const AppNavigator = createSwitchNavigator( {AppStack,SugarStack, }, {initialRouteName: props.path || 'AppStack', }, ); const Navigation = createAppContainer(AppNavigator); return ();};

5、 H5页面调用原生页面进而调用RN页面(吐血三连)

这波骚操作源于项目本身就是一个H5与原生混合的app,其中有一个酱紫的功能。H5页显示一条消息提醒用户有待办事项,而用户点击进行处理的操作是需要跳转到RN页面的。如果按照前文中带参跳转也只能跳转到RN栈的第一个页面。因此需要使用到`deep-link`方案。深度链接是一项可以让一个App通过一个URL地址打开,之后导航至特定页面或者资源,或者展示特定UI的技术

传送门:[Deep linking](/docs/zh-Hans/deep-linking.html)

1)RN配置导航容器,使其能够从传入应用程序的 URI 中提取路径。

const SimpleApp = createAppContainer(createStackNavigator({...}));const prefix = 'mychat://';const MainApp = () => ;

2)在Appdelegate文件中,将iOS应用程序配置为使用 mychat:// URI 方案打开。

- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options{ return [RCTLinkingManager application:app openURL:url options:options];}

3)在xcode中,设置`info`->`URL Type`为mychat

二、打包

1) 导出js bundle包和图片资源

终端进入RN项目的根目录下创建文件夹,此处名为`release_ios`

react-native bundle --entry-file index.js --platform ios --dev false --bundle-output release_ios/main.jsbundle --assets-dest release_ios/

entry-file代表入口文件,platform是平台的意思,后面一串是指输出资源到哪个文件或文件夹。

2) 将资源包导入到iOS项目。

通过上述命令,可以在`relise_ios`文件夹下找到`assets`和`main.jsbundle`。将这两个文件拖入到iOS工程下。勾选第一和第三选项

3) 打包发布

在顶部菜单栏找到xCode->Product->Archive打ipa包

三、调试中遇见的一点小问题

1、iOS真机调试,reload的时候永远没反应,摇一摇弹出的调试界面也差了好几个按钮。把上文中所打的`main.jsbundle`移除后,真机运行直接奔溃。真真是一入红屏深似海:

Connection to http://localhost:8081/debugger-proxy?role=client timed out. Are you running node proxy? If you are running on the device, check if you have the right IP address in RCTWebSocketExecutor.m.

AFN弹出提示:“未能找到使用指定主机名的服务器”。也就是说RN并未调起js server。

确保mac和手机连的是同一网络之后,去xCode中搜索`域名.xip.io`。发现并没有这个文件。

在受到这两篇文章的启发之后,才明白

传送门:

[在设备上运行](/docs/running-on-device/)

[iOS 真机 No bundle URL present](/u013531215/article/details/82820350)

我的iOS项目是从别处拷贝过来,而ip.txt文件是在没有设置SKIP_BUNDLING的情况下初次构建的时候创建的。在构建app之后,加入做了clean操作或者拷贝到其他机器,创建ip.txt的步骤就被省略了。

解决方法是:到`guessPackagerHost`方法中,不要返回localhost,直接返回本机地址即可。

2、关于null is not an object(evaluating '_RNGestureHandlerModule.default.Direction')

RN环境在6.0以上,React-navigation在4.x。重装过pod或者node module还是无济于事。遂在想是不是没有在podfile文件中加入。之后查询到该信息。

pod 'RNGestureHandler', :podspec => '../node_modules/react-native-gesture-handler/RNGestureHandler.podspec'

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。