My Note Pad

エンジニアリングや日々の雑感を書いていきます

ReactNativeでiOSの動作環境を準備

前回↓に続いて、iOSでも動作するように準備してみた。

yuki10.hatenablog.com

公式ページの手順を参考に。

を選択しました。

facebook.github.io

少し眺めて、「あれ、やること無くないか・・?」という状態に。
※既にXCodeはインストール済み

というわけで、前回作成したAwesomeProject配下で以下のコマンドを実行する。

$ react-native run-ios

あっさり起動したので拍子抜け。

f:id:yuki10k:20170117005512p:plain

せっかくなので、コードのホットリローディングを試してみる。

プロジェクト直下のindex.ios.jsを編集する。

        <Text style={styles.welcome}>
-          Welcome to React Native!
+          Welcome to React Native Test!
        </Text>  

保存して、iOSシミュレータ上でCommand⌘ + R
そうするとすぐに画面がリフレッシュされて、変更した内容が表示された。

f:id:yuki10k:20170117005922p:plain

これはすごい・・!

GenymotionでReactNativeの動作環境を作った

iOSアプリ開発の学習をしていましたが、最近ReactNativeが活発になってきているので少し試してみようと思い、試してみることにした。

インストール方法

公式ページが分かりやすい

facebook.github.io

の手順で試してみた。

Node, Watchmanのインストール

nodeはanyenvで入れているのでスキップ。
watchmanはFacebook製のファイルシステムの変更を監視するツールらしい。

$ brew install watchman

React Native CLIのインストール

npmを使って

$ npm install -g react-native-cli

Android Studioのダウンロードとインストール

Android Studioは既に入っているのでスキップ。
JavaがJDK8以上が必要らしいので、バージョンだけ確認。

$ javac -version
javac 1.8.0_102

インストール済みのSDKなどはこちらから確認できるので状況に応じて追加でインストールする。

Android Stuid起動画面の右下のConfigure

f:id:yuki10k:20170116192316p:plain

SDK Managerを選択

f:id:yuki10k:20170116192326p:plain

Launch Standalone SDK Managerを選択

f:id:yuki10k:20170116192335p:plain

必要なパッケージのを選択。

あとで出てくるけどAndroid SDK Build-Toolsが必要で、以下のバージョンをインストールしておく必要がある。
Android SDK Build-Tools 23.0.1

f:id:yuki10k:20170116192349p:plain

Package毎に Accept Licenseを選択してinstall

f:id:yuki10k:20170116192401p:plain

ANDROID_HOMEの設定

手順では~/.bashrcですがzshを使っているので、~/.zshrcに設定した。

export ANDROID_HOME=~/Library/Android/sdk
export PATH=${PATH}:${ANDROID_HOME}/tools
export PATH=${PATH}:${ANDROID_HOME}/platform-tools

ターミナルを再起動するか、sourceで読み込み、確認する。

$ source ~/.zshrc
$ env | grep ANDROID
ANDROID_HOME=/Users/USER_NAME/Library/Android/sdk

Genymotionのインストール

www.genymotion.com

※Virtual Boxが必要なので、別途インストールしておく。

公式ページからダウンロードする。
アカウントにサインインした状態で
Resources -> Fun Zone -> からPersonal Editionをダウンロードする。
※個人での利用に限り、無料で利用できる。

f:id:yuki10k:20170116192956p:plain

起動したら Addを選択。

f:id:yuki10k:20170116193003p:plain

利用したいVirtual Deviceを選択する。
Nexus 5Xとかを選んでおけば問題ない。
※サインインしている必要あり。

f:id:yuki10k:20170116193011p:plain

Virtual deviceの名前を入力し、Next。

f:id:yuki10k:20170116193020p:plain

バイス一覧に追加された。

f:id:yuki10k:20170116193027p:plain

そのままStartを選択すると、VMが起動する。

f:id:yuki10k:20170116193033p:plain

そのまま起動させておく。

ReactNativeアプリケーションの作成と実行

適当なディレクトリに移動して、以下のコマンドを実行する

$ react-native init AwesomeProject
$ cd AwesomeProject
$ react-native run-android

ここで起動できなくてかなりハマった。

