混合開發(fā):RN調(diào)用原生頁(yè)面或View


前言


RN和iOS混合開發(fā)的幾種場(chǎng)景。

  • 原生項(xiàng)目中巢音,調(diào)用部分RN頁(yè)面遵倦。
  • 原生頁(yè)面中,調(diào)用部分RN組件港谊。
  • RN項(xiàng)目中骇吭,調(diào)用部分原生頁(yè)面。
  • RN頁(yè)面中歧寺,調(diào)用部分原生View燥狰。
  • RN項(xiàng)目中棘脐,調(diào)用部分原生模塊。

場(chǎng)景一和場(chǎng)景二其實(shí)是一樣的龙致,因?yàn)樵赗N看來(lái)蛀缝,頁(yè)面和組件在廣義上都是組件,對(duì)應(yīng)于原生里的View目代。

場(chǎng)景三和場(chǎng)景四是一樣的屈梁,因?yàn)闊o(wú)論RN要調(diào)用原生的頁(yè)面還是View,我們最終都是把原生的View交給它調(diào)用榛了。還是那句話在讶,RN那邊的組件對(duì)應(yīng)原生里的View,而沒(méi)法對(duì)應(yīng)ViewController霜大。

場(chǎng)景五和場(chǎng)景三构哺、場(chǎng)景四的區(qū)別在于,RN調(diào)用原生頁(yè)面或View是指調(diào)用原生視圖層面的東西來(lái)做UI布局的(當(dāng)然這些視圖也可能會(huì)有操作事件)战坤,而RN調(diào)用原生模塊是指調(diào)用原生功能層面的東西來(lái)實(shí)現(xiàn)某個(gè)功能(例如調(diào)用日歷曙强、通訊錄等模塊,調(diào)用分享途茫、三方登錄碟嘴、支付等三方SDK,調(diào)用我們自己的某些功能代碼塊囊卜,等等)娜扇。

上一篇講解了原生調(diào)用RN頁(yè)面或組件(場(chǎng)景一和場(chǎng)景二)的詳細(xì)開發(fā)步驟,這一篇我們講解RN調(diào)用原生頁(yè)面或View(場(chǎng)景三和場(chǎng)景四)的詳細(xì)開發(fā)步驟边败,下一篇講解RN調(diào)用原生模塊(場(chǎng)景五)的詳細(xì)開發(fā)步驟袱衷。


示例:RN調(diào)用原生頁(yè)面或View(場(chǎng)景三和場(chǎng)景四)的詳細(xì)開發(fā)步驟


該示例實(shí)現(xiàn)的是:在RN頁(yè)面里調(diào)用原生的MapView來(lái)展示和使用捎废。

其實(shí)很簡(jiǎn)單的笑窜,無(wú)非就是為每個(gè)原生View都創(chuàng)建一個(gè)對(duì)應(yīng)的、繼承自RCTViewManager的子Manager來(lái)管理原生View登疗,RN那邊再創(chuàng)建相應(yīng)的組件來(lái)接收一下這個(gè)導(dǎo)出的原生View就可以使用了排截。原生View和子Manager是一一對(duì)應(yīng)的,我們會(huì)在子Manager里創(chuàng)建原生View辐益、導(dǎo)出原生View断傲、導(dǎo)出原生View的一些屬性、導(dǎo)出原生View的一些事件來(lái)供RN調(diào)用這個(gè)原生View智政。

第一步:創(chuàng)建RN項(xiàng)目认罩,打開對(duì)應(yīng)的原生項(xiàng)目,直接編寫原生部分的代碼

既然是RN調(diào)用原生续捂,這就表示RN項(xiàng)目是占主導(dǎo)地位的垦垂,因此我們就不需要額外地創(chuàng)建原生項(xiàng)目宦搬,再和RN項(xiàng)目建立連接供它調(diào)用了。而是直接打開RN項(xiàng)目對(duì)應(yīng)的原生項(xiàng)目來(lái)編寫原生部分的代碼劫拗。

我們這里創(chuàng)建一個(gè)RN項(xiàng)目间校,名字叫作 HybridApp,然后打開它對(duì)應(yīng)的iOS項(xiàng)目页慷,直接編寫原生部分的代碼憔足。

第二步:為MapView創(chuàng)建對(duì)應(yīng)的子Manager來(lái)創(chuàng)建它、導(dǎo)出它酒繁、管理它

