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"];#endif2) 創建容納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](https://reactnavigation.org/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](https://reactnavigation.org/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`。發現并沒有這個文件。
在受到這兩篇文章的啟發之后,才明白
傳送門:
[在設備上運行](https://reactnative.cn/docs/running-on-device/)
[iOS 真機 No bundle URL present](https://blog.csdn.net/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'總結
以上是生活随笔為你收集整理的5页面调用原生相机_React Native与原生通信全梳理(iOS端)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 光大信用卡欠款2年没还什么结果
- 下一篇: 民生全币种信用卡可以国内消费吗