Android SDK Build-tools versionは23系の最新を使えばいいと思っていたので23系の最新を入れていたけど、実際は以下のバージョンで固定だった。
Android SDK Build-tools version 23.0.1

23.0.1をインストールすると無事にGenymotion上でReactNativeのアプリを起動することができた。

f:id:yuki10k:20170116193057p:plain

BFGを使用するにあたって色々調べてみた

BFGとは

rtyley.github.io

git-filter-branchの代わりに使うことができる(事もある)

概要

  • クレイジーな巨大なファイルをgitリポジトリから削除できる
  • パスワード、資格情報、プライベートな情報も削除できる
  • scalaで実装されており、実行にはjavaが必要

git-filter-branchと比較して

  • 10 - 720倍高速に動作する
  • よりシンプル
  • コードが公開されているのでカスタマイズして使用できる

使い所

  • git-filter-branchのオプションが結構複雑なのでシンプルに実行したいとき
  • git-filter-branchでは時間がかかりすぎて終りが見えないとき

といった感じ。

各オプションの詳細はこちら

https://repository.sonatype.org/service/local/repositories/central-proxy/content/com/madgag/bfg/1.12.14/bfg-1.12.14.txt

使用してみる

BFGの実行ファイルダウンロード

# Homebrewでも入れられるらしい
$ wget http://repo1.maven.org/maven2/com/madgag/bfg/1.12.14/bfg-1.12.14.jar

$ java -version
java version "1.8.0_102"
Java(TM) SE Runtime Environment (build 1.8.0_102-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.102-b14, mixed mode)

まずは検証用に適当なリポジトリを作成。

空のリポジトリを作成

# bareリポジトリ
$ mkdir hoge_bare.git
$ cd hoge_bare.git
$ git init --bare
Initialized empty Git repository in /Users/USER_NAME/Documents/bfg_test/hoge_bare.git/


# gitリポジトリ
$ cd ..
$ mkdir hoge
$ cd hoge
$ git init
Initialized empty Git repository in /Users/USER_NAME/Documents/bfg_test/hoge/.git/


# bareをremoteとして登録
$ git remote add origin ../hoge_bare.git
$ git remote -v
origin  ../hoge_bare.git (fetch)
origin  ../hoge_bare.git (push)


# 適当な画像を置いてcommit push
$ git add .
$ git commit -m "initial commit"
[master (root-commit) 14668fc] initial commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 1.png
 
$ git push origin master                                                                                                        (master)
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 54.77 KiB | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To ../hoge_bare.git
 * [new branch]      master -> master

適当にブランチを切ったりしてワークツリーを作成し、remoteにpushしておく
ここでは1コミットにpngファイルを1つコミットしている。

f:id:yuki10k:20170113193403p:plain

実際にやってみる

パターン①:特にオプションを付けない(png指定のみ)

# bareからmirrorでcloneする
$ git clone --mirror hoge_bare.git hoge_test1.git
$ java -jar bfg-1.12.14.jar --delete-files '*.png' hoge_test1.git
$ cd hoge_test1.git
$ git reflog expire --expire=now --all && git gc --prune=now --aggressive

$ cd ..
$ git clone hoge_test1.git hoge_result1

オプションなしの場合、HEADが指しているブランチのみプロテクトの対象となり、最新のコミットが保持される。

masterでは最初のコミットと、5番目のコミットで追加されたpngファイルが残っており、一番最初に追加した画像が5番目のコミットで追加したことになっている。

f:id:yuki10k:20170113193458p:plain

hogeブランチやfugaブランチは何も無かったことに。

f:id:yuki10k:20170113193507p:plain

masterの1番最初のコミットでの画像追加が無かったことに。

f:id:yuki10k:20170113193514p:plain

パターン②:--no-blob-protectionオプションを付けてみる

このオプションは対象ファイルを履歴上からも全て消し去る。

# bareからmirrorでcloneする
$ git clone --mirror hoge_bare.git hoge_test2.git
$ java -jar bfg-1.12.14.jar --no-blob-protection --delete-files '*.png' hoge_test2.git
$ cd hoge_test2.git
$ git reflog expire --expire=now --all && git gc --prune=now --aggressive

