Cocoapodsで入れたライブラリをReact Nativeとbridgeして使う

More than 1 year has passed since last update.

この記事ではCocoapodsで導入したライブラリをReact Native側から呼び出して利用する方法についてご紹介します。

今回は、今更Objective-CでNative側のコード書くのもなんだかなー、と思いSwiftで書きました。

React Native側のボタンを押すと、Whisperというライブラリが呼び出される、ということを試しました。

こんな見た目の奴です。

Whisper

https://github.com/hyperoslo/Whisper

今回のソースはこちらに置いてあるのでよければ参考にしてください。

https://github.com/kouchi67/CocoapodsReactBridge

では、ハマったポイントも含めて説明していきます。


環境


  • OS: El Capitan (10.11.6)

  • Xcode: 8.1

  • react native 0.38.1


Project作成

React NativeのProjectを作成します。

$ react-native init CocoapodsReactBridge


Cocoapods導入

Cocoapodsを導入していきます。

$ cd CocoapodsReactBridge/ios

$ pod init
$ vim Podfile


Podfile

platform :ios, '10.0'

use_frameworks!

target 'CocoapodsReactBridge' do
pod 'Whisper', '~> 3.1'
end


これを書いている現在では、 Whisperの最新Versionは 4.0 でしたが、

Swift 3 でビルドできなかったので、 Swift 2.3 で動作する Version を導入しています。

Podfileの記述が完了したら、ライブラリをインストールします。

$ pod install

pod installが完了したらXcodeでプロジェクトを開きます。

$ open CocoapodsReactBridge.xcworkspace


Bridgeクラスの作成

Native側の処理をReact Nativeから呼べるように準備します。

以下のファイルを作成します。


  • ReactWhisper.m

  • ReactWhisper.swift

  • CocoapodsReactBridge-Bridging-Header.h

CocoapodsReactBridge-Bridging-Header.h に関しては、

Swiftファイルを作成するときに「Bridging-Headerファイル作る?」って聞かれるので、その際に作成しましょう。

順番にファイルを解説します。


ReactWhisper.swift

import UIKit

import Whisper

@objc(ReactWhisper)
class ReactWhisper: NSObject {

@objc func showWhisper(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {

dispatch_async(dispatch_get_main_queue()) {
let murmur = Murmur(title: "Called Native Modules !!")
show(whistle: murmur, action: .Show(2.0))
}

resolve([])
}

}


swiftファイルは、ライブラリを呼び出す本体のクラスとなります。

クラスとメソッドを宣言するところで、 @objc とつけることで、Objective-Cから呼び出せるようになります。

React NativeとNative ModulesのBridging部分はObjective-Cで書く必要があるため、 @objc が必要です。

(Bridging部分もSwiftで書けるやり方があれば教えてください・・・)


ReactWhisper.m

#import <Foundation/Foundation.h>

#import "RCTBridgeModule.h"

@interface RCT_EXTERN_MODULE(ReactWhisper, NSObject)

RCT_EXTERN_METHOD(showWhisper: (RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)

@end


ReactWhisper.m では、React NativeとNative ModulesのBridgingについて記述します。

RCT_EXTERN_MODULE RCT_EXTERN_METHOD を使うことで、Native側のクラスやメソッドをReact Nativeから参照できるようになるようです。

今回は ReactWhisper.swift で記述した showWhisper メソッドを呼び出せるようにします。


CocoapodsReactBridge-Bridging-Header.h

#import "RCTBridgeModule.h"


このファイルは、SwiftからObjective-Cのクラスを参照するために必要です。

Swiftから RCTPromiseResolveBlock など参照する必要があるため、必要です。


Swift 2.3で動くようにする

今のままではビルドが通らないので、Swift 2.3で動作するように設定を変えます。

ビルド設定にある Use Legacy Swift Version という項目を Yes に修正します。

以下のようになります。

image

これでビルドが通るようになりました。

Native側の修正はこれで一旦終わりです。


React NativeからNative Modulesを呼び出す

index.ios.js に必要最低限なコードが記述されてますが、ここにWhisperを呼び出すためのボタンを追加します。


index.ios.js

import React, { Component } from 'react';

import {
AppRegistry,
StyleSheet,
Text,
Button,
NativeModules,
View
} from 'react-native';

export default class CocoapodsReactBridge extends Component {

onPressShowWhisper() {
NativeModules.ReactWhisper.showWhisper();
}

render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
<Text style={styles.instructions}>
To get started, edit index.ios.js
</Text>
<Text style={styles.instructions}>
Press Cmd+R to reload,{'\n'}
Cmd+D or shake for dev menu
</Text>
<Button
onPress={this.onPressShowWhisper}
title="show Whisper"
color="#841584"
/>
</View>
);
}
}

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});

