こんにちは!
クラウドソリューション開発部の大川です。
弊社でスマホアプリ開発に携わる機会が増えており、いくつかの案件でReact Nativeを使用しています。そこでスマホアプリ開発案件でReact Nativeを使ってみてよかったことを実体験に基づいて紹介したいと思います!
今回の記事が今後、スマホアプリ開発系の案件でReact Nativeを使用するか迷っている人の参考になれば幸いです。
一度にiOSとAndroidアプリを開発!
- 同時に複数のプラットフォームに対応できるため、開発時間を大幅に短縮!
こんな時に便利
デザインと処理がOS毎に異ならない場合にReact Nativeを使えば言語をOS毎に分ける必要がないので便利です!
さらに開発費用もアプリ毎に言語を分ける必要がないため抑えることができます。
ブリッジ機能でネイティブ機能を活用!
- ネイティブコードとのシームレスな連携が可能で、ユーザーエクスペリエンスを向上させることができる!
ネイティブコードを使う場面
React Nativeで提供されているライブラリで実現できない場合や端末の機能関係を駆使したい場合にネイティブコードをブリッジします。
Bluetooth、画面収録、カメラ関係はReact Nativeで提供されているライブラリだとカスタマイズがあまりできなかったのでネイティブコードで実装したほうが良いと感じました。
アプリの画面を録画するライブラリはReact Nativeで提供されているものが、そもそも見つかりませんでした。。。
そこで!!
例として、React NativeのサンプルプロジェクトにSwiftで録画処理を実装して組み込んでみます!
- {プロジェクト名}-Bridging-Header.h
1 2 3 4 |
#import <React/RCTBridgeModule.h> #import <React/RCTViewManager.h> #import <React/RCTUIManager.h> |
- ScreenCaptureNativeModule.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#import <Foundation/Foundation.h> #import <React/RCTBridgeModule.h> @interface RCT_EXTERN_MODULE(ScreenCaptureNativeModule, NSObject) RCT_EXTERN_METHOD(startRecord: (id *) param callback: (RCTResponseSenderBlock)callback ) RCT_EXTERN_METHOD(stopRecord: (id *) param callback: (RCTResponseSenderBlock)callback ) @end |
- ScreenCaptureNativeModule.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
import UIKit import ReplayKit import Photos import React @objc(ScreenCaptureNativeModule) class ScreenCaptureNativeModule: NSObject, RPScreenRecorderDelegate { let recorder = RPScreenRecorder.shared() var recordingComponentId: String? @objc public func startRecord(_ node: NSNumber, callback: RCTResponseSenderBlock) { // 画面収録開始 startScreenRecording() callback(["funcCallbackWithParams"]) } @objc public func stopRecord(_ param: Any, callback: @escaping RCTResponseSenderBlock) { // 画面収録終了 stopScreenRecording(callback) callback(["funcCallbackWithParams"]) } private func startScreenRecording() { guard recorder.isAvailable, !recorder.isRecording else { return } recorder.startRecording { [weak self] error in if let error = error { print("録画開始中にエラーが発生しました: \(error.localizedDescription)") } else { print("録画が開始されました") } } } private func stopScreenRecording(_ callback: @escaping RCTResponseSenderBlock) { recorder.stopRecording { [weak self] (previewViewController, error) in guard error == nil else { print("録画停止中にエラーが発生しました: \(error!.localizedDescription)") callback([error!.localizedDescription]) return } if let previewViewController = previewViewController { previewViewController.previewControllerDelegate = self UIApplication.shared.keyWindow?.rootViewController?.present(previewViewController, animated: true, completion: nil) } else { print("プレビューコントローラがnilです") callback(["プレビューコントローラがnilです"]) } } } } extension ScreenCaptureNativeModule: RPPreviewViewControllerDelegate { func previewControllerDidFinish(_ previewController: RPPreviewViewController) { previewController.dismiss(animated: true) { print("プレビューコントローラが閉じられました") } } func previewController(_ previewController: RPPreviewViewController, didFinishWithActivityTypes activityTypes: Set<String>) { if activityTypes.contains(UIActivity.ActivityType.saveToCameraRoll.rawValue) { print("動画がカメラロールに保存されました") } else { print("動画が保存されませんでした") } previewController.dismiss(animated: true, completion: nil) } } |
- App.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
import React from 'react'; import type {PropsWithChildren} from 'react'; import {NativeModules, Platform, SafeAreaView, ScrollView, StatusBar, StyleSheet, Text, TouchableOpacity, useColorScheme, View} from 'react-native'; import {Colors, DebugInstructions, Header, LearnMoreLinks, ReloadInstructions} from 'react-native/Libraries/NewAppScreen'; type SectionProps = PropsWithChildren<{ title: string; }>; function Section({children, title}: SectionProps): React.JSX.Element { const isDarkMode = useColorScheme() === 'dark'; return ( <View style={styles.sectionContainer}> <Text style={[ styles.sectionTitle, { color: isDarkMode ? Colors.white : Colors.black, }, ]}> {title} </Text> <Text style={[ styles.sectionDescription, { color: isDarkMode ? Colors.light : Colors.dark, }, ]}> {children} </Text> </View> ); } function App(): React.JSX.Element { const isDarkMode = useColorScheme() === 'dark'; const backgroundStyle = { backgroundColor: isDarkMode ? Colors.darker : Colors.lighter, }; return ( <SafeAreaView style={backgroundStyle}> <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} backgroundColor={backgroundStyle.backgroundColor} /> <ScrollView contentInsetAdjustmentBehavior="automatic" style={backgroundStyle}> <Header /> <View style={{ backgroundColor: isDarkMode ? Colors.black : Colors.white, }}> <Section title="Step One"> Edit <Text style={styles.highlight}>App.tsx</Text> to change this screen and then come back to see your edits. </Section> <Section title="See Your Changes"> <ReloadInstructions /> </Section> <Section title="Debug"> <DebugInstructions /> </Section> <Section title="Learn More">Read the docs to discover what to do next:</Section> <LearnMoreLinks /> </View> <View> {Platform.OS === 'ios' && ( <View style={{ flexDirection: 'row', justifyContent: 'center', alignItems: 'center', }}> <TouchableOpacity onPress={() => { NativeModules.ScreenCaptureNativeModule.startRecord('', (result: number) => { if (result !== -1) { console.log('result>>>', result); } }); }}> <View style={{ borderWidth: 1, width: 100, height: 50, backgroundColor: 'red', flexDirection: 'row', justifyContent: 'center', alignItems: 'center', }}> <Text style={{color: '#FFFFFF', fontWeight: 'bold'}}>録画</Text> </View> </TouchableOpacity> <TouchableOpacity onPress={() => { NativeModules.ScreenCaptureNativeModule.stopRecord('startStop', (result: number) => { if (result !== -1) { console.log('result>>>', result); } }); }}> <View style={{ borderWidth: 1, width: 100, height: 50, backgroundColor: '#000000', flexDirection: 'row', justifyContent: 'center', alignItems: 'center', }}> <Text style={{color: '#FFFFFF', fontWeight: 'bold'}}>停止</Text> </View> </TouchableOpacity> </View> )} </View> </ScrollView> </SafeAreaView> ); } const styles = StyleSheet.create({ sectionContainer: { marginTop: 32, paddingHorizontal: 24, }, sectionTitle: { fontSize: 24, fontWeight: '600', }, sectionDescription: { marginTop: 8, fontSize: 18, fontWeight: '400', }, highlight: { fontWeight: '700', }, }); export default App; |
実際に組み込んだものを動作確認してみます。
web開発経験者でも分かりやすい!
JavaScriptベースのReact Nativeは、web開発経験者にとってなじみやすい言語とフレームワークなので、既存のスキルを活かしながら新たなモバイルアプリを開発できます。
まとめ
React Nativeを使えば、開発のしやすさと開発コストの観点からも大きなメリットがあります。ぜひ一度試してみてください!