$ cd ..
$ git clone hoge_test2.git hoge_result2

消え去っている。
master以外のブランチからも削除されている。

f:id:yuki10k:20170113194056p:plain

完全に消し去りたい時はこれで良さそう。

パターン③:--protect-blobs-fromオプションを付けてみる

--protect-blobs-fromでは、プロテクトするブランチを指定できる。
master, hogeブランチをプロテクト対象にする。

# bareからmirrorでcloneする
$ git clone --mirror hoge_bare.git hoge_test3.git
$ java -jar bfg-1.12.14.jar --protect-blobs-from master,hoge --delete-files '*.png' hoge_test3.git
$ cd hoge_test3.git
$ git reflog expire --expire=now --all && git gc --prune=now --aggressive

$ cd ..
$ git clone hoge_test3.git hoge_result3

masterは、パターン①と同様で、masterブランチで追加したファイルが最新のコミットにまとめらている。

f:id:yuki10k:20170113194304p:plain

hogeブランチは、masterからの派生時に存在した1.pnghogeブランチで追加した画像2つが最新のコミットでaddされたことになっている。

f:id:yuki10k:20170113194307p:plain

当然、2.pngを追加した時点のコミットは空コミットに。

f:id:yuki10k:20170113194315p:plain

fugaブランチはプロテクトの対象にしなかったので、画像は追加されない。

f:id:yuki10k:20170113194310p:plain

masterにhogeブランチとfugaブランチをそれぞれマージしてみる。

fugaブランチのコミット履歴上、何も追加していないことになっているので、fugaブランチのマージ履歴は空に。

f:id:yuki10k:20170113194318p:plain

hogeブランチのマージでは、2.png, 4.pngが正しくマージされる。
1.pnghogeの最新コミット履歴で追加したことになっていたけど、ここでコンフリクトなどは発生しない。

f:id:yuki10k:20170113194322p:plain

最終的なmasterブランチでは、fugaブランチで追加した画像ファイル以外の画像がちゃんと残っている。

f:id:yuki10k:20170113194326p:plain

その他注意点

  • --delete-foldersはパスの区切り文字を使えず、フォルダ名のみで探して削除するので、同名のフォルダが違うパスで存在する場合、両方とも削除されてしまう。(未検証)
  • 特定のフォルダ以下を対象にするといった事はできない。(未検証)
  • bfg自体はかなり高速に動作するものの、その後のgit gc --prune=now --aggressiveはファイル数やサイズによっては結構時間がかかる。
  • それでもgit-filter-branchより速い
  • CPUコア数でかなり差が出るのでMac Book Pro 13inchとかだと結構辛い

雑感

  • filter-branchと比べると実際にやっていることはシンプル。
  • バイナリファイルをまとめて消し去りたいときには、--no-blob-protectionを付けて実行するときれいにできるのでgit lfsに移行したい時なんかには重宝しそう。
  • 対象ファイルをプロテクトした場合、最新のコミット履歴で追加されたように見えるので、後で履歴を見直したときには何だこれ?となりそう。

ウィッチャー3を購入!

PS4版 ウィッチャー3(GAME OF THE YEAR EDITION)をAmazonで購入し、届きました。

www.spike-chunsoft.co.jp

f:id:yuki10k:20170112010343j:plain

まだプレイしていないけど、以下のようなゲームのようです。

オープンワールド

FF15で日本国内でも知名度が一気に上がった気がするけど、本格的なオープンワールドゲームらしい。

オープンワールド - Wikipedia

オープンワールド (Open World) とは、英語におけるコンピュータゲーム用語で、舞台となる広大な世界を自由に動き回って探索・攻略できるように設計されたレベルデザインを指す言葉である。

FF15はクリアするまではプレイしたのでなんとなく分かった気になっていたものの、ウィッチャー3をプレイした後にFF15をプレイした人はガッカリすることが多かったとか。
FF15でも自由に行動できる事が多かったけど、それ以上に広大な世界が広がっていそうなので楽しみ。

ファンタジーRPG

