首先在我們的Xcode文件中旋讹,新建一個 View 用來實現(xiàn)我們要封裝的控件功能。我這里取名為 TakePhotoView夏醉。然后經過一番編碼(具體功能自己實現(xiàn))之后琼懊,TakePhotoView 就可以為我所用皱埠。
然后這個時候呢肮帐,我們需要再創(chuàng)建一個 View 繼承于 RCTViewManager。在我的項目中边器,我取名為: TakePhotoManager 训枢。這個文件的作用是用來橋接 RN 層于原生層的通信。
TakePhotoManager.h 文件中的代碼如下:
#import <React/RCTViewManager.h>
@interface TakePhotoManager : RCTViewManager<RCTBridgeModule>
@end
這個時候忘巧,我們需要對 TakePhotoManager.m 文件進行一些編碼恒界,來實現(xiàn)我們的橋接功能。 TakePhotoManager.m 頁面的代碼如下:
#import "TakePhotoManager.h"
#import "TakePhotoView.h"
@implementation TakePhotoManager
// 標記宏(必要)
RCT_EXPORT_MODULE()
- (UIView *)view {
TakePhotoView *takePhotoView = [[TakePhotoView alloc] init];
return takePhotoView;
}
@end
然后在 RN 的項目中砚嘴,我們新建一個頁面十酣,用來承接這個 TakePhotoManager涩拙。在 RN 層中,我們新建一個頁面耸采,叫做 TakePhotoiOS.js兴泥。在這個頁面中,我們需要引入在 原生層中暴露出來的 TakePhoto 頁面虾宇。所以搓彻, TakePhotoiOS.js的代碼如下:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
requireNativeComponent,
NativeModules,
} from 'react-native';
// 該方法將 原生層TakePhotoManager 中 return 出來的 View 賦值給 RNTakePhoto。這個時候 RNTakePhoto 就是我們在原生中中的頁面了嘱朽。
// requireNativeComponent() 該方法中有兩個參數(shù)旭贬,第一個是原生層暴露的UIView,另一個是在RN層要承接的 Class搪泳。在這里我們可以看到稀轨,原生層暴露的UIView的文件叫做 TakePhotoManager,而在這里用的話岸军,只用TakePhoto靶端。這只能說明,原生層的封裝需要按照一定的規(guī)則來做凛膏。
const RNTakePhoto = requireNativeComponent('TakePhoto', TakePhotoiOS);
class TakePhotoiOS extends Component {
constructor(props) {
super(props);
}
render() {
return (
<RNTakePhoto
style={styles.container}
/>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'transparent',
},
});
module.exports = TakePhotoiOS;
到了這一步看似我們的封裝工作就完成了杨名。運行一遍,發(fā)現(xiàn)沒有報錯猖毫,頁面也是正常跳轉的台谍;但是呢在這個時候,我們發(fā)現(xiàn)吁断,相機的視圖是一片空白趁蕊,并沒有按照我們想象中的,出現(xiàn)預覽圖仔役。這個時候我們檢查了代碼之后掷伙,發(fā)現(xiàn),原來在頁面將要加載的時候又兵,我們需要讓我們的相機開始工作任柜。那么這個時候,我們就需要在 TakePhotoiOS.js 文件中增加如下代碼:
// 視圖加載完成
componentDidMount() {
//需要啟動相機
}
// 視圖將要消失
componentWillUnmount() {
//需要關閉相機
}
這個時候問題就來了沛厨,我們該怎么暴露我們的方法給 RN 層調用呢宙地?很簡單,這個時候我們先在我們的 TakePhotoView.h 文件中逆皮,暴露兩個方法宅粥。代碼如下:
#import <UIKit/UIKit.h>
@interface TakePhotoView : UIView
// 初始化
- (instancetype)init;
// 相機開始工作
- (void)camareStartRunning;
// 相機停止工作
- (void)camareStopRunning;
然后在 TakePhotoView.m 文件中實現(xiàn)這個兩個方法,代碼如下:
// 相機開始工作
- (void)camareStartRunning{
[self.captureSession startRunning];
}
// 相機停止工作
- (void)camareStopRunning {
[self.captureSession stopRunning];
}
這個時候呢电谣,我們 相機開始工作以及停止工作的兩個兩個方法都實現(xiàn)了秽梅,現(xiàn)在就需要將這兩個方法暴露給 RN層那邊調用抹蚀。這個時候,我們需要修改我們的 TakePhotoManager.m 文件企垦,增加一些代碼况鸣,讓我們的RN層能調用到我們露出來的方法。 TakePhotoManager.m的代碼改為如下
import "TakePhotoManager.h"
import "TakePhotoView.h"
@interface TakePhotoManager()
@property (nonatomic, strong) TakePhotoView *takePhotoView;
@end
@implementation TakePhotoManager
// 標記宏(必要)
RCT_EXPORT_MODULE()
- (UIView *)view {
_takePhotoView = [[TakePhotoView alloc] init];
return _takePhotoView;
}
/**
- 導出方法
- 相機開始工作
*/
RCT_EXPORT_METHOD(camareStartRunning) {
[_takePhotoView camareStartRunning];
}
/**
- 導出方法
- 相機停止工作
*/
RCT_EXPORT_METHOD(camareStopRunning) {
[_takePhotoView camareStopRunning];
}
@end
然后我們需要在 RN層中 的文件中竹观,增加如下代碼镐捧,來實現(xiàn)點擊事件的傳遞,代碼如下:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
requireNativeComponent,
NativeModules,
Dimensions,
} from 'react-native';
import NavOutView from '../../../components/NavOutView';
import { Actions } from 'react-native-router-flux';
import { i18n } from "../../../config";
// 該方法將 原生層TakePhotoManager 中 return 出來的 View 賦值給 RNTakePhoto臭增。這個時候 RNTakePhoto 就是我們在原生中中的頁面了懂酱。
// requireNativeComponent() 該方法中有兩個參數(shù),第一個是原生層暴露的UIView誊抛,另一個是在RN層要承接的 Class列牺。在這里我們可以看到,原生層暴露的UIView的文件叫做 TakePhotoManager拗窃,而在這里用的話瞎领,只用TakePhoto。這只能說明随夸,原生層的封裝需要按照一定的規(guī)則來做九默。
const RNTakePhoto = requireNativeComponent('TakePhoto', TakePhotoiOS);
// 通過該方法,我們可以拿到 TakePhotoManager.js 中暴露出來的方法
const TakePhotoManager = NativeModules.TakePhotoManager;
class TakePhotoiOS extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
/*
* 這里采用延遲250毫秒后調用相機開啟的方法是宾毒,因為在
* 原生層中驼修,TakePhotoView 被創(chuàng)建之后,才能調用 相機開啟
* 也等同于要 RNTakePhoto 被創(chuàng)建之后诈铛,才能調用
*/
this.timeOutReFresh = setTimeout(() => {
TakePhotoManager.camareStartRunning();
}, 250);
}
componentWillUnmount() {
// 相機停止工作
TakePhotoManager.camareStopRunning();
}
render() {
return (
<RNTakePhoto
style={styles.container}
/>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'transparent',
},
});
module.exports = TakePhotoiOS;
到了這個時候乙各,我們就可以發(fā)現(xiàn)我們的相機已經可以工作了。然后這個時候問題又來了幢竹,由于我是將相機視圖是做成全屏幕的耳峦,所以這個時候我也是將返回事件放在了原生的視圖中(總之,怎么作死怎么來)焕毫。那么問題來了蹲坷,當我點擊了關閉按鈕之后,在我的RN層中咬荷,要怎么知道我已經點了關閉了呢冠句?這里有兩個解決方法:一是直接把關閉按鈕做到RN層中轻掩,直接在RN層中調用視圖返回幸乒,關閉相機等方法。二是在原生層中唇牧,將關閉按鈕的點擊事件給暴露出來罕扎,然后在RN層中聚唐,監(jiān)聽并做相應的處理。這里采用的是第二種方式腔召。于是乎杆查,我們又需要對我們的代碼進行改動了。
首先臀蛛,我們可以先想到亲桦,要將 TakePhotoView 中的點擊事件方法傳出來,可以用到代理浊仆,通知客峭,block等方法,這個地方我采用了block抡柿。所以在 TakePhotoView.h 文件中舔琅,我們需要增加block的聲明,代碼如下:
#import <UIKit/UIKit.h>
@interface TakePhotoView : UIView
// 關閉按鈕的block
typedef void(^onTouchBackBlock)(NSDictionary *dicBlock);
@property (nonatomic, copy) onTouchBackBlock onTouchBackBlock;
// 初始化
- (instancetype)init;
// 相機開始工作
- (void)camareStartRunning;
// 相機停止工作
- (void)camareStopRunning;
@end
在 TakePhotoView.m 文件中洲劣,我們需要在關閉按鈕的點擊事件中备蚓,添加上我們的block,添加如下代碼:
// 關閉按鈕的點擊事件
- (void)btnCloseAction:(UIButton *)sender {
//移除所有的通知
[self removeNotification];
// 相機停止工作
[self camareStartRunning];
// 實現(xiàn)block
_onTouchBackBlock(@{@"message": @"goBack"});
}
這個時候囱稽,我們還需要在 TakePhotoManager 文件中郊尝,將我們的 block 暴露過去給我們的 RN 層調用,那么這個時候我們需要在 TakePhotoManager.m 文件中战惊,增加一個RN 層的 block虚循, 用于將我們 TakePhotoView 的點擊回調傳遞過去,代碼如下:
#import "TakePhotoManager.h"
#import "TakePhotoView.h"
#import <Photos/PHPhotoLibrary.h>
#import <AVFoundation/AVCaptureDevice.h>
#import <AVFoundation/AVMediaFormat.h>
@interface TakePhotoManager()
@property (nonatomic, strong) TakePhotoView *takePhotoView;
// 點擊返回的block
@property (nonatomic, copy) RCTBubblingEventBlock onTouchBackBlock;
@end
@implementation TakePhotoManager
// 標記宏(必要)
RCT_EXPORT_MODULE()
// 事件的導出
RCT_EXPORT_VIEW_PROPERTY(onTouchBackBlock, RCTBubblingEventBlock)
- (UIView *)view {
_takePhotoView = [[TakePhotoView alloc] init];
_takePhotoView.onTouchBackBlock = ^(NSDictionary *dicBlock) {
};
return _takePhotoView;
}
/**
* 相機開始工作
*/
RCT_EXPORT_METHOD(camareStartRunning) {
[_takePhotoView camareStartRunning];
}
/**
* 相機停止工作
*/
RCT_EXPORT_METHOD(camareStopRunning) {
[_takePhotoView camareStopRunning];
}
@end
然后我們需要在 RN層中 的文件中样傍,增加如下代碼横缔,來實現(xiàn)點擊事件的傳遞,代碼如下:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
requireNativeComponent,
NativeModules,
} from 'react-native';
import NavOutView from '../../../components/NavOutView';
import { Actions } from 'react-native-router-flux';
import { i18n } from "../../../config";
const RNTakePhoto = requireNativeComponent('TakePhoto', TakePhotoiOS);
const TakePhotoManager = NativeModules.TakePhotoManager;
class TakePhotoiOS extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
/*
* 這里采用延遲250毫秒后調用相機開啟的方法是衫哥,因為在
* 原生層中茎刚,TakePhotoView 被創(chuàng)建之后,才能調用 相機開啟
* 也等同于要 RNTakePhoto 被創(chuàng)建之后撤逢,才能調用
*/
this.timeOutReFresh = setTimeout(() => {
TakePhotoManager.camareStartRunning();
}, 250);
}
componentWillUnmount() {
TakePhotoManager.camareStopRunning();
}
render() {
return (
<RNTakePhoto
style={styles.container}
onTouchBackBlock={(event) => {
console.log(event.nativeEvent);
const eventMessage = event.nativeEvent;
console.log(eventMessage.message);
if (eventMessage.message === 'goBack') {
Actions.pop();
}
}}
/>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'transparent',
},
});
module.exports = TakePhotoiOS;