(Mac)Xamarin StudioにPrism Template Packを導入する
導入手順
導入自体は簡単です。
アドインマネージャーを起動
アドインマネージャーから選択してインストール
検索バーに「prism」と入力すると候補に出てくる
利用手順
新規プロジェクト作成時に「Prism Unity App」を選択
新規プロジェクトが出来たらそのままスケルトンをビルドしたいところですが…。
そのままだとエラーになってしまいます。
usingの追加
.iOSのAppDelegate.csと.DroidのMainActivity.csに下記のusingを追加する。
ここまでやるとスケルトンがビルドできます。
あとがき
本来なら、Prismとは何ぞやとかMVVMについてとか、自分自身のために整理しておきたいところですが…。
その辺りはまた暇を見て書けたらなぁと思います。
Prismについては、nuits.jpさんがきちんとまとめて下さっていて、非常に参考になります! www.nuits.jp
Prismを使用するメリットはたくさんあります。
本当は、もう少しすっぴんのXamarin.Formsで苦労をしてからその恩恵を受けようと思っていましたが…。
便利なので必要に応じてガンガン使っちゃいます!w
(Xamarin.Forms)TabbedPage
(以前の記事)
Xamarin.Formsの画面遷移 - Makopy'5 La6
特に理解しておくべき画面遷移として、下記を挙げています。
- Hierarchical Navigation
- Modal Pages
- TabbedPage
これまで、
- Hierarchical Navigation
- Modal Pages
にスポットを当ててきました。
今回はTabbedPageにスポットを当ててみたいと思います。
TabbedPageのポイント
Xamarinでのアプリ開発は日が浅いのであまり実戦事例が足りていませんが…。
スマホアプリ開発として見ると、実戦の開発では、Tabによる画面(機能)の切り替えは遭遇率が高いです。
画面(機能)が多岐に渡る場合はMaster-Detail Pageもありえますが。
例のごとく、ベーシックな知識は公式のDevelopersガイドから得るのが一番です。
developer.xamarin.com
画面管理
- TabbedPageの子要素(
Children
)にPageを設定する ItemsSource
,ItemTemplate
を使用した画面設定も可能
ItemsSource
, ItemTemplate
を使う方法は同一構成の複数画面が前提なので、個人的にはCarousel Pageなイメージです。
また、
「機能的に異なるいくつかの画面 + 設定画面」
みたいなのが、Tabを使ったアプリには多い気がします。
そのような場合は画面単位に実装を担当する可能性が高く、基本的にはChildren
にPageを設定するような実装をまずは押さえておくべきだと思います。
Tab構成に関する注意点
- 子要素は
NavigationPage
,ContentPage
のインスタンスで構成されるべき - 含まれる子要素が多すぎるとパフォーマンスが影響を受ける可能性がある
TabbedPage
をNavigationPage
に配置するのは非推奨- iOSでは
UITabBarController
がUINavigationController
のラッパーとして動作するため
- iOSでは
UI関連
Tab表示位置に関する悩み…
上述の
「いくつかの画面 + 設定画面」
のパターンを考えてみましょう。
ListViewを設定画面トップとして、各セルタップで子画面をPushAsync
と、やりたくなるのですが…。
Android, WindowsではTabが画面上部に表示されます。
Tabの子要素にNavigationPageを設定すると、NavigationBarがTabのさらに上に表示されてしまいます…。
NavigationBarを非表示にする方法
(参考)
XAMLなら、
C#なら、
NavigationBarを非表示にすると…
NavigationBarを非表示にするとなると、また別の悩みが出てきます。
Backキーを持たないiOS用にわざわざ戻るUIを実装しなければならない。
実装するにしても、Tabの表示位置の違いを意識して配置することになり、非常に悩みます。
個人的に現時点での落としどころとしては…。
その他もろもろ
表示画面変更の捕捉
OnCurrentPageChanged
をオーバーライドする
表示画面を切り替える
Current
を変更する
サンプル
TabbedPageについて、サンプル実装してみました。
いつもながら実験用の適当なコードですが…。
(Xamarin.Forms)Backキー押下に対応する
Backキー
Androidにあるハードキーの戻るボタンです。
扱ったことがないのでよく知らないのですが、Windows Phoneにもあるみたいです。
Backキー押下への対応方法
Page.OnBackButtonPressed
をオーバーライドすることで対応できます。
iOSではBackキー押下のイベント自体が上がってきません。
注意事項
オーバーライドして対応を入れた場合、メソッドの戻り値としてtrueを返すべき。
うっかりfalseを返してしまうと…。
アプリがBackキー押下に反応しなかったとシステムに判断されてスタート画面やホーム画面に戻されてしまいます。
Backキーの無効化
Backキーを無効化する為には、下記のようにすればよい。
何もせずにtrueを返すことで、事実上バックキーを無効化することができます。
ここでもうっかりfalseを返すと…。
上記の「注意事項」に記載の通り、意図せずスタート画面やホーム画面に戻されてしまいます。
(Xamarin.Forms)Modal Pages
(以前の記事)
Xamarin.Formsの画面遷移 - Makopy'5 La6
特に理解しておくべき画面遷移として、下記を挙げています。
- Hierarchical Navigation
- Modal Pages
- TabbedPage
前回はHierarchical Navigationにスポットを当てました。
今回はModal Pagesにスポットを当ててみたいと思います。
Modal Pagesのポイント
やはり、ベーシックな知識は公式のDevelopersガイドから得るのが一番です。
Modal Pages - Xamarin
画面遷移の管理
- ModalStackで管理
- ModalStackはNavigationPageを必要としない
- 任意のPage派生クラスでOK
- 画面を表示するときはModalStackにPush
Navigation.PushModalAsync
- 画面を閉じるときはModalStackからPop
Navigation.PopModalAsync
ModalStackの操作
- ModalStackは操作できない
InsertPageBefore
やRemovePage
のような操作が存在しない
PopToRootAsync
はサポートされていない
ModalStackの取得
Page内でなら、下記の様に取得できます。
PageのList(ReadOnly)が取得できます。
UI関連
- 基本的にNavigationBarを伴わない
- NavigationPageを
PushModalAsync
した場合は表示される- ただし、戻るボタンはサポートされない
- 画面を閉じるためのUIは自前で実装しなければならない
- NavigationPageを
その他
Backキー無効については、別記事にて整理予定です。
Modal Pages/Hierarchical Navigationと画面ライフサイクル
下記のサンプルコードで確認してみました(iOS/Android)
相変わらず、動作確認用のどうしようもないコードです…。
Modal PagesとHierarchical Navigationを複合しています。
- TopPage→Page1→Page2→Page3→NavigationRootPageと
PushModalAsync
- NavigationRootPage→PushPage1→PushPage2→PushPage3と
PushAsync
- PushPage3→PushPage2→PushPage1→NavigationRootPageと
PopAsync
- NavigationRootPage→Page3→Page2→Page1→TopPageと
PopModalAsync
- 各画面の
OnAppearing
とOnDisappearing
でログ出力
確認まとめ
PushModalAsync
,PopModalAsync
iOSでの呼ばれる順番に注意!
呼ばれる順番性を当てにした実装はしてはいけませんね。
おまけの確認(ModalStackとNavigationStack)
各画面のOnAppearing
とOnDisappearing
で、NavigationStackとModalStackの中身をログ出力しました。
- ContentPageを
PushModalAsync
- ModalStackにのみ積まれて、 NavigationStackはカラ
- NavigationPageを
PushModalAsync
- ModalStackとNavigationStack両方にNavigationPageが積まれる
- このときのログ出力
- NavigationStack
- 具体的な名前が表示される
- ex) “ModalPagesSample.NavigationRootPage”
- 具体的な名前が表示される
- ModalStack
- NavigationPageとして表示される
- “Xamarin.Forms.NavigationPage”
- NavigationPageとして表示される
- NavigationStack
ModalStackとNavigationStack
公式のDevelopersガイドで図解してくれていますが、図だけ見るとほとんど概念上の差がありません…。
Hierarchical Navigation - Xamarin
Modal Pages - Xamarin
ということで…。
サンプル実装と確認結果を踏まえて、個人的な整理として図式化してみました。
(ちなみに、筆者の作図能力は著しく低いです…)
(Xamarin.Forms)Hierarchical Navigation
前回のお話。
makopy5la6.hatenadiary.jp
画面遷移のことを書きましたが。
特に理解しておくべき画面遷移として、下記を挙げました。
- Hierarchical Navigation
- Modal Pages
- TabbedPage
今回はHierarchical Navigationにスポットを当ててみたいと思います。
Hierarchical Navigationのポイント
兎にも角にも、まずはベーシックな知識は公式のDevelopersガイドから得るのが一番です。
Hierarchical Navigation - Xamarin
画面遷移の管理
- NavigationStackで管理
- NavigationStackのRootは
NavigationPage
とする - 次の画面に行くときはNavigationStackにPush
Navigation.PushAsync
- 前の画面に戻るときはNavigationStackからPop
Navigation.PushAsync
- Root画面までPopできる
Navigation.PopToRootAsync
NavigationStackの操作
- NavigationStack内に画面を挿入
Navigation.InsertPageBefore
- NavigationStack内から画面を削除
Navigation.RemovePage
あくまで私感でしかありませんが…。
この手の操作はあまり好きになれません。
PopToRootAsync
も含めて、Pop/Push以外の処理を書かねばならない場合、
「そもそも画面遷移仕様がきちんと整理されているのか?」
「仕様はともかく、実装面で他の方法はないのか?」
という風に疑うべきだと思います。
またこの様な場合、
「特定の条件やシーンに応じて画面遷移に変更を加えたい」
というようなパターンが多いと思いますが、仕様面の都合はNavigationStackでは管理してもらえません。
なるべくベーシックで自然な画面遷移に収めた方が安心です。
とは言え…。
「事実は小説より奇なり」
本番の開発では諸般の都合でトリッキーな画面遷移を書かざるを得ない時があるワケです。
こういう特殊な操作があることは覚えておいて、やむを得ない時は十分注意して使うというくらいのスタンスを取りたいと思います。
NavigationStackの取得
Page内でなら、下記の様に取得できます。
PageのList(ReadOnly)が取得できます。
NavigationStack - Xamarin
ReadOnlyなので、直接NavigationStackの操作ができるわけではありませんが。
ログ出ししたり、操作メソッドの引数に設定するPageを取得したりという用途で使えると思います。
UI関連
- 基本的にNavigationBarを伴う
- NavigationBarのTitleにはPageのTitleが採用される
- 基本的にはこのTitleが次の画面の戻るボタンのTitleになる
- Pageのタイトル
画面のライフサイクル
画面遷移に伴わせて、どんなライフサイクルメソッドが呼ばれるかを検証したかったのですが…。
Xamarin.Formsの画面ライフサイクルって…
OnAppearing
OnDisappearing
の2つしかなくて、 基本的には画面の表示/非表示しか捕捉できません。
Hierarchical Navigationと画面ライフサイクル
下記のサンプルコードで確認してみました(iOS/Android)
動作確認用のどうしようもないコードですが…。
- TopPage→Page1→Page2→Page3とPushAsync
- Page3→Page2→Page1→TopPageとPopAsync
- Page3からは
PopToRootAsync
,InsertPageBefore
,RemovePage
も試しています。 - 各画面の
OnAppearing
とOnDisappearing
でログ出力
確認まとめ
PushAsync
,PopAsync
- 遷移元画面の
OnDisappearing
→ 遷移先画面のOnAppearing
- 遷移元画面の
PopToRootAsync
- 遷移元画面の
OnDisappearing
→ 遷移先画面のOnAppearing
- 遷移元画面の
InsertPageBefore
- ライフサイクルメソッドは呼ばれない
RemovePage
- ライフサイクルメソッドは呼ばれない
当然ながら、画面の表示/非表示が発生しなければ、OnAppearing
やOnDisappearing
は呼ばれない。
おまけの確認
各画面のOnAppearing
とOnDisappearing
のタイミングで、併せてNavigationStackの中身をログ出力していたのですが…。
PopToRootAsync
した場合、NavigationStackが一度空になってからTopPageが積まれる
Page3.xaml.cs - OnDisappearing ***** NavigationStack ***** TopPage.xaml.cs - OnAppearing ***** NavigationStack ***** HierarchicalNavigationSample.TopPage
Xamarin.Formsの画面遷移
前回、アプリのライフサイクルについて整理しました。
今回は画面のライフサイクルについて整理しようと思いましたが…。
その前に画面遷移についてさらっと整理をしておきたくなりました。
Xamarin.Formsの画面遷移のパターン
公式のDevelopersガイドによれば、 Xamarin.Formsで用意された画面遷移のパターンは下記の通りです。
- Hierarchical Navigation
- TabbedPage
- CarouselPage
- MasterDetailPage
- Modal Pages
- Displaying Pop-Ups
(参考)
https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/navigation/
iOSエンジニア的には捉えやすい分類になっていますね☆
画面遷移は超重要!
実戦の開発では、画面遷移は非常に重要です。
開発を進めるにあたっての要件定義や設計の進め方は色々あると思います。
その中で個人的にもっとも重視しているのは、
「早いうちに画面を洗い出し、画面遷移を決めにかかる」
ということです。
それがそのアプリの骨組みになります。
場合によっては、
その骨組みを成り立たせるために、
- 特殊な画面遷移制御が必要なのか
- 必要だとすればどう実現すればいいのか
という検討が必要です。
画面遷移そのものの検討にも、
特殊な場合の検討にも、
ベーシックな画面遷移の理解が必須だと思っています。
特に理解しておくべきと思う画面遷移
下記の3つは特に理解しておくべきと思います。
- Hierarchical Navigation
- Modal Pages
- TabbedPage
どれも頻出の画面遷移です。
いろんなアプリがあるので一概に言えませんが…。
この3つの適切な組み合わせで対応できるケースは少なくないと思います。
加えて。
これらの画面遷移パターンに絡めて、画面のライフサイクルを押さえておくことも重要だと思います。
ということで。
画面のライフサイクルについては、画面遷移パターンに絡めて整理して記事にしていこうと思います!
「基礎の基礎が怖いってことを、今日何度も言っておきます」
というようなセリフを何かのCMで聞いたことがあります。
スマホアプリ開発において、
ライフサイクルや画面遷移は基礎の基礎ではないかなぁと思います。
本番の開発でも、ここの理解・認識・検討を誤ると大変なことになります。
だから、分かってるつもりだけど、敢えて整理をして覚書をしておこうと思ったりもするわけです。
(Xamarin.Forms)アプリのライフサイクル
ライフサイクルの捕捉
ライフサイクルの捕捉は、スマホアプリ開発において非常に重要です。
機能要件や実装都合等の事情により、
ライフサイクルのタイミングに合わせて処理をする。
ということがしばしばあるからです。
ライフサイクルは大きく2つに分けられます。
- アプリのライフサイクル
- 画面のライフサイクル
今回は「アプリのライフサイクル」について書きます!
Xamarin.Formsにおけるアプリのライフサイクル
公式のDevelopersガイドに記載があります。
https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/app-lifecycle/
Application
クラスには、以下の3つのライフサイクルメソッドが存在します。
- OnStart
- アプリケーションの起動時に呼び出される
- OnSleep
- アプリケーションがバックグラウンドに移動するたびに呼び出される
- OnResume
- アプリケーションがバックグラウンド遷移後、再開されたときに呼び出される
泣いても笑ってもこの3つしかありません。
以下ではこの3つのライフサイクルメソッドが呼び出されるタイミングを調査します。
呼び出されるタイミングについて調査
- Appクラス
- .DroidのMainActivity.cs
- .iOSのAppDelegate.cs
のそれぞれのライフサイクルメソッドにログを張って、
- 起動
- バックグラウンド
- フォアグラウンド
に関連しそうな操作をしてみました。
調査そのものに関係ないのですが…。
.iOSのAppDelegateのライフサイクルメソッド名がネイティブとだいぶイメージが違うなぁと思いましたw
iOSネイティブが大好物な人なので、違和感ありありですw
調査結果から見たポイント
調査ログを延々と記載する前に…。
先に結論というかポイントをまとめておきますw
OnResumeの注意点
OnResume
はAndroidにおけるOnResume
と違って、起動時は呼ばれない
OnSleep/OnResumeの注意点
iOSでは、本来バックグラウンドと見なしたくない下記のタイミングで、OnSleep
/OnResume
が呼ばれてしまう。
- 通知センターの表示/非表示
- コントロールセンターの表示/非表示
実際にはバックグラウンド/フォアグラウンドではなく、アクティブ/インアクティブに紐づいてしまっている。
従って、
OnSleepが呼ばれるタイミングは必ずしもバックグラウンドではない
ということで、以下調査ログの記録です。
iOS
起動時
- Appの
OnDestroy
がAppDelegateのWillFinishLaunching
の直後に呼ばれる
AppDelegate.cs - WillFinishLaunching AppDelegate.cs - FinishedLaunching App.xaml.cs - .ctor App.xaml.cs - OnStart AppDelegate.cs - OnActivated
ホームボタン押下時
- Appの
OnDestroy
がAppDelegateのOnResignActivation
の直後に呼ばれる
AppDelegate.cs - OnResignActivation App.xaml.cs - OnSleep AppDelegate.cs - DidEnterBackground
ホームボタン押下後、マルチタスク画面から選択
- Appの
OnResume
がAppDelegateのOnActivated
の直後に呼ばれる
AppDelegate.cs - WillEnterForeground AppDelegate.cs - OnActivated App.xaml.cs - OnResume
ホームボタン押下後、アプリアイコンから起動
- Appの
OnResume
がAppDelegateのOnActivated
の直後に呼ばれる
AppDelegate.cs - WillEnterForeground AppDelegate.cs - OnActivated App.xaml.cs - OnResume
マルチタスク画面表示時
- Appの
OnSleep
がAppDelegateのOnResignActivation
の直後に呼ばれる
AppDelegate.cs - OnResignActivation App.xaml.cs - OnSleep
マルチタスク画面から再選択
- Appの
OnSleep
がAppDelegateのOnResignActivation
の直後に呼ばれる
AppDelegate.cs - OnActivated App.xaml.cs - OnResume
通知センター表示
- Appの
OnSleep
がAppDelegateのOnResignActivation
の直後に呼ばれる
AppDelegate.cs - OnResignActivation App.xaml.cs - OnSleep
通知センター非表示
- Appの
OnResume
がAppDelegateのOnActivated
の直後に呼ばれる
AppDelegate.cs - OnActivated App.xaml.cs - OnResume
コントロールセンター表示
- Appの
OnSleep
がAppDelegateのOnResignActivation
の直後に呼ばれる
AppDelegate.cs - OnResignActivation App.xaml.cs - OnSleep
コントロールセンター非表示
- Appの
OnResume
がAppDelegateのOnActivated
の直後に呼ばれる
AppDelegate.cs - OnActivated App.xaml.cs - OnResume
マルチタスク画面からアプリKill
- AppDelegateの
DidEnterBackground
とWillTerminate
が呼ばれる - Appのライフサイクルメソッドは呼ばれない
AppDelegate.cs - DidEnterBackground AppDelegate.cs - WillTerminate
Android
アプリ起動時
- Appの
OnStart
がActivityのOnStart
の直後に呼ばれる - Activityの
OnResume
は呼ばれるが、AppのOnResume
は呼ばれない
MainActivity.cs - OnCreate App.xaml.cs - .ctor MainActivity.cs - OnStart App.xaml.cs - OnStart MainActivity.cs - OnPostCreate MainActivity.cs - OnResume MainActivity.cs - OnPostResume
ホームボタン押下時
- Appの
OnSleep
がActivityのOnStop
の直後に呼ばれる
MainActivity.cs - OnUserLeaveHint MainActivity.cs - OnPause MainActivity.cs - OnSaveInstanceState MainActivity.cs - OnStop App.xaml.cs - OnSleep
ホームボタン押下後、アプリ一覧から選択
- Appの
OnResume
がActivityのOnRestart
の直後に呼ばれる
MainActivity.cs - OnRestart App.xaml.cs - OnResume MainActivity.cs - OnStart MainActivity.cs - OnResume MainActivity.cs - OnPostResume
ホームボタン押下後、アプリアイコンから起動
- Appの
OnStart
がActivityのOnStart
の直後に呼ばれる - Activityの
OnResume
は呼ばれるが、AppのOnResume
は呼ばれない
MainActivity.cs - OnCreate App.xaml.cs - .ctor MainActivity.cs - OnStart App.xaml.cs - OnStart MainActivity.cs - OnPostCreate MainActivity.cs - OnResume MainActivity.cs - OnPostResume
マルチタスクボタン押下時
- Appの
OnSleep
がActivityのOnStop
の直後に呼ばれる
MainActivity.cs - OnUserLeaveHint MainActivity.cs - OnPause MainActivity.cs - OnSaveInstanceState MainActivity.cs - OnStop App.xaml.cs - OnSleep
マルチタスクボタン押下後、アプリ一覧から再選択
- Appの
OnResume
がActivityのOnRestart
の直後に呼ばれる
MainActivity.cs - OnRestart App.xaml.cs - OnResume MainActivity.cs - OnStart MainActivity.cs - OnResume MainActivity.cs - OnPostResume
通知バー表示
- 何も呼ばれない
アプリ一覧からアプリKill
- Activityの
OnDestroy
が呼ばれる - Appのライフサイクルメソッドは呼ばれない
MainActivity.cs - OnDestroy