PVを見た感じだとダークファンタジーなRPGという感じ。
世界観や戦闘シーンがダークソウル3みたいな雰囲気。
ダークソウル3は死にまくったものの、難易度調整が絶妙で、途中で投げることなくクリアできた。
あそこまで難易度高くなくて良いけど、プレイスタイルも自由なのでこちらも楽しみ。

Skyrimと迷った

Skyrimと迷いました。
Skyrimの方がよく聞く気がするけど、好みが別れるっぽかったのでPVを見てウィッチャー3の雰囲気が自分に合ってそうだったので、今回はウィッチャー3にしました。
Skyrimは自由にキャラメイキングできるので、そのあたりも好みが分かれそう。

そもそも何で購入したのか

一番は英語の勉強のため。
iknowを利用してかれこれ半年以上、毎日30分〜1時間継続して英語学習ができている。

iknow.jp

おかげで、ある程度ボキャブラリーが増えてきたので、少しずつ英語が読めるようになってきた。
次に何をしようかと考えていたときに、技術ドキュメントを読む事が多いのでリーディングを伸ばしたいと思い、いい題材を探していた。
ウィッチャー3は英語音声 + 英語字幕を表示できる上にクエストなどで文章もかなり多いらしく楽しみながら英語を大量にリーディングできるのではないかという甘い考え。
自分の今の語彙力で対応できるかわからないけど、まずはやってみようと思う。

まとめ

  • ウィッチャー3はオープンワールドなダークファンタジーRPG
  • 日本版でも英語ボイス + 英語字幕表示可
  • 英語学習に活用したい(という甘い考え)
  • そもそも帰宅できるのが日が変わってからというブラックぶりなのでプレイする時間が殆どない・・
  • まだプレイしていない・・

ゼロから作るDeep Learningの学習環境をDockerで作った

会社で「ゼロから作るDeep Learning」を使ってDeep Learningを学ぶ勉強会をしようということになったので、環境を作ってみた。

使用する書籍はこちら↓

www.oreilly.co.jp

なぜDockerなのか

  • Pythonや各ライブラリのバージョン差異によるメンバー間の挙動の違いが発生しないようにしたい。
  • そもそも環境の構築が面倒なのでサクッと作れるようにしたい。

といった感じ。

というわけで、Docker for Macを使って学習環境を作った。

環境・バージョンなど

Mac

  • OS X ElCapitan (10.11.6)
  • Docker for Mac 1.12.5 (最新で大丈夫な気がする)

Docker

  • Python 3.5.2 :: Anaconda 4.2.0 (64-bit)
  • Jupyter 4.2.0

Docker for Macのインストール

まだインストールしていなければこちらからDocker for Macをダウンロードしてインストールする。
詰まるところは無いと思うのでインストール方法は省略。

docs.docker.com

公式のGit HubをForkしてClone

こちらがオライリーが公式に公開している、「ゼロから作るDeep Learning」用のGitHubリポジトリ
色々コードを触りたいので、自分のGitHubアカウントにForkして、Cloneする。

github.com

今回は/Users/{USER_NAME}/github/deep-learning-from-scratchにcloneした。

$ cd ~
$ mkdir github
$ cd github
$ git clone https://github.com/{GITHUB_ACCOUNT}/deep-learning-from-scratch.git

Jupyter notebookを使うことになるので、.gitignoreに以下の行を追加しておくといい。

.ipynb_checkpoints/ 

Docker Imageの作成

※Docker for Macは起動させておく。

Docker Imageはこちらのannaconda用イメージを利用した。

docker-images/anaconda3 at master · ContinuumIO/docker-images · GitHub

# docker imageをpullする
$ docker pull continuumio/anaconda3

# docker imageを作成(ざっくり以下の設定を行っている)
# cloneしておいたGitHub用のリポジトリを、/opt/notebooks/deep-learning-from-scratchにマッピング
# 8888番ポートをフォワード
$ docker run -i -t -v /Users/{USER_NAME}/github/deep-learning-from-scratch:/opt/notebooks/deep-learning-from-scratch -p 8888:8888 continuumio/anaconda3 /bin/bash

docker runするとプロンプトがdocker内に移動するので、以下のコマンドでJupyter notebookをインストールする。