這里滓彰,我們?yōu)镸apView創(chuàng)建一個(gè)對(duì)應(yīng)的、繼承自RCTViewManager的子Manager州袒,名字叫作INEMapViewManager找蜜,來(lái)管理MapView。

// INEMapViewManager.h

#import <React/RCTViewManager.h>
#import <MapKit/MapKit.h>

@interface INEMapViewManager : RCTViewManager

@end


// INEMapViewManager.m

#import "INEMapViewManager.h"

@implementation INEMapViewManager

// 導(dǎo)出該原生View
RCT_EXPORT_MODULE(INEMapView)

// 創(chuàng)建并返回該原生View
- (UIView *)view
{
  MKMapView *mapView = [[MKMapView alloc] init];
  return mapView;
}

@end

上面有三個(gè)需要注意的地方:

  • 我們的類名使用了INE前綴以避免與其它框架產(chǎn)生命名沖突稳析。蘋果自有框架使用了兩個(gè)字符的前綴洗做,而RN則使用RCT作為前綴。為避免命名沖突彰居,我們建議您在自己的類中使用RNT以外的其它三字符前綴诚纸。
  • 在使用RCT_EXPORT_MODULE()宏導(dǎo)出原生View的地方,名字取為類名除去Manager陈惰,比如這里的類名為INEMapViewManager畦徘,而導(dǎo)出的原生View則取名為INEMapView,到時(shí)候RN使用時(shí)會(huì)自動(dòng)在后面添加Manager抬闯。
  • 請(qǐng)不要在-view方法中給UIView實(shí)例設(shè)置frame或是backgroundColor屬性井辆,我們的選擇是統(tǒng)一在RN項(xiàng)目里為組件添加這些布局屬性。
第三步:在RN項(xiàng)目中調(diào)用該原生View

其實(shí)經(jīng)過(guò)第二步溶握,就這么簡(jiǎn)單杯缺,RN項(xiàng)目中已經(jīng)可以調(diào)用該原生View了,我們來(lái)編寫一些RN代碼來(lái)看看效果睡榆。

// MapView.js

import {requireNativeComponent} from 'react-native';

// requireNativeComponent會(huì)自動(dòng)把'INEMapView'解析為'INEMapViewManager'
export default requireNativeComponent('INEMapView');
// App.js

import React, {Component} from 'react';
import MapView from './js/MapView.js';

export default class App extends Component {
    render() {
        return (
            <MapView style={{flex: 1}}/>
        );
    }
}

用Xcode運(yùn)行一下項(xiàng)目萍肆,發(fā)現(xiàn)已經(jīng)成功在RN項(xiàng)目里調(diào)用了原生的MapView,諸如捏放和其它的手勢(shì)都已經(jīng)完整支持胀屿。但是現(xiàn)在我們還不能真正得在RN項(xiàng)目控制它塘揣。

第四步:導(dǎo)出原生View的一些屬性,供RN使用

舉例來(lái)說(shuō)宿崭,我們希望在RN項(xiàng)目中能夠禁用地圖的手指捏放操作亲铡。禁用捏放操作只需要一個(gè)布爾值類型的屬性就行了,所以我們添加這么一行:

// INEMapViewManager.m

#pragma mark - 導(dǎo)出原生View的一些屬性,供RN使用

RCT_EXPORT_VIEW_PROPERTY(zoomEnabled, BOOL)

導(dǎo)出的這些屬性(如zoomEnabled)都是iOS里原生View的同名屬性奖蔓,亂改了可用不了的哦琅摩,類型也是同樣的道理。

現(xiàn)在要想禁用捏放操作锭硼,我們只需要在RN項(xiàng)目設(shè)置對(duì)應(yīng)的屬性:

// App.js

<MapView
    style={{flex: 1}}
    zoomEnabled={false}
/>

但這樣并不能很好的說(shuō)明這個(gè)組件的用法——用戶要想知道我們的組件有哪些屬性可以用房资,以及可以取什么樣的值,他不得不一路翻到OC的代碼檀头。要解決這個(gè)問(wèn)題轰异,我們可以創(chuàng)建改造一下MapView.js,讓它通過(guò)PropTypes來(lái)說(shuō)明這個(gè)組件都有哪些接口可用暑始。

// MapView.js

import React, {Component} from 'react';
import {requireNativeComponent} from 'react-native';
import PropTypes from 'prop-types';

