React NativeのNative Componentでpropsの変更を検知する(iOS)
Native Componentを作成した際、JS側でpropsを更新し再レンダリングさせた場合Native Component(iOS)側ではどのようにそれを検知してViewに反映させればいいかMKMapView
を用いて見ていきます。
showsUserLocation
というプロパティにBooleanを設定することで自分の青ポチ(?)を表示したり非表示にしたりできます。
このshowsUserLocation
をpropsで設定できるようにします。
セットアップ
npx react-native init IosNativeComponentChangedProps --template react-native-template-typescript
cd IosNativeComponentChangedProps
yarn ios
まずはMKMapView
をJSXで表示できるようにします。
Xcodeでワークスペースを開いて、AppDelegate.mがある階層に以下のファイルを作成します。
最初のswiftファイルを作成するタイミングでBridging-Headerファイルを作成するか聞かれるので作成してください。
- MapView.swift
- MapManager.swift
- MapManager.m
Bridging-Headerを以下のように編集します。
#import <React/RCTViewManager.h>
MapView.swiftを以下のように編集します。
import Foundation
import MapKit
import CoreLocation
class MapView: MKMapView {
var locationManager = CLLocationManager()
override public init (frame: CGRect) {
super.init(frame: frame)
locationManager.requestWhenInUseAuthorization()
self.showsUserLocation = true
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) is not implemented.")
}
}
MapManager.swiftを以下のように編集します。
import Foundation
import UIKit
@objc(MapManager)
class MapManager: RCTViewManager {
override func view() -> UIView! {
return MapView()
}
override var methodQueue: DispatchQueue! {
return DispatchQueue.main
}
override static func requiresMainQueueSetup() -> Bool {
return true
}
}
MapManager.mを以下のように編集します。
#import <Foundation/Foundation.h>
#import <React/RCTViewManager.h>
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(MapManager, RCTViewManager)
@end
info.plistにLocation When In Use Usage Description
を追加してください。
これで一旦ネイティブ側はOKです。
App.tsxを以下のように編集します。
import {
requireNativeComponent,
StyleSheet,
View,
ViewProps,
} from 'react-native';
type MapViewProps = ViewProps;
const NativeMap = requireNativeComponent<MapViewProps>('Map');
const App = () => {
return (
<View>
<NativeMap style={styles.map} />
</View>
);
};
const styles = StyleSheet.create({
map: {
width: '100%',
height: '100%',
},
});
export default App;
これで再度ビルドしてください。アプリが起動するとマップが表示されていて、位置情報の使用を許可すると自分の青いマークが表示されていると思います。
propsを渡す
<NativeMap />
にpropsを渡せるようにします。
ネイティブ側では、デフォルトは自分の青マークは非表示にするのでMapView.swiftの
self.showsUserLocation = true
は削除します。
代わりにshowUserLocationPoint
プロパティを追加します。このshowUserLocationPointをJS側からporpsで渡せるようにします。
MapView.swiftを以下のように編集してください。
import Foundation
import MapKit
import CoreLocation
class MapView: MKMapView {
var locationManager = CLLocationManager()
@objc var showUserLocationPoint = false // 追加
override public init (frame: CGRect) {
super.init(frame: frame)
locationManager.requestWhenInUseAuthorization()
// self.showsUserLocation 削除
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) is not implemented.")
}
}
実行ファイルでRCT_EXPORT_VIEW_PROPERTY
マクロを使用してpropsとして宣言します。
MapManager.mを以下のように編集してください。
#import <Foundation/Foundation.h>
#import <React/RCTViewManager.h>
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(MapManager, RCTViewManager)
RCT_EXPORT_VIEW_PROPERTY(showUserLocationPoint, BOOL) // 追加
@end
これでporpsとして認識されます。App.tsxを以下のように編集してください。
import React, {useState} from 'react';
import {
requireNativeComponent,
StyleSheet,
TouchableOpacity,
View,
ViewProps,
} from 'react-native';
type MapViewProps = ViewProps & {
showUserLocationPoint: boolean; // 追加
};
const NativeMap = requireNativeComponent<MapViewProps>('Map');
const App = () => {
// 追加
const [showUserLocationPoint, setShowUserLocationPoint] = useState(true);
return (
<View>
<NativeMap
style={styles.map}
showUserLocationPoint={showUserLocationPoint} // 追加
/>
{/* 追加 */}
<TouchableOpacity
style={styles.button}
onPress={() => {
setShowUserLocationPoint(!showUserLocationPoint);
}}
/>
</View>
);
};
const styles = StyleSheet.create({
map: {
width: '100%',
height: '100%',
},
button: {
position: 'absolute',
bottom: 90,
backgroundColor: 'pink',
height: 70,
width: 70,
borderRadius: 70,
alignSelf: 'center',
},
});
export default App;
propsの変更を検知する
ネイティブ側のプロパティをJS側のporpsで渡すことができるようになりましたが、これでは青マークの表示非表示の変更をすることはできません。
didSetProps
というメソッドをオーバーライドすることで、porpsがセットされた毎に実行したい処理を書くことができます。
changedProps
というArrayのパラメータが渡され、更新されたpropsの名前が格納されています。
MapView.swiftを以下のように編集してください。
import Foundation
import MapKit
import CoreLocation
class MapView: MKMapView {
var locationManager = CLLocationManager()
@objc var showUserLocationPoint = false
// didSetPropsの追加
override func didSetProps(_ changedProps: [String]!) {
// 更新されたかどうかの判定
let shouldReconfigureUserLocationVisible = changedProps.contains("showUserLocationPoint")
if shouldReconfigureUserLocationVisible {
// ネイティブ側のプロパティの更新
configureUserLocationVisible()
}
}
func configureUserLocationVisible() {
self.showsUserLocation = showUserLocationPoint
}
override public init (frame: CGRect) {
super.init(frame: frame)
locationManager.requestWhenInUseAuthorization()
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) is not implemented.")
}
}
これでSwiftで作成したクラスのプロパティをJS側からのpropsで更新させることができました!
Discussion