# /opt/notebooksをJupyterのルートディレクトリにし、8888番ポートを利用する
$ /opt/conda/bin/conda install jupyter -y --quiet && /opt/conda/bin/jupyter notebook --notebook-dir=/opt/notebooks --ip='*' --port=8888 --no-browser

Jupyterが起動するので一度Ctrl + Cで終了し、exitで抜けておく。

以上でDockerの環境ができた。

Docker環境完成後

2回目以降は以下の手順でDocker Imageを立ち上げることができる。

# dockerのプロセスを探す
$ docker ps -a
CONTAINER ID        IMAGE                   COMMAND                  CREATED             STATUS                    PORTS               NAMES
a5f0b5dbc6db        continuumio/anaconda3   "/usr/bin/tini -- /bi"   12 hours ago        Exited (0) 10 hours ago                       serene_curran

# ↑で探したコンテナID(今回の場合:a5f0b5dbc6db)を指定してdockerを起動する
$ docker start -i a5f0b5dbc6db

dockerのプロンプトに移動するのでJupyterを起動する

$ /opt/conda/bin/jupyter notebook --notebook-dir=/opt/notebooks --ip='*' --port=8888 --no-browser

Jupyter起動中は、ブラウザでhttp://localhost:8888/にアクセスすることでJupyterを使う事ができる。

f:id:yuki10k:20170111015925p:plain

また、Mac側の指定したディレクトリとシンクされているので、deep-learning-from-scratchというディレクトリが既に存在する。
deep-learning-from-scratch以下には各チャプターごとの参考コードが載っているし、notebookを作成し、notebookから該当のコードを呼び出すことができる。
呼び出し方は書籍内に出て来るのでそちらを参照。

f:id:yuki10k:20170111020041p:plain

Mac側とディレクトリを共有しているので、当然finderで中身を見ることもできるし、ブラウザ上のJupyter notebookで編集したnotebookを、自分のGitHubリポジトリにpushすることもできる。

これで色々捗りそうなので、Deep Learningについても学んでいきたい。

【iOS】Table Viewを使ってみる

前回の続き。

yuki10.hatenablog.com

今回はTable Viewを表示する所をやってみる。
予め2ページ目の中央に配置していたLabelは削除しておく。

story boardでObject LibraryからTable Viewを探してページに置く。

f:id:yuki10k:20170110005546j:plain

続けてTable View Cellを探し、先程配置したTable Viewの上に置く。

f:id:yuki10k:20170110005600j:plain

ここで2ページ目を表示するためのView Controllerを作成する。
プロジェクトツリーで右クリック -> New File..を選択。
以下の画面でCocoa Touch Classを選択する。

f:id:yuki10k:20170110005610p:plain

クラス名にSecondPageViewControllerと入力し、
Subclass of にはUIViewControllerを選択する。
※Table Viewだけのページであれば、UITableViewControllerでも良さそう。

f:id:yuki10k:20170110005618p:plain

Storty Boardに戻り、2ページ目のView ControllerのAttribute Inspectorの設定で、classに先程作成したSecondPageViewControllerを指定する。

f:id:yuki10k:20170110005631j:plain

Assistant Editor(∞みたいなアイコン)を開くと、左側にStory Board 右側にSecondPageViewControllerが表示される。
※表示されなければ表示されるように調整する。

この状態で、Table ViewからCtrlキーを押しながらSecondPageViewControllerのクラス定義の直下にドラッグする。
↓画像のようなダイアログが出るので、tableViewという名前で設定する。

f:id:yuki10k:20170110005642j:plain

そうすると、@IBOutletでStory BoardのUITableViewとSecondPageViewControllerのUITableViewがtableViewという変数名で接続され、以下のようにコードが挿入される。

f:id:yuki10k:20170110005654p:plain

その後、クラス定義部分にUITableViewDelegateUITableViewDataSourceを追加して以下のように書き換える。

class SecondPageViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

Story Boardに戻って、tableViewConnection Inspectorを開く。

f:id:yuki10k:20170110005701j:plain

dataSourcedelegateからSecondPageViewControllerに向かってドラッグする。

f:id:yuki10k:20170110005733j:plain

そうすると、dataSourcedelegateSecondPageViewControllerに接続される。

f:id:yuki10k:20170110005742p:plain

