React.js
Pocket

React Nativeでページ遷移の実装をやってみました。

今回やってみたのは3パターン。NeactNative標準のNavigatorを使うものとNavigatorIOSを使うもの、またライブラリのReact Native Navigationを使ったものです。

それぞれ使ってみたイメージは、Navigatorは簡単に実装できるけどネイティブっぽいデザインにするには自分で頑張る必要がある。NavigatorIOSはネイティブに近いけどIOSのみでAndroidは別で実装しないとダメ。React Native NavigationはIOS、Androidどちらも対応してるけど、実装が大変という感じです。どれも使い込んだ訳ではないので、ざっくりとした感想です。

Navigator

必要なのはNavigatorですが、タップした時にハイライトしてくれるTouchableHighlightも追加します。navigator.pushで次のページに遷移し、 navigator.popで一つ前にもどります。

index.ios.js

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  // ここ2つ追加
  Navigator,
  TouchableHighlight
} from 'react-native';

class Scene extends Component {
  render() {
    return (
      <View style={styles.container}>
        <Text>ページタイトル: {this.props.title}</Text>
        <TouchableHighlight onPress={this.props.onForward}>
          <Text>進む</Text>
        </TouchableHighlight>
        <TouchableHighlight onPress={this.props.onBack}>
          <Text>戻る</Text>
        </TouchableHighlight>
      </View>
    )
  }
}

export default class reactapp extends Component {
  render() {
    return (
      <Navigator
        initialRoute={{title: 'Initial Scene', index: 0}}
        renderScene={(route, navigator) =>
          <Scene title={route.title}
            onForward={() => {
              const nextIndex = route.index + 1;
              navigator.push({
                title: 'Scene ' + nextIndex,
                index: nextIndex,
              });
            }}
            onBack={() => {
              if (route.index > 0) {
                navigator.pop();
              }
            }}
        />
      }
      />
    )
  }
}

AppRegistry.registerComponent('reactapp', () => reactapp);

完成イメージ

Navigator

NavigatorIOS

index.ios.js

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  // 追加
  NavigatorIOS,
  TouchableHighlight
} from 'react-native';

class Scene extends Component {
  render() {
    return (
      <View style={styles.container}>
        <TouchableHighlight onPress={() => {
          const nextIndex = this.props.index +1;
          this.props.navigator.push({
            component: Scene,
            title: 'Scene ' + nextIndex,
            passProps: {
              index: nextIndex
            },
          })
        }}>
        <Text>進む</Text>
        </TouchableHighlight>
      </View>
    )
  }
}

export default class reactapp extends Component {
  render() {
    return (
      <NavigatorIOS
        style={{flex: 1}}
        itemWrapperStyle={{flex: 1, paddingTop: 64}}
        initialRoute={{
          component: Scene,
          title: 'Initial Scene',
          passProps: {
            index: 0,
          },
        }}
      />
    );
  }
}

AppRegistry.registerComponent('reactapp', () => reactapp);

完成イメージ

NavigatorIOS

React Native Navigation

React Native NavigationはWixが作っています。

React Native Navigation

高機能でいろんなナビゲーションを実装してくれるみたいですが、導入までにちょっと手順が必要です。

iOSの場合の手順はこちらに書いてあります。

Installation iOS

まずはライブラリをインストール

npm install react-native-navigation@next --save