AppRegistry.registerComponent('CocoapodsReactBridge', () => CocoapodsReactBridge);


NatveModules をimportして使用しているところがミソです。

ここまで書けたらXcodeからRunします。

iOS Simulatorが起動できたら、 show Whisper ボタンを押してみましょう。

Simulator Screen Shot 2016.12.07 19.18.14.png

Status BarのところにWhisperが表示されていますね。(背景が白でわかりにくいですが・・・)

無事に呼び出すことに成功したようです。


Warningの対応

useNativeDrive is not supported とか表示されてます。

これについてちょっとよくわかってないですが、デフォルトではNative側で動作するアニメーションは対応していないようです。

Simulator上では普通に動いてますが、消したほうがよいのでしょう。。。

とりあえずぐぐったらこんなページを発見しました。

https://github.com/facebook/react-native/issues/11094#issuecomment-263240420



  1. find RCTAnimation.xcodeproj from node_modules/react-native/NativeAnimation folder

  2. open xcode

  3. open project navigator (folder icon from the left)

  4. drag & drop RCTAnimation.xcodeproj from the previous step to Libraries folder

  5. Expand RCTAnimation.xcodeproj in project navigator and find libRCTAnimation.a file inside of Products folder

  6. click on your project name on top of project navigator and go to Build Phrases tab

  7. drag and drop libRCTAnimation.a to Link Binary With Libraries

  8. Clean and run :)


こちらの手順に従って、 libRCTAnimation.a とやらをXcodeのProjectに追加してあげると良いようです。

RCTAnimation.xcodeprojとやらをまず探します。

私の環境では以下に保存されていました。

CocoapodsReactBridge/node_modules/react-native/Libraries/NativeAnimation/RCTAnimation.xcodeproj

中を確認しても .a ファイルが存在しませんでした。

手順とは違いますが、このプロジェクトでビルドすると .a ファイルが作成できるはずなので、Xcodeで開きます。

$ cd CocoapodsReactBridge/node_modules/react-native/Libraries/NativeAnimation

$ open RCTAnimation.xcodeproj

開いたらビルドしてみます。

image

無事に作成されたようです。

あとは、このファイルをCocoapodsReactBridgeプロジェクトにドラッグ&ドロップで追加します。

こんなファイル構成になりました。

image

これでもう一度Runしてみると、Warningが消えていることが確認できると思います。

Simulator Screen Shot 2016.12.07 19.42.18.png

ちゃんとWarningが消えてますね!


注意点

swiftファイルの以下の記述について補足します。

    dispatch_async(dispatch_get_main_queue()) {

let murmur = Murmur(title: "Called Native Modules !!")
show(whistle: murmur, action: .Show(2.0))
}

dispatch_async( ... という記述がありますが、これはUI操作に関する処理がある場合に必要となります。

iOSでは GCD という機構が用意されており、それを使って特定の処理のみをメインスレッドで処理することができます。

iOSでは、UI操作に関する処理はメインスレッドで処理することがルールとなっています。

メインスレッド以外でも処理はできますが、想定した動作とならないことが多いです。

(即座に反応しない、所定の位置に表示されない、そもそも表示されない、などなど)


最後に

いかがでしたでしょう?

Cocoapodsで導入したライブラリをReact Nativeから呼び出す、ということをやってみました。

Androidとコードが共通化できるReact NativeではiOSライブラリを利用することはあまりないかもしれませんが、

既存のiOSライブラリには便利なものがたくさん公開されています。

また、今回はSwiftで書きましたが、Swiftはまだまだバージョンアップが頻繁で古いコードではビルドが通らないこともよくあります。

ライブラリが追従しておらずビルドできないというケースも珍しくありません。

そのため注意が必要ではありますが、活用できる資産はどんどん使っていけばよいかなと私は思います。

もしかしたらですが、Objective-Cで書いたほうがビルドエラーに悩まされる事は少ないかもしれません。

(Swiftほど構文の変化はないはずなので)

Advent Calendarの投稿に遅れてしまいましたが、私の記事はこれで終わりです。