ここでdataSourcedelegateを実装していないのでコンパイルエラーが出るけど、先にCellの定義を追加する。
新たにCocoa Touch Class作成画面を開く。
クラス名にSecondPageTableViewCellを指定し、
Subclass of: にUITableViewCellを指定する。

f:id:yuki10k:20170110005800p:plain

Story BoardでTableViewCellのIdentity Inspectorを開き、classにSecondPageTableViewCellを指定する。
ついでにCellで画面表示したい項目をLabelで配置しておく。
画像の例だと、Labelでidnameを配置している。

f:id:yuki10k:20170110005803j:plain

同様にAttributes Inspectorを開き、IdentifierにSecondPageCellを指定する。
※この項目は後ほどコード上でCellを取得するために使われる。

f:id:yuki10k:20170110005817j:plain

Assistant Editorで左にStory Board 右にSecondPageTableViewCellを開いて、
Story BoardのCellからid項目とname項目をCtrlキーを押しながらドラッグして、SecondPageTableViewCellに接続する。

f:id:yuki10k:20170110005808j:plain

更に、データセット用のメソッドを定義する。

    func setUserData(id: String!, name: String!) {
        self.id.text = id
        self.name.text = name
    }

最終的には以下のようなクラスになっている。

import UIKit

class SecondPageTableViewCell: UITableViewCell {

    @IBOutlet weak var id: UILabel!
    @IBOutlet weak var name: UILabel!
    
    func setUserData(id: String!, name: String!) {
        self.id.text = id
        self.name.text = name
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

}

SecondPageViewControllerに戻って、TableViewの実装を行う。

まずはtableViewが何行あるかを表示するための以下のメソッドを実装する。
今回は練習なので単純に5をreturnする。

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5
    }

さらに、対象行のCellを取得するためのメソッドを定義する。
withIdentifierに設定する項目は、Attributes InspectorでIdentifierに設定した値に合わせる必要がある。
今回の場合は、SecondPageCellになる。
また、型はSecondPageTableViewCellを指定する。
最後にsetUserDataでCellにセットする項目を入れる。
※今回の場合、現在の行のindexというidと"hoge"という名前がセットされる。

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "SecondPageCell", for: indexPath) as! SecondPageTableViewCell
        cell.setUserData(id: String(indexPath.row), name: "hoge")
        return cell
    }

この状態でビルドして実行すると、以下の画面のようにTable Viewを表示することができた。

f:id:yuki10k:20170110005822p:plain

ここまで進めてきて徐々に難しくなってきた反面、楽しくなってきたので引き続きiOSアプリ開発方法を学んでいきたい。
Swiftのコード実装も増えてくるといい感じ。

【iOS】NavigationBarで前の画面に戻れるようにする

前回の続き。

yuki10.hatenablog.com

今回は画面遷移した後、元のページに戻るという部分をやってみた。
今回もソースコードの変更はなし。

まずは右下のObject LibraryからNavigation Barを探して、遷移後の画面に配置する。

f:id:yuki10k:20170109172552j:plain

続いて同様にBar Button Itemを探して、先程追加したNavigation Barの左側に配置する。

f:id:yuki10k:20170109172602j:plain

↑で配置したTitle、Itemと表示されている部分をダブルクリックすると表示名を変更できるようになるので、適当な名前に変更しておく。
今回の場合だと、

  • Title -> 2nd Page
  • Item -> Back

にそれぞれ変更した。

f:id:yuki10k:20170109172609j:plain

続いてBar Button Itemを選択した状態で、Ctrlキーを押しながら1画面目のView Controllerにドロップする。

f:id:yuki10k:20170109172617j:plain

Segueのアクションをどうするか聞かれるので、Showを選択。

f:id:yuki10k:20170109172628p:plain

そうすると、↓図のように2ページ目から1ページ目へのSegueが表示されるようになる。

f:id:yuki10k:20170109172641p:plain

この状態でシミュレータを起動すると、

  • 1ページ目のNextボタン -> 2ページ目
  • 2ページ目のBackボタン -> 1ページ目

に遷移できるようになった。

f:id:yuki10k:20170109172651p:plain

今回もコードはいじっていないので次はコードを触っていきたい。