背景
既存のSwiftで書かれているiOSアプリケーションにReactNativeを取り入れていく場合に、手っ取り早くViewController内で定義されたメソッドをjs側で呼び出したいことがある。
手法
SwiftとReactNativeとでイベントのやりとりをするには、ネイティブ側で編集するべきは下記の3つのファイルになる。
- appname-Bridging-Header.h (add or edit)
- FooViewController.m(add)
- FooViewController.swift(edit)
Swiftだけではメソッドの受け渡しができないため、少しだけ Objective-C を取り入れる必要がある。
1. appname-Bridging-Header.h
Reactのヘッダーファイルを読み込んでおく必要があるため、
#import <React/RCTBridge.h>
#import <React/RCTEventDispatcher.h>
を書き足す。Bridging-Headerファイルがない場合は新たに作成する。(その場合、2.の手順を先に行うと、Xcodeが Bridging-Headerファイルを作成するかどうか尋ねてくれる。)
2. FooViewController.m
新たに作成すべきファイル。メソッドをやりとりしたい {ViewController名}.m で作成する。
#import "RCTBridgeModule.h"
@interface RCT_EXTERN_MODULE(ChatViewController, NSObject)
RCT_EXTERN_METHOD( testEvent:(NSString *)eventName )
@end
これで、ReactNative側とやりとりができるように宣言したことになる。
3. FooViewController.swift(edit)
さて、2. だけではまだswift側が対応できていないので、
class FooViewController: UIViewController {
func testEvent( arg: String ) {
// whatever
}
}
という既存のコードにおける class
, func
それぞれに対して
@objc( FooViewController )
class FooViewController: UIViewController {
@objc func testEvent( arg: String ) {
// whatever
}
}
と、 @objc
を付け足す。
すると、ReactNative側では
import React, { Component } from 'react';
import {
AppRegistry,
Platform,
NativeModules,
NativeAppEventEmitter,
StyleSheet,
Text,
View,
TouchableHighlight,
TouchableNativeFeedback
} from 'react-native';
class FooComponent extends Component {
constructor(props) {
super(props);
}
onTouch() {
NativeModules.FooViewController.testEvent('test');
}
render() {
let self = this;
let TouchableElement = TouchableHighlight;
if (Platform.OS === 'android') {
TouchableElement = TouchableNativeFeedback
}
return (
<View>
<TouchableElement onPress={this.onTouch.bind(self)}>
<View>
<Text>
Button
</Text>
</View>
</TouchableElement>
</View>
);
}
}
AppRegistry.registerComponent('FooComponent', () => HelloWorld);
とすることで、 Button
をタップしたときに、ViewController の testEvent が呼ばれることが確認できる。
また、 ReactNative -> Swift -> ReactNative と、もう一度 ReactNative に値か何かを返したい場合は、
import React
@objc( FooViewController )
class FooViewController: UIViewController {
var bridge: RCTBridge!
@objc func testEvent( arg: String ) {
// whatever
let eventName: String = arg
self.bridge.eventDispatcher().sendAppEventWithName( eventName, body: "test event emitted." )
}
}
class FooComponent extends Component {
constructor(props) {
super(props);
NativeAppEventEmitter.addListener('test', (body) => {
console.log(body);
});
}
onTouch() {
NativeModules.FooViewController.testEvent('test');
}
render() {
...
}
}
とすれば、 console.log(body)
経由で test event emitted.
が吐かれていることが確認できる。