🍃このブログは移転しました。
3秒後、自動的に移動します・・・。

React NativeでNative機能をSwiftで書いて使うには

React Nativeの対応が追いついてないNative機能を使うには、自分でブリッジを実装する必要がある。

ただ公式のDocsはほぼObj-Cのことしか書いてなくて、Swiftでもできるよ!って一言くらいしかない。
もちろん調べてもろくな例が出てこない!

かといってObj-CよりSwiftの方が親しみやすい気がするなーということで・・・、四苦八苦しながらやり遂げたことをメモ。

アプリエンジニアではないので、非効率なコードや勘違いしてることを書いてる可能性もありです!

必要なファイル

いちおうドキュメントはコレ。

Native Modules

基本的な流れはココに書いてあるけど、Swift版の端折られ感よ!
要約すると、

  • `XXX.swift`で実装する
  • `XXX.m`でエクスポートする
  • それらを認識させる`MyProject-Bridging-Header.h`を用意する

この3つが必要。

サンプルコード

端末内に同期してある音楽ファイルから、アルバムの一覧を取得するやつ。
それぞれのファイルを用意していく。

以下は`MyProject`というプロジェクトで、`XXX`というブリッジモジュールを作る前提です。

MyProject-Bridging-Header.h

`.swift`のファイルを追加したらXcodeが勝手に追加してくれるけど、してくれないなら自分で作る。

#ifndef MyProject_Bridging_Header_h
#define MyProject_Bridging_Header_h

#import "RCTBridgeModule.h"

#endif

`RCTBridgeModule.h`をインポートしてるのが重要。

XXX.m

#import <Foundation/Foundation.h>
#import "RCTBridgeModule.h"

@interface RCT_EXTERN_MODULE(XXX, NSObject)

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

@end

`.swift`側で実装したものをエクスポートする記述たち。

`XXX`ってクラスと`getAlbums()`ってメソッドを定義してて、これは`Promise`を返すようになってる。
このObj-Cの文法はさっぱりわからん(なぜ第二引数からラベルがいるのかとか)けど、こう書けば動く。

さて次が本丸のSwift実装。

XXX.swift

import Foundation
import MediaPlayer

@objc(XXX)
class XXX: NSObject {

  @objc func getAlbums(resolve: RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) {
    let albumsQuery: MPMediaQuery = MPMediaQuery.albumsQuery()
    // 略すが↑を使って必要なものを↓で`resolve`する
    resolve([])
  }

}

`@objc`ってのが重要らしい。
ここはただのSwiftなので、あれこれ調べながら頑張って実装する。
もちろんさっきエクスポートする時に指定したものと同じ名前じゃないとコンパイルエラーになる。

ハマりポイントとしては、React Native側に渡せる値の型。
ようはプリミティブな値しか渡せないのでstructの配列とか返しちゃうと盛大にエラーになるので注意。

jsで使う

import {
  NativeModules,
} from 'react-native';
const XXX = NativeModules.XXX;

XXX.getAlbums().then((albums) => {
  // なんやかんや
});

お疲れ様でした!
今回は`Promise`を返すように実装したけど、普通にコールバックのスタイルでも実装はできる。

ちなみにAPIではなくてネイティブのコンポーネントを呼び出す仕組みもあるけど、そっちをSwiftでやる方法をまだ見いだせてないのでまたいつか・・。