Android
iOS
reactjs
reactnative

【React-Native】iOS, Android両対応する。

More than 1 year has passed since last update.

はじめに

iOS, Android 両対応させるにあたって学んだ小ネタを少し紹介させていただきます。
頑張って共通なコンポーネントを作る話というよりは、見た目は望んだ通りにつくるけどこうして分けてますよといった話です。
iOS, AndroidではUIをわけるべきという意見もあるかと思います。その際にもお役立てるかと思います。

ついでに

「iOSとAndroidでUI分けたいならReact Native使わない方がいい?」

と以前言われたことがあるんですが、技術的な要件にもよるけどもそんなこともないかと思います。

例えば、Reduxをつかった例になりますがこちらの図のように

Viewとロジックを分離させてしまえば、ロジックのコードはワンソースだけどiOS, AndroidでUIを分けれているというアプリケーションも作ることができます。当然ながら、ほぼほぼViewを共通化したコードも書くこともできます。もちろんロジックのテストコードは共通だし、JavaScriptがわかればかける、1ピクセルの修正を確認するのにビルドも必要ないし、それぞれのエンジニアを集めるといったことも必要ない。と行った具合にメリットがあるかなと思います。

本題 iOS, Androidで何が違うか

スクリーンショット 2016-12-01 12.00.22.png

公式のドキュメントからです。iOS, Androidと書かれているものがあります。これはそれぞれで扱うことができるプロパティが異なっていることを示しています。基本的には共通で使えるのですが中にはこのようにそれぞれで独自のものがあったりします。コンポーネントも同じように各プラットフォーム独自のものもあります。

他には

スクリーンショット 2016-12-01 12.02.52.png

このようにサードパティー製のライブラリもiOS, Android用でわかれていたりします。
あとは当然ですが、Androidでは、バックボタンで戻れるがiOSはスワイプバックで戻るなどなども・・・。

ですのでそのままでは、エラーが起こったりレイアウトがうまくいかなくなったり・・・・。
だからといって頑張って共通にしようとするとやりたいことを諦めなければならないことも出てきてしまいます・・・。ここからは対処法とtipsについて紹介していきます。

分け方

ファイル拡張子で分ける。

コンポーネント丸ごとiOS, Androidで分けてしまうといった場合はこの方法がいいかもしれません。パフォーマンス的にも以下の方法よりもこの方法の方がいいのかもしれません。
ただし、この方法は軽微な切り分けであってもファイル数が増えてしまうという欠点があります。

keyboard.ios.js
import React from 'react';

・・・・

const Keyboard = props => (

・・・・
keyboard.android.js
import React, { Component } from 'react';

・・・

class Keyboard extends Component {

・・・

といった具合に、それぞれの拡張子をつけたkeyboardコンポーネントを作成します。
これを他のファイルで、

app.js
import Keyboard from './keyboard';

・・・

と読み込んだ時には、それぞれのOSに対応しているファイルが読み込まれます。
iOSならばkeyboard.ios.js
Androidならばkeyboard.android.js
が読み込まれます。

Platformを使う

Platform.OS

Platformを使うことでOSを判断することもできます。
例えば、Androidでだけ使いたいメソッド setAndroidBackFunctionというものがあったとしてこれを使いたければ、

app.js
import {
   Platform,
} from 'react-native';

・・・

if (Platform.OS === 'android') setAndroidBackFunction();

・・・

とすることで同一ファイルでありながら、setAndroidBackFunctionをandroidの場合のみ動作させることができます。

Platform.select

Platformを使うことで場合分けをすることもできます。

import {
  Platform,
} from 'react-native';

const NAV_HEIGHT = Platform.select({
   ios: 64,
   android: 44,
});

これで、OSによってNAV_HEIGHTに異なる値を設定することができます。

実践的な小ネタ

やっていてぶつかった障害を書きます。

ナビゲーションについて

ステータスバーの色設定

react-nativeではステータスバーの色設定を変更するような機能も提供されているのですが、このAPIもOSによって異なります。

setup.ios.js
import {
   StatusBar,
} from 'react-native';

・・・

StatusBar.setBarStyle('dark-content', true);
setup.android.js
import {
   StatusBar,
} form 'react-native';

・・・

StatusBar.setBackgroundColor('#008080', true);

それぞれのメソッドはそれぞれのOSでしか使うことができません。

ステータスバーの高さ

iOSではステータスバーに高さがないので、背景を設定する、あるいはナビゲーションをステータスバーの高さも見込んだ高さに変える必要があります。
これに続いて、コンテンツのナビゲーションバー分の余白も変更する必要があります。

そのため、背景に被らないようにコンテンツを表示させるためには下記のようにします。

import {
    NAV_HEIGHT_IOS,
    NAV_HEIGHT_ANDROID,
} from './style-props';
import {
    Platform,
    StyleSheet,
    View,
} from 'react-native'

const styles = StyleSheet.create({
    container: {
       flex: 1,
       paddingTop: Platform.select({
           ios: NAV_HEIGHT_IOS,
           android: NAV_HEIGHT_ANDROID,
       }),
    },
});

const App = props => (
     <View style={styles.container}>
         {props.children}
     </View>
);
export default App;

ナビゲーションバーも同じでiOSで調整したものをそのまま使うことができないので切り分けでうまいこと表示させる必要があります。

画像の取り扱いについて

assetsをつかうとiOSアプリではレイアウトを合わせるのがとても簡単になります。もちろんパス指定も!
ただし、Androidでは使うことができないので共通化したいコンポーネントでは直接画像をフォルダにいれて使うのが良いと思います。

assetsの使い方

import {
    Image,
} from 'react-native';

const App = props => (
    <Image
       source={{ uri: 'background', }}
              style={{ flex: 1, }}
    >
        {props.children}
   </ Image>
);

export default App;

とすればassetsの場合は画像を引き延ばしていいflex:1となるように表示してくれます。

assetsじゃない時

flex:1としてもflex:1にならないので、flex:1と共にwidth: undefined, height: undefinedを指定します。

import {
    Image,
} from 'react-native';

const App = props => (
    <Image
       source={require('./../lib/images/background.png')}
       style={{ 
          flex: 1,
          width: undefined,
          height: undefined,
       }}
    >
        {props.children}
   </ Image>
);

export default App;

TextInputについて

multiLineについて

iOSではTextInputにmultilineを設定するといい感じに表示してくれます。
しかし、Androidではmultilineを設定してもテキストが真ん中から入力されます。

qiita_text.png

ですので、textAlignVertical: 'top'を設定しておくと大丈夫になります。

・・・
<TextInput
   style={{
      flex: 1,
      textAlignVertical: 'top',
   }}
/>
・・・

終わりに

以上です。動作周りは苦戦しなかったですが、レイアウト周りは色々と苦戦しました。
画像に関しては背景とイラストがあるようなものは、背景とイラストを分けておくといいかもしれません。
主にiOSアプリをReactNativeで作ってたんですが、Androidは未知な部分が多くて面白いです。
ご参考になると幸いです!また、これからも快適なReact Nativeライフを送れると幸いです!
また、お詳しい方はご助言いただけますと助かります。