// 這里我們把requireNativeComponent的第二個(gè)參數(shù)從null變成了MapView搭独,
// 就使得RN的底層框架可以檢查該MapView的屬性和原生View的是否一致,來(lái)減少出現(xiàn)問(wèn)題的可能廊镜。
const INEMapView = requireNativeComponent('INEMapView', MapView);

export default class MapView extends Component {
    static propTypes = {
        /**
         * A Boolean value that determines whether the user may use pinch
         * gestures to zoom in and out of the map.
         */
        zoomEnabled: PropTypes.bool,
    };

    render() {
        return (
            <INEMapView
                // 這個(gè)代表接收所有原生View導(dǎo)出過(guò)來(lái)的原生屬性
                {...this.props}
            />
        );
    }
}

這樣RN里的組件使用起來(lái)就更清晰了牙肝,現(xiàn)在用Xcode運(yùn)行一下項(xiàng)目,就發(fā)現(xiàn)我們已經(jīng)可以成功地禁用捏放操作了嗤朴。

第五步:導(dǎo)出原生View的一些事件配椭,供RN使用

現(xiàn)在在RN項(xiàng)目里,我們已經(jīng)有了一個(gè)MapView雹姊,而且可以通過(guò)屬性來(lái)控制它股缸,不過(guò)我們?cè)趺床拍芟雐OS里那樣來(lái)監(jiān)聽并處理用戶對(duì)地圖一些操作的事件呢,譬如拖動(dòng)地圖操作吱雏?

我們知道iOS里針對(duì)這些事件敦姻,都是有相關(guān)的代理方法來(lái)供我們監(jiān)聽并做處理的,而且上面我們已經(jīng)成功地導(dǎo)出了原生View的屬性歧杏,那我們就想到是不是可以為原生View添加一些block作為屬性導(dǎo)出出去镰惦,并在這些代理方法里調(diào)用它們,這樣RN那邊就可以使用這些block作為回調(diào)監(jiān)聽我們對(duì)MapView的事件操作了犬绒。

沒(méi)問(wèn)題旺入,這個(gè)思路很穩(wěn),可以實(shí)踐懂更。但是眼下有一個(gè)問(wèn)題眨业,我們導(dǎo)出的是系統(tǒng)的MKMapView啊,我們沒(méi)法直接為它添加屬性的沮协,那我們就考慮使用Category或者繼承來(lái)為系統(tǒng)的類添加屬性了,這里我們就采用繼承吧卓嫂。于是我們自定義了一個(gè)繼承自MKMapView的類慷暂,名字叫作INEMapView

// INEMapView.h

#import <MapKit/MapKit.h>
#import <React/RCTComponent.h>

@interface INEMapView : MKMapView

// 添加一個(gè)block,用來(lái)作為事件監(jiān)聽的回調(diào)
@property (nonatomic, copy) RCTBubblingEventBlock onRegionChange;

@end


// INEMapView.m

#import "INEMapView.h"

@implementation INEMapView

@end

然后修改修改INEMapViewManager行瑞。

// INEMapViewManager.h

#import <React/RCTViewManager.h>
#import <MapKit/MapKit.h>

@interface INEMapViewManager : RCTViewManager <MKMapViewDelegate>

@end


// INEMapViewManager.m

#import "INEMapViewManager.h"
#import "INEMapView.h"

@implementation INEMapViewManager

// 導(dǎo)出該原生View
RCT_EXPORT_MODULE(INEMapView)

// 創(chuàng)建并返回該原生View
- (UIView *)view
{
    INEMapView *mapView = [INEMapView new];
    mapView.delegate = self;
    return mapView;
}


#pragma mark - 導(dǎo)出原生View的一些屬性奸腺,供RN使用

RCT_EXPORT_VIEW_PROPERTY(zoomEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onRegionChange, RCTBubblingEventBlock)


#pragma mark - MKMapViewDelegate

- (void)mapView:(INEMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
  if (mapView.onRegionChange) {
    
    MKCoordinateRegion region = mapView.region;
    mapView.onRegionChange(@{
                             @"region": @{
                                 @"latitude": @(region.center.latitude),
                                 @"longitude": @(region.center.longitude),
                                 @"latitudeDelta": @(region.span.latitudeDelta),
                                 @"longitudeDelta": @(region.span.longitudeDelta),
                                 }
                             });
  }
}

@end