react-nativeコマンドが使えるなら、このコマンドで設定終了。使えない場合は手動でXcodeから設定となります。(Linking Libraries

react-native link

そして「AppDelegate.m」をXcodeから以下のリンク先にあるコードに書き換えます。

AppDelegate.m

ここまでやってシュミレーターを起動して特にエラーがでなければ無事準備完了のはずです。

プロジェクトフォルダ内に新しくディレクトリを作ります。今回は「screens」という名前で。そのディレクトリの中にページ遷移用のファイルを作っておきます。NavigatorやNavigatorIOSでやっていたプッシュのページ遷移に加えて、タブ切り替えもやってみます。

├── index.ios.js
└── screens
    ├── FirstTabScreen.js
    ├── PushedScreen.js
    ├── SecondTabScreen.js
    ├── ThirdTabScreen.js
    └── index.js

index.ios.js

import {
  Platform
 } from 'react-native';
 import {Navigation} from 'react-native-navigation';

 // screen related book keeping
 import {registerScreens} from './screens';
 registerScreens();

 const createTabs = () => {
  let tabs = [
    {
      label: 'One',
      screen: 'example.FirstTabScreen',
      title: 'Screen One'
    },
    {
      label: 'Two',
      screen: 'example.SecondTabScreen',
      title: 'Screen Two',
      navigatorStyle: {
        tabBarBackgroundColor: '#3b5998',
      }
    },
    {
      label: 'Third',
      screen: 'example.ThirdTabScreen',
      title: 'Screen Third',
      navigatorStyle: {
        tabBarBackgroundColor: '#3b5998',
      }
    }
  ];
  if (Platform.OS === 'android') {
    tabs.push({
      label: 'Collapsing',
      screen: 'example.CollapsingTopBarScreen',
      title: 'Collapsing',
    });
  }
  return tabs;
 };
 // this will start our app
 Navigation.startTabBasedApp({
  tabs: createTabs()
 });

screens/index.js

import {Navigation} from 'react-native-navigation';

import FirstTabScreen from './FirstTabScreen';
import SecondTabScreen from './SecondTabScreen';
import ThirdTabScreen from './ThirdTabScreen';
import PushedScreen from './PushedScreen';

// register all screens of the app (including internal ones)
export function registerScreens() {
  Navigation.registerComponent('example.FirstTabScreen', () => FirstTabScreen);
  Navigation.registerComponent('example.SecondTabScreen', () => SecondTabScreen);
  Navigation.registerComponent('example.ThirdTabScreen', () => ThirdTabScreen);
  Navigation.registerComponent('example.PushedScreen', () => PushedScreen);
}

screens/FirstTabScreen.js

import React, {Component} from 'react';
import {
  Text,
  View,
  TouchableOpacity,
  StyleSheet,
  Alert,
  Platform
} from 'react-native';
import {Navigation} from 'react-native-navigation';

export default class FirstTabScreen extends Component {
  static navigatorButtons = {
    leftButtons: [{
      id: 'menu'
    }],
    rightButtons: [
      {
        title: 'Edit',
        id: 'edit'
      },
      {
        id: 'add'
      }
    ]
  };
  static navigatorStyle = {
    navBarBackgroundColor: '#3b5998',
    navBarTextColor: '#fff',
    navBarSubtitleTextColor: '#ff0000',
    navBarButtonColor: '#ffffff',
    statusBarTextColorScheme: 'light',
    tabBarBackgroundColor: '#4dbce9',
    tabBarButtonColor: '#ffffff',
    tabBarSelectedButtonColor: '#ffff00'
  };

  constructor(props) {
    super(props);
    // if you want to listen on navigator events, set this up
    this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
  }

  onNavigatorEvent(event) {
    if (event.id === 'menu') {
      this.props.navigator.toggleDrawer({
        side: 'left',
        animated: true
      });
    }
    if (event.id === 'edit') {
      Alert.alert('NavBar', 'Edit button pressed');
    }
    if (event.id === 'add') {
      Alert.alert('NavBar', 'Add button pressed');
    }
  }

  render() {
    return (
      <View style={{flex: 1, padding: 20}}>
        <TouchableOpacity onPress={ this.onPushPress.bind(this) }>
          <Text style={styles.button}>Push Plain Screen</Text>
        </TouchableOpacity>
      </View>
    );
  }

  onPushPress() {
    this.props.navigator.push({
      title: "More",
      screen: "example.PushedScreen"
    });
  }

  onPushStyledPress() {
    this.props.navigator.push({
      title: "Styled",
      screen: "example.StyledScreen"
    });
  }

  onModalPress() {
    this.props.navigator.showModal({
      title: "Modal",
      screen: "example.ModalScreen"
    });
  }

  onLightBoxPress() {
    this.props.navigator.showLightBox({
      screen: "example.LightBoxScreen",
      style: {
        backgroundBlur: "dark"
      },
      passProps: {
        greeting: 'hey there'
      },
    });
  }

  onInAppNotificationPress() {
    this.props.navigator.showInAppNotification({
      screen: "example.NotificationScreen"
    });
  }

  onStartSingleScreenApp() {
    Navigation.startSingleScreenApp({
      screen: {
        screen: 'example.FirstTabScreen'
      }
    });
  }
}

const styles = StyleSheet.create({
  button: {
    textAlign: 'center',
    fontSize: 18,
    marginBottom: 10,
    marginTop: 10,
    color: 'blue'
  }
});

screens/SecondTabScreen.js

import React, {Component} from 'react';
import {
  Text,
  View,
  ScrollView,
  TouchableOpacity,
  StyleSheet,
  Alert
} from 'react-native';

export default class SecondTabScreen extends Component {
  static navigatorStyle: {
    navBarBackgroundColor: '#3b5998',
    navBarTextColor: '#fff',
    navBarSubtitleTextColor: '#ff0000',
    navBarButtonColor: '#ffffff',
    statusBarTextColorScheme: 'light'
  };

  constructor(props) {
    super(props);
    this.buttonsCounter = 0;
    // if you want to listen on navigator events, set this up
    this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
  }

  render() {
    return (
      <View style={styles.container}>

        <TouchableOpacity onPress={ this.onChangeButtonsPress.bind(this) }>
          <Text style={styles.button}>Change Buttons</Text>
        </TouchableOpacity>

        <TouchableOpacity onPress={ this.onChangeTitlePress.bind(this) }>
          <Text style={styles.button}>Change Title</Text>
        </TouchableOpacity>

        <TouchableOpacity onPress={ this.onSwitchTabPress.bind(this) }>
          <Text style={styles.button}>Switch To Tab#1</Text>
        </TouchableOpacity>

        <TouchableOpacity onPress={ this.onSetTabBadgePress.bind(this) }>
          <Text style={styles.button}>Set Tab Badge</Text>
        </TouchableOpacity>

        <TouchableOpacity onPress={ this.onToggleTabsPress.bind(this) }>
          <Text style={styles.button}>Toggle Tabs</Text>
        </TouchableOpacity>

      </View>
    );
  }
  onChangeTitlePress() {
    this.props.navigator.setTitle({
      title: Math.round(Math.random() * 100000).toString()
    });
  }
  onChangeButtonsPress() {
    let buttons;
    if (this.buttonsCounter % 3 == 0) {
      buttons = [
        {
          title: 'Edit',
          id: 'edit',
          disabled: true
        },
        {
          id: 'add'
        }
      ];
    } else if (this.buttonsCounter % 3 == 1) {
      buttons = [{
        title: 'Save',
        id: 'save'
      }];
    } else {
      buttons = [];
    }
    this.buttonsCounter++;

    this.props.navigator.setButtons({
      rightButtons: buttons,
      animated: true
    });
  }
  onSwitchTabPress() {
    this.props.navigator.switchToTab({
      tabIndex: 0
    });
  }
  onSetTabBadgePress() {
    this.props.navigator.setTabBadge({
      badge: 12
    });
  }
  onToggleTabsPress() {
    this.props.navigator.toggleTabs({
      to: this.tabsHidden ? 'shown' : 'hidden'
    });
    this.tabsHidden = !this.tabsHidden;
  }
  onNavigatorEvent(event) {
    // handle a deep link
    if (event.type == 'DeepLink') {
      const parts = event.link.split('/');
      if (parts[0] == 'tab2') {
        this.props.navigator.resetTo({
          title: "Replaced Root",
          screen: parts[1],
          animated: true
        });
        this.props.navigator.switchToTab();
      }
    }
    // handle a button press
    if (event.type == 'NavBarButtonPress') {
      if (event.id == 'edit') {
        Alert.alert('NavBar', 'Dynamic Edit button pressed');
      }
      if (event.id == 'add') {
        Alert.alert('NavBar', 'Dynamic Add button pressed');
      }
      if (event.id == 'save') {
        Alert.alert('NavBar', 'Dynamic Save button pressed');
      }
    }
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: 'white'
  },
  button: {
    textAlign: 'center',
    fontSize: 18,
    marginBottom: 10,
    marginTop:10,
    color: 'blue'
  }
});

