【React Native】Native Modules: iOS の書き方
Native Modules というAPIを利用することで、Swift, Objective-C や Java のコードを呼び出すことができます。これによって、以下が可能です。
- JavaScriptから利用できない iOS/Android のAPIを使用する
- 既存の Objective-C、Swift、Java、C++ ライブラリを 再利用する
ポイント
- iOSの Native Moduleを作成する
- Swift, Objective-C のコードを呼び出す
- JavaScriptから引数を受け取る
- JavaScriptへ返り値を返す
- iOS側で起こるイベントをJavaScriptでsubscribeする
ReactNativeプロジェクトの作成
まだプロジェクトがない場合は、作成します。
npx react-native init myApp
Swiftファイルの作成
- ios/myApp.xcodeprojにあるプロジェクトをXCodeで開きます。
- NativeModulesフォルダを作成します。(任意)
- NativeModulesフォルダ内に.swiftファイルを作成します。
今回は counter.swift
というファイルを作成しました。
Objective-C Bridging Headerの作成
.swiftファイルを作成すると、「Objective-C Bridging Headerを作成しますか?」 とダイアログが表示されるので、作成します。
このファイルは、名前の通りSwiftファイルとObjective-Cファイルをブリッジするものです。
ファイル名は変えてはいけません。
以下のように、Objective-C Bridging Headerに追記します。
// myApp-Bridging-Header.h
#import"React/RCTBridgeModule.h"
モジュールの実装
簡単なネイティブモジュールを実装してみましょう。
カウンターの値を変化させるモジュールで、以下を含んでいます。
-
Counter
クラス -
add
メソッド
import Foundation
@objc(Counter)
class Counter: NSObject {
private var count = 0
@objc
func add() {
count += 1
print("count is \(count)")
}
}
モジュールをReactNativeに露出する
実装したモジュールをReactNativeから呼び出すために、Objective-Cのファイルを作成します。
先ほど作成したSwiftファイルと同じ名前 で、同ディレクトリに作成します。
例えば先程 counter.swift
という .swift ファイルを作成したので、counter.m
という名前で新しくObjective-Cのファイルを作成します。
作成したファイルに以下を追記します。
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(Counter, NSObject)
RCT_EXTERN_METHOD(add)
@end
- RCT_EXTERN_MODULE: モジュールを外部参照可能にします。
- 上記の例では、
Counter
クラスを指定しています。
- 上記の例では、
- RCT_EXTERN_METHOD: メソッドを外部参照可能にします。
-
Counter
に実装されたadd
メソッドを指定しています。
-
ReactNativeから呼び出す
NativeModules
をインポートし、クラス名.メソッド名
で呼び出すことができます。
import { NativeModules } from 'react-native'
NativeModules.Counter.add();
引数を受け取る
add
メソッドが引数を受けとる場合は、以下のように記述します。
Swiftファイル
@objc
func add(_ num: Int) {
count += num
print("count is \(count)")
}
Obj-Cファイル
引数を受け取る場合、Obj-Cファイルも変更が必要です。
@interface RCT_EXTERN_MODULE(Counter, NSObject)
RCT_EXTERN_METHOD(add: (NSInteger)num)
@end
メソッド名: (型)引数名
に対応しています。
ReactNativeから呼び出す
引数を与えることができます。
import { NativeModules } from 'react-native'
NativeModules.Counter.add(123);
返り値を返す
iOSの世界から、JavaScript(ReactNative)の世界に、返り値を返す場合は、Promiseを使用する実装になります。
以下の特殊な関数を引数に追加し、それぞれ 正常終了時の返り値
と エラー時の返り値
に使用できます。
型 | タイミング | コールバック |
---|---|---|
RTCPromiseResolveBlock | 処理成功時 | then 句 |
RTCPromiseRejectBlock | 処理失敗時 | catch 句 |
Swiftファイル
@objc
func add(
_ num: Int,
resolver resolve: RTCPromiseResolveBlock, // 追加
rejecter reject: RTCPromiseRejectBlock // 追加
) {
do {
count += num
resolve(count) // 成功
}
catch {
reject("Error!", NSError(...)) // 失敗
}
}
Obj-Cファイル
Obj-Cファイルも変更が必要です。
@interface RCT_EXTERN_MODULE(Counter, NSObject)
RCT_EXTERN_METHOD(
add: (NSInteger)num
resolver: (RTCPromiseResolveBlock)resolve // 追加
rejecter: (RTCPromiseRejectBlock)reject // 追加
)
@end
ReactNativeから呼び出す
.then
や.catch
のコールバックで、返り値を扱うことができます。
import { NativeModules } from 'react-native'
NativeModules.Counter.add(123)
.then((res) => console.log(res))
.catch((err) => console.error(err));
Event Emitterの作成
iOSネイティブ側で起こるイベントをSunscribeしたい時には、RCTEventEmitterを使用します。
Obj-Cファイル
Objective-Cのファイルに、以下を記述します。
#import "React/RCTBridgeModule.h"
#import "React/RCTEventEmitter.h"
@interface RCT_EXTERN_MODULE(Counter, RCTEventEmitter)
Bridging-Header ヘッダファイル
さらに、ブリッジヘッダファイルにも追記します。
#import "React/RCTBridgeModule.h"
#import "React/RCTEventEmitter.h"
Swift ファイル
Swift側では以下のような実装を行います。
@objc(Counter)
class Counter: RCTEventEmitter {
@objc
func increment() {
count += 1
print("count is \(count)")
sendEvent(withName: "onIncrement", body: ["count": count])
}
override func supportedEvents() -> [String]! {
return ["onIncrement"]
}
}
ReactNativekからイベントリスナーで待ち受ける
import {
NativeModules,
NativeEventEmitter
} from 'react-native'
const CounterEvents = new NativeEventEmitter(NativeModules.Counter);
CounterEvents.addListener(
"onIncrement",
(res) => console.log(res)
);
NativeModules.Counter.increment();
参考
Discussion
文中にいくつか
RTCPromiseResolveBlock
とRTCPromiseRejectBlock
という誤字があるようです...!TとCが逆になっている。