但是在原生端看起來(lái)我們是給onRegionChange這個(gè)block傳遞了一個(gè)region對(duì)象,但實(shí)際傳遞過(guò)去的是一個(gè)事件對(duì)象event血久,而真正的region對(duì)象被包裹在event.nativeEvent對(duì)象里突照,所以為了在RN端使用起onRegionChange方法來(lái)更清晰,我們會(huì)在MapView對(duì)這個(gè)參數(shù)做做處理:

// MapView.js

import React, {Component} from 'react';
import {requireNativeComponent} from 'react-native';
import PropTypes from 'prop-types';

// 這里我們把requireNativeComponent的第二個(gè)參數(shù)從null變成了MapView氧吐,
// 就使得RN的底層框架可以檢查該MapView的屬性和原生View的是否一致讹蘑,來(lái)減少出現(xiàn)問(wèn)題的可能。
const INEMapView = requireNativeComponent('INEMapView', MapView);

export default class MapView extends Component {
    static propTypes = {
        /**
         * A Boolean value that determines whether the user may use pinch
         * gestures to zoom in and out of the map.
         */
        zoomEnabled: PropTypes.bool,

        /**
         * Callback that is called continuously when the user is dragging the map.
         */
        onRegionChange: PropTypes.func,
    };

    render() {
        return (
            <INEMapView
                // 這個(gè)代表接收所有原生View導(dǎo)出過(guò)來(lái)的原生屬性
                {...this.props}
                // 這個(gè)代表接收我們自己為原生View擴(kuò)展的屬性
                onRegionChange={(event) => {
                    if (this.props.onRegionChange) {
                        // 傳遞出去nativeEvent筑舅,這樣外界在使用onRegionChange方法時(shí)就可以直接點(diǎn)出他們?cè)谠糠謧鬟^(guò)來(lái)的參數(shù)了
                        this.props.onRegionChange(event.nativeEvent);
                    }
                }}
            />
        );
    }
}
// App.js

import React, {Component} from 'react';
import MapView from './js/MapView.js';

export default class App extends Component {
    render() {
        return (
            <MapView
                style={{flex: 1}}
                zoomEnabled={false}
                onRegionChange={(nativeEvent) => {
                    console.log(nativeEvent.region);
                }}
            />
        );
    }
}

好座慰,經(jīng)過(guò)這一堆操作就可以成功地導(dǎo)出原生View的一些事件,供RN使用了翠拣,快去運(yùn)行試試吧版仔。

第六步:導(dǎo)出原生界面

經(jīng)過(guò)以上步驟,我們就成功地實(shí)現(xiàn)了RN調(diào)用原生組件误墓,但其實(shí)RN調(diào)用原生頁(yè)面和這個(gè)是一模一樣的操作蛮粮,只不過(guò)我們不能直接導(dǎo)出ViewController,而是導(dǎo)出ViewController.view供RN使用谜慌,也就是說(shuō)RN只能使用原生頁(yè)面上頁(yè)面的部分蝉揍,需要自己添加導(dǎo)航欄啊、TabBar啊什么的畦娄,如此而已又沾。

- (UIView *)view
{
  ViewController *vc = [ViewController new];
  return vc.view;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市熙卡,隨后出現(xiàn)的幾起案子杖刷,更是在濱河造成了極大的恐慌,老刑警劉巖驳癌,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滑燃,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡颓鲜,警方通過(guò)查閱死者的電腦和手機(jī)表窘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)甜滨,“玉大人乐严,你說(shuō)我怎么就攤上這事∫履Γ” “怎么了昂验?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我既琴,道長(zhǎng)占婉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任甫恩,我火速辦了婚禮逆济,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘磺箕。我一直安慰自己奖慌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布滞磺。 她就那樣靜靜地躺著升薯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪击困。 梳的紋絲不亂的頭發(fā)上涎劈,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音阅茶,去河邊找鬼蛛枚。 笑死,一個(gè)胖子當(dāng)著我的面吹牛脸哀,可吹牛的內(nèi)容都是我干的蹦浦。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼撞蜂,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼盲镶!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蝌诡,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤溉贿,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后浦旱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宇色,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年颁湖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宣蠕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡甥捺,死狀恐怖抢蚀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涎永,我是刑警寧澤思币,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布鹿响,位于F島的核電站羡微,受9級(jí)特大地震影響谷饿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜妈倔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一博投、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧盯蝴,春花似錦毅哗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至闽烙,卻和暖如春翅睛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背黑竞。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工捕发, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人很魂。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓扎酷,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親遏匆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子法挨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容