screens/ThirdTabScreen.js

import React, {Component} from 'react';
import {
  Text,
  View,
  TouchableOpacity,
  StyleSheet,
  Alert,
  Platform
} from 'react-native';
import {Navigation} from 'react-native-navigation';

export default class ThirdTabScreen extends Component {
  static navigatorButtons = {
    leftButtons: [{
      id: 'menu'
    }],
    rightButtons: [
      {
        title: 'Edit',
        id: 'edit'
      },
      {
        id: 'add'
      }
    ]
  };
  static navigatorStyle = {
    navBarBackgroundColor: '#3b5998',
    navBarTextColor: '#fff',
    navBarSubtitleTextColor: '#ff0000',
    navBarButtonColor: '#ffffff',
    statusBarTextColorScheme: 'light',
    tabBarBackgroundColor: '#4dbce9',
    tabBarButtonColor: '#ffffff',
    tabBarSelectedButtonColor: '#ffff00'
  };

  constructor(props) {
    super(props);
    // if you want to listen on navigator events, set this up
    this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
  }

  onNavigatorEvent(event) {
    if (event.id === 'menu') {
      this.props.navigator.toggleDrawer({
        side: 'left',
        animated: true
      });
    }
    if (event.id === 'edit') {
      Alert.alert('NavBar', 'Edit button pressed');
    }
    if (event.id === 'add') {
      Alert.alert('NavBar', 'Add button pressed');
    }
  }

  render() {
    return (
      <View style={{flex: 1, padding: 20}}>
        <TouchableOpacity onPress={ this.onPushPress.bind(this) }>
          <Text style={styles.button}>Push Plain Screen</Text>
        </TouchableOpacity>
      </View>
    );
  }

  onPushPress() {
    this.props.navigator.push({
      title: "More",
      screen: "example.PushedScreen"
    });
  }

  onPushStyledPress() {
    this.props.navigator.push({
      title: "Styled",
      screen: "example.StyledScreen"
    });
  }

  onModalPress() {
    this.props.navigator.showModal({
      title: "Modal",
      screen: "example.ModalScreen"
    });
  }

  onLightBoxPress() {
    this.props.navigator.showLightBox({
      screen: "example.LightBoxScreen",
      style: {
        backgroundBlur: "dark"
      },
      passProps: {
        greeting: 'hey there'
      },
    });
  }

  onInAppNotificationPress() {
    this.props.navigator.showInAppNotification({
      screen: "example.NotificationScreen"
    });
  }

  onStartSingleScreenApp() {
    Navigation.startSingleScreenApp({
      screen: {
        screen: 'example.ThirdTabScreen'
      },
      drawer: {
        left: {
          screen: 'example.SideMenu'
        }
      }
    });
  }
}

const styles = StyleSheet.create({
  button: {
    textAlign: 'center',
    fontSize: 18,
    marginBottom: 10,
    marginTop: 10,
    color: 'blue'
  }
});

screens/PushedScreen.js

import React, {Component} from 'react';
import {
  Text,
  View,
  ScrollView,
  TouchableOpacity,
  StyleSheet
} from 'react-native';

export default class PushedScreen extends Component {
  static navigatorStyle = {
    drawUnderTabBar: true
  };
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <View style={styles.container}>
        <TouchableOpacity onPress={ this.onPushPress.bind(this) }>
          <Text style={styles.button}>Push Plain Screen</Text>
        </TouchableOpacity>

        <TouchableOpacity onPress={ this.onPopPress.bind(this) }>
          <Text style={styles.button}>Pop Screen</Text>
        </TouchableOpacity>

        <TouchableOpacity onPress={ this.onPopToRootPress.bind(this) }>
          <Text style={styles.button}>Pop To Root</Text>
        </TouchableOpacity>
      </View>
    );
  }
  onPushPress() {
    this.props.navigator.push({
      title: "More",
      screen: "example.PushedScreen"
    });
  }
  onPushStyledPress() {
    this.props.navigator.push({
      title: "More",
      screen: "example.StyledScreen"
    });
  }
  onPopPress() {
    this.props.navigator.pop();
  }
  onPopToRootPress() {
    this.props.navigator.popToRoot();
  }
  onResetToPress() {
    this.props.navigator.resetTo({
      title: "New Root",
      screen: "example.StyledScreen",
      animated: true
    });
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: 'white'
  },
  button: {
    textAlign: 'center',
    fontSize: 18,
    marginBottom: 10,
    marginTop:10,
    color: 'blue'
  }
});

完成イメージ

React Native Navigation

もしエラーが出る場合は、シュミレーターを再起動したり、エラーメッセージに出るコマンドを実行してみるとなおると思います。

さいごに

長くなりましたが、3パターン試してみました。アプリ作るなら、ちょっとハードルありますが、React Native Navigationを使うのが本格的に作れるかなという印象です。

他にもナビゲーション系のライブラリはあるみたいなので、また試してみたいと思います。

Pocket

Category : Tag :