React Native の Native Module に props を追加する方法(iOS編)
こんにちは!
KANNA の開発のお手伝いをしております、 len_prog です。
React NativeのNative Moduleでカメラ起動させてみた(iOS/Swift)
React NativeのNative Module化について(Android)
上記記事にて Native Module を使用して新規実装を行う方法については解説していただきましたが、実際の業務では既存の Native Module に対して props を追加する必要がある場面もあるかと思います。
今回、実際に私が iOS の Native Module に対して props を追加する機会がありましたので、せっかくなので調べたことを記事として残そうと思います。
サンプルアプリについて
今回の主題は新しい props を アプリから渡して iOS の Native Module 側で使用できる状態にすることなので、説明のノイズにならないようサンプルアプリ自体はかなり簡素なものとしました。
見たとおりではありますが、懐中電灯を再現したアプリケーションです。
ボタンをタップすると端末のライトのオン・オフができる機能のみを持ちます。
なお、端末のライトのオン・オフを行うロジックが Native Module として書かれております。
以下が実際に動作している様子です。
なお、サンプルアプリのソースコードは以下のリポジトリにございますので、ご興味ある方はご覧ください!
Native Module に機能を追加する
仮にこのサンプルアプリに対して、明るさをスライダーで調整できるようにしたいという要望が来たとします。
その場合、以下の手順で Native Module に明るさを表す props を追加する必要があります。
- アプリから任意の明るさを Native Module に渡せるようにする
- Native Module の props として明るさを受け取れるようにする
以下、上記それぞれの手順について詳しく解説していきます。
1. アプリから任意の明るさを Native Module に渡せるようにする
まず、アプリのコード上で明るさの値を表す brightness
state を定義し、 Native Module の props に渡せるようにします。
なお、ここについては至って普通の React のコードなので説明を省略いたします(追加:
ではじまるコメントが入っている箇所が追加部分です)。
// src/Page.tsx
import React, {useState} from 'react';
import {View, SafeAreaView, NativeModules, StyleSheet} from 'react-native';
import {Header, Button, Slider, Icon, lightColors} from '@rneui/themed';
export const Page = () => {
const [isActive, setIsActive] = useState(false);
const [brightness, setBrightness] = useState(0.5); // 追加: 現在の明るさを表す state。
const toggle = () => {
NativeModules.ElectricTorchModule.toggle(isActive);
setIsActive(prev => !prev);
};
// 追加: スライダーが動いた際に呼ばれる関数。現在の明るさを ElectricTorchModule#changeBrightness に渡す。
const changeBrightness = (nextBrightness: number) => {
if (!isActive) {
return;
}
NativeModules.ElectricTorchModule.changeBrightness(brightness);
setBrightness(nextBrightness);
};
return (
<SafeAreaView>
<Header
containerStyle={styles.container}
leftComponent={{icon: 'menu', color: '#FFF'}}
centerComponent={{text: '懐中電灯アプリ(仮)', style: styles.heading}}
/>
<View style={{alignItems: 'center'}}>
<Slider
disabled={!isActive}
value={brightness}
onValueChange={changeBrightness} // 追加: Slider の値が変わったときに changeBrightness 関数を呼ぶ。
minimumValue={0.01}
maximumValue={1.0}
step={0.01}
thumbStyle={{height: 20, width: 20, backgroundColor: 'transparent'}}
style={{width: '90%'}}
thumbProps={{
children: (
<Icon
name="brightness-6"
type="material-community"
size={20}
reverse
containerStyle={{bottom: 20, right: 20}}
color={isActive ? lightColors.secondary : lightColors.grey4}
/>
),
}}
/>
<View
style={{
flexDirection: 'row',
justifyContent: 'center',
marginTop: 20,
}}>
<Button
color="secondary"
title={isActive ? '消灯する' : '点灯する'}
onPress={toggle}
containerStyle={{width: 150}}
/>
</View>
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#397af8',
marginBottom: 20,
width: '100%',
paddingVertical: 10,
},
heading: {
color: 'white',
fontSize: 22,
fontWeight: 'bold',
},
});
2. Native Module の props として明るさを受け取れるようにする
手順1でアプリから明るさを props として渡せるようにしたので、次にそれを Native Module 側で受け取れるようにする必要があります。
この手順では2つのファイルを編集する必要があります。
1つは module の実態(ios/ElectricTorchModule.swift
)、もう1つは module を React Native から認識させるために必要なファイル(ios/ElectricTorchModule.m
)です。
// ios/ElectricTorchModule.swift
import Foundation
import AVFoundation
@objc(ElectricTorchModule)
class ElectricTorchModule: NSObject {
private var currentBrightness: Float = 0.5
// ...省略
// ① メソッドを定義する
@objc
func changeBrightness(_ nextBrightness: Float) {
currentBrightness = nextBrightness
let avCaptureDevice = AVCaptureDevice.default(for: AVMediaType.video)
if !avCaptureDevice!.hasTorch { // 端末のライトが使用できるか確認する
return
}
do {
try avCaptureDevice!.lockForConfiguration() // avCaptureDevice のメソッドを呼ぶ前に必ずロックする必要がある
try avCaptureDevice?.setTorchModeOn(level: nextBrightness)
} catch let error {
print(error)
}
avCaptureDevice!.unlockForConfiguration() // avCaptureDevice のロックを解除する
}
}
// ios/ElectricTorchModule.m
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(ElectricTorchModule, NSObject)
RCT_EXTERN_METHOD(toggle:(BOOL) isActive)
RCT_EXTERN_METHOD(changeBrightness:(float) nextBrightness) // ② 追加したメソッドのシグネチャを宣言する
@end
ここからは、2つのファイル内にあるコメントの ① メソッドを定義する
/ ② 追加したメソッドのシグネチャを宣言する
の箇所について説明します。
①メソッドを定義する
アプリ側から呼ぶメソッドを定義します。
React Native から呼べるようにするために最初に @objc
を書く必要がありますが、残りの箇所には普通にロジックを書けば大丈夫です。
② 追加したメソッドのシグネチャを宣言する
①で実装した Native Module のメソッドを React Native から呼び出せるようにするために、アプリと Native Module の橋渡しを行う Objective-C のファイル内でメソッドのシグネチャを宣言する必要があります。
私もそうですが Objective-C に慣れていない方も多いと思うので、このコードが何をしているのかを説明します。
RCT_EXTERN_METHOD
は、 React/RCTBridgeModule.h
で定義されているマクロです。このマクロにメソッドのシグネチャの情報を渡すことで React Native からこのメソッドを呼び出せるようになります。
RCT_EXTERN_METHOD(changeBrightness:(float) nextBrightness)
なお、RCT_EXTERN_METHOD
マクロに渡しているchangeBrightness:(Float) nextBrightness
は以下の構造になっています。
- changeBrightness…メソッド名
- (Float)…引数名の型
- nextBrightness…仮引数名
動作確認
さて、これで実装が終わったので XCode でビルドし、iPhone の実機で動作確認しましょう。
以下が実際に動作している様子です。
お疲れ様でした!
これで、無事 Native Module に新しく props を渡して新機能が実装できました 🎉
株式会社アルダグラムのTech Blogです。 世界中のノンデスクワーク業界における現場の生産性アップを実現する現場DXサービス「KANNA」を開発しています。 採用情報はこちら: herp.careers/v1/aldagram0508/
Discussion