Makopy'5 La6

スマホアプリ開発とかその周辺のこととか関係ないこと。または恋は言ってみりゃボディー・ブロー

(Xamarin.Forms)WebViewについて -その2- Webブラウザで開く

以前、WebViewに関してごちゃごちゃと書きました。

以前の記事はコチラ↓
(Xamarin.Forms)WebViewについて - Makopy'5 La6

今後、WebView周りの実装について何度かに分けて覚書しておく予定です。
今回は直接WebViewのトピックではありませんが、Webブラウザでページを開く制御について書きます。

Webブラウザで開く

Device.OpenUriを用います。

サンプルプロジェクトはこちら

github.com

WebやWebView周りの諸々については、このプロジェクトに組み込んでいく予定です。

スマホアプリにおけるWebページ表示

ケースバイケースですが。
スマホアプリにおけるWebページ表示については、概ね下記のような要望のパターンがあります。

  • WebViewを用いて内部で開く
  • Webブラウザで開く
  • 内部コンテンツはWebViewで、WebページはWebブラウザで開く
  • 内部コンテンツと一部WebページをWebViewで、それ以外はブラウザで開く

オンライン/オフラインに関わらずユーザに一読してもらいたい文書や、原則的に変更がないコンテンツなどを内部コンテンツとして持っておくことがあります。
そのようなコンテンツをHTML/CSSで作成してWebViewで表示するということがよくあります。

逆に、閲覧を強制する必要のないコンテンツや既存のWebページをそのまま利用したい場合は、Web上のページにアクセスすることが多いです。
この場合、WebViewを用いるかWebブラウザで開くかはケースバイケースです。 既存のWebページをWebViewで開く場合、アプリとは関係のない先へのリンクなどが存在することもあり、

  • WebView上の戻り遷移をサポートする
  • WebView上のリンクを制限する

といったような、何かしらの制御が必要になります。

アプリ上の画面遷移の整合性なども考えると、一難去ってまた一難的に、あれこれと考えなければならないことが多いです。
特殊な事情やアプリ内部に閉じ込める必要がない場合には、Webブラウザで開く方向に調整をした方が楽だと思います。

一番危険なのは、
「WebViewにURL設定するだけ」
という前提から入ってしまうことです。

ブラウザアプリやハイブリッドアプリを除けば、WebViewがメインに来ることはあまりないと思います。
それゆえにWebView周辺のことに関しては見積もりが甘くなることもしばしばです。
WebViewやその制御自体の問題ではありませんが、こういったこともWebViewの怖いところです…。

(Xamarin.Forms)端末の画面サイズの取得

基本的に意識すべきではない

じゃあ、こんな記事書くなよ…。
と、思われてしまうかもしれませんが。

端末の画面サイズを取得して、それを元に計算するようなシーンはない方がいいです。
画面サイズやOSによらず、共通のレイアウトを適用できるように作るべきだし、それがXamarin.Formsのいいところだからです。

しかしながら、

  • 回転
  • キーボード表示

などの要因により、
Pageのサイズが動的に変わってしまうような状況もありえます。
そのような場合においては、
固定値として基準になるサイズ
が欲しくなることがあります。

そんな時の為に、その方法を記しておこうと思います。

実装

  • インタフェース部

  • .Droid

  • .iOS

  • 使い方

サンプルプロジェクトはこちら↓
github.com

ポイント

Androidでは、下記の点に注意です。

  • ステータスバーの高さを別途取得して引いておく必要がある
  • 取得できるサイズの単位はPixel
    • Xamarin.Formsで扱うサイズの単位はDIPなので、PixelDIPへの変換が必要
      • Density-independent Pixels = 密度に非依存のピクセル

(Xamarin.Forms)SVProgressHUDを使う

SVProgressHUDとは?

“SVProgressHUD is a clean and easy-to-use HUD meant to display the progress of an ongoing task on iOS and tvOS.”

github.com

dev.classmethod.jp

iOSのネイティブ開発ではよくお世話になっています!

SVProgressHUDのいいところ

☆とにかく便利☆

  • UIがシンプルで標準UIとの親和性が高く、組み込みやすい
  • コード上での使い方もシンプル
  • UIActivityIndicatorViewを使った面倒な実装とサヨウナラ
    • これはこれで必要な場合もあるのでケースバイケース

Xamarin.FormsでSVProgressHUDを使いたい!

非常に便利なので、
Xamarin.Formsでも使いたいなぁ…

と調べてみると、
iOS/Androidについては、Xamarin用にポーティングされたものが存在します。

これらのパッケージを追加して、DependencyServiceから使えばよさそうです。

実装例

ここでは自分が好んでよく使う形式に丸め込んで単純な形で共通I/F化しています。

  • インタフェース部

  • .Droid

  • .iOS

  • 使い方

サンプルプロジェクトはこちら↓

github.com

(Xamarin.Forms)WebViewについて

WebViewの用途

例えば、下記のような用途があります。

  • 内部HTMLコンテンツの表示
  • 外部ページの表示
  • Web APIレスポンスパース

WebViewは楽じゃない…

実戦のスマホアプリ開発において、WebViewの存在はまぁまぁ欠かせないものだと思います。
WebViewは単純に配置して使ってみるだけであれば、割と簡単なコンポーネントです。

しかしながら、実戦の開発においては、
単純にWebページやコンテンツを表示して終わり
というパターンは少なく、

  • 表示ページからのリンク先を制限する
  • 内部HTMLから外のページへリンクする場合は外部ブラウザで表示する
  • HTML上のボタンタップでネイティブの画面に遷移する

と言ったような要求や仕様と隣り合わせになることが多いです。
その実現のためには、

  • 各種イベントの捕捉(フック)
  • JavaScript連携

といった制御が必要になります。

WebViewで上記のような対応をする場合、
本来サーバサイドやWebページ側が対応すべきだったり、その方が楽な場合も多いのですが。
「アプリのWebViewの制御で可能な限り何とかする」
という、Web周りの面倒を吸収するような役割を期待されていることもあります。

Xamarin.FormsにおけるWebView

例によって、公式のDevelopers Guidesです。
WebView - Xamarin

Webページやコンテンツの表示だけであれば問題なさそうです。
しかしながら、イベントを捕捉したりJavaScriptと連携するような込み入った制御はできそうにありません。
込み入ったことをしたい場合は、Custom RendererでOSごとの実装が必要になります。
OSごとの実装をする一方、UI層との連携には統一のI/Fを準備する必要があります。

この辺りは別記事で、後日改めて記事にしたいと思います。

実は…。
現在進行形で実戦の開発でぼちぼち大変な思いをしています。
大変なのは業務上の経緯や都合も多分に含んではいますがw
業務的なお話は除いた技術的なトピックの部分で、自身が苦労したことを覚書としてしたためる予定です。

(Xamarin.Forms)Warningを無効化する

Warningの無効化

Warningの取り扱いについては、個人やプロジェクトの単位でいろんなポリシーがあると思います。
WarningをErrorと見なすような厳しいポリシーもあれば、100以上ものWarningが放置されているプロジェクトを見かけることもあります。
それらの是非についてはここでは触れません。
どれにもそれなりの理由や背景があると思います。
是非はともかく、Warning無効化の方法について触れたいと思います。

Xamarin.FormsでのWarning無効化

以下、CS0618というWarningを例とします。
いずれの方法も、コード CS0618から、「CS」を除いた番号の部分を使用します。
0618でもよいし、618でもOKです。

CS0618については、こちら。
Compiler Warning (level 2) CS0618

プロジェクト全体で無効化

  • コンパイラ設定で警告無視対象に含める
    • (Visual StudioとXamarin Studioでは具体的な画面や設定項目名が異なります)

ファイル単位で無効化

  • ファイル冒頭でdisable

局所的に無効化

  • Warningを回避したい箇所をdisableとrestoreで囲む

Xamarin Studioでの困りごと

筆者の個人的な開発環境はMac OS上のXamarin Studioです。
この記事を書いている時点では、
Xamarin Studio Community 6.2.1(build 3)
を使用しています。

上記環境では、以下のような困りごとがあります。

  • エラー出力のWarning表示はリアルタイムではない
    • エディタ上はリアルタイムにWarning箇所に波線でアンダーライン表示される
    • エラー出力でのWarning表示はビルド完了時に反映される
      • ここまでこないとコードが分からない…。
  • コンパイラの[警告を無視]のリストに番号を設定した場合、エディタ上の表示はWarningとして表示されたまま

Xamarin.FormsでのWarning

Xamarin.Formsでの開発は、スマホアプリ開発です。
スマホアプリの開発となると避けられない問題として、
「どこまで古いOSバージョンに対応するか?」
というのがあります。

古いOSをサポート対象に含めている場合、どうしてもOSバージョン分岐して古い形式での実装を残さざるを得ないことがあります。
今回例に出したCS0618を引き起こすケースのひとつです。
Warning無効化に安易に頼らずに解決するの努力は必要ですが…。
どうしてもの場合はあれこれ考えるよりは、「局所的に」潰してしまうのがいいと思っています。
もちろん、動作的に問題がないことの確認が取れていることは大前提になります。

個人的なWarningに対するポリシー

個人的なWarningに対するポリシーは、
「不必要なWarningは出すべきでないし、Warningは放置しない」
です。

何か込み入った問題が起きた時に、Warningがあれば疑ってみたくなります。
その時に100も200もWarningが放置されていると困りますね…。
また、そのような状態からでは、新たなWarningが埋もれやすくなるなど、見通しの悪さを生み出します。
簡単に潰せるWarningはもちろん放置せず、どうしても消えないWarningは無効化で対応できれば対応するのが、個人的な流儀です。

(Xamarin.Forms)アプリを終了させる

スマホアプリにおけるアプリ終了

基本的にあまりしたくないことではありますが…。
実戦のアプリ開発では、
「アプリを終了させたい 」
というシーンや要求に出くわすことがあります。

iOSの場合

可能かどうかと問われれば、可能です。
exit()をコールすればアプリは終了します。
しかしながらiOSにおいて、
プログラミングによりアプリ終了をしてよいかどうか?
については、諸説あります。

iOS Human Interface Guidelines」に下記の記載があります。

iOSアプリケーションをプログラム的に強制終了させることは避ける。  
単にクラッシュしたものと受け取られる可能性があります。  
何らかの原因でアプリケーションが意図通り機能しない場合は、その状況を伝え、何ができるかを説明しなければなりません。

iOSヒューマンインターフェイスガイドライン: 起動と停止

iOSの公式のドキュメントで上記のように言及されていることもあり、
iOSではアプリを終了してはいけない」
という論調が多く見られます。
しかしながら、
「いきなり終わって、不正終了みたくなるのがよくない」
と言っているわけであって、
「理由を表示した上でアプリを終了するならよい」
という解釈もあり、筆者はこちら派です。
どちらが真に正しいかは判断しかねますが、実際にexit()をしているアプリも見かけます。
exit()そのものがリジェクトに直結するわけではないと考えられます。

上述のような経緯から、
iOSでアプリ終了に持ち込みたい場合、

  • 何かしらメッセージを表示した上でexit()する
  • 再起動を促すメッセージを表示した上で、ユーザーが何もできない状態にする
    • ユーザーは再起動以外できることがない

という2つの選択肢があります。

Androidの場合

Androidについては、
「プログラミングによりアプリ終了をしてよいかどうか? 」
に関する議論はあまり見たことがない気がします。

アプリ終了の具体的な実現方法については、いくつかあります。
下記の記事が詳しくまとめてくれています。
www.ecoop.net

Xamarin.Formsにおけるアプリ終了

System.Diagnostics.Process.GetCurrentProcess().CloseMainWindow()

ググってみると、

System.Diagnostics.Process.GetCurrentProcess().CloseMainWindow()
を使えばよさそうなことが分かりました。 しかしながら共通実装部からは使えません。
使うためにはNuGetパッケージを追加しなければならないのですが、 共通実装部ではパッケージの追加もできません。

(補足)
この点については環境を含めた筆者の認識・理解不足があるかもしれません。  
少なくとも手持ちの環境(Mac OS上のXamarin Studio)ではパッケージ追加できませんでした。  
また、System.Diagnostics.Processとは、本来共通実装部で使用すべきでない/できないものなのかもしれません。  
気持ち悪いですが、ここでは深追いせずに、手持ちで出来る範囲で実現した方法を記載しておきます。  

さて、共通実装部ではできなくても、
各ネイティブ(.Droid/.iOS)にはパッケージを追加して、利用することができます。
ので、今回はDependencyServiceとして実装することにしました。

Xamarin.Formsでアプリ終了を実現するコード

DependencyServiceにより実現

  • 共通実装部
  • .Droid

全く同じメソッドを呼んで同じ処理をするのにDependencyServiceにするのはちょっと納得いかないですが…。
強いて言えば、Androidでのアプリ終了はいくつかの方法があるので、実情に応じて別の実装に置き換えてもいいと思います。
例えば、

  • .Droid

とすることもできます。

今回実験をしたサンプルコードはこちら。
github.com

相変わらず実験用の適当なコードです…。

Xamarin.FormsにおけるMVVM

はじめに

本記事は、個人的な理解と見解を基に記述しています。
MVVMについて、本質を正しく・深く理解することを目的とした場合には、こことは別な記事を参照された方が良いと思います。
ここでの狙いは、Xamarin.Formsでの開発にどうしてもつきまとうMVVMという考え方を、まずはなるべく最小限の理解で乗り切ろうという発想です。

UIソフトウェアアーキテクチャパターン

以降は「UIパターン」と呼ぶことにします。
いろいろありますが、有名どころで言うと下記のようなものがあります。

  • MVC(Model-View-Controller)
  • MVP(Model-View-Presenter)
  • MVW(Model-View-Whatever)
  • MVVM(Model-View-ViewModel)

Xamarin.FormsはMVVMだと言われています。

UIパターンを理解するための考え方

上記のようなUIパターンには、それぞれの背景などがあるわけですが。
共通して言えることは、
「関心の分離」
ということだと思います。
すなわち、適切な役割分担をすることで、
クラスやモジュール間の結合を疎にしたい
という意図がまず第一にあると考えます。

一定規模以上のアプリ開発においては、
V、すなわちUIビジネスロジックの部分の分離
については、そうした方がいいのはある程度誰にでも自明のことであり、
何かしらの手段により分離が実現されていることが多いと思います。

だから、いくつかあるUIソフトウェアアーキテクチャについては、

  • まずV(UI)をどのような手法で実装すべきか/できるか?
  • V(UI)の実装のあり方から逆算してビジネスロジック部をどう分離し、繋ぎこむのがよいか?

という捉え方をした上で、
開発言語/SDK/フレームワークの特性を加味していくと、ある程度概形が見えてくるのではないかと思っています。

Xamarin.FormsにおけるMVVM

「Xamarin.FormsはMVVM」と言われる大きな要因として、XAMLの存在があります。

Xamarin.Formsにおいては、V(UI)の構築にXAMLを使用することになります。
(コードビハインドにより実装することもできますが、多くの場合XAMLを使用すると思います。)

XAMLに対して値を動的に設定したい場合はBindingが発生します。
XAMLのBinding対象となる裏方というか、影のような存在が必要です。
それがまさにVM(ViewModel)の存在です。
XAMLへのBinding対象となる存在をViewModelという呼び方をしましょう。
Viewそのものではないので、Viewと異なる関心として切り離しておきましょう。
上記の仕組みから、見た目の部分のつながりはView-ViewModelによって成り立つことになります。
その上で、ビジネスロジックは見た目から分離させた方がいいので、その部分をModelに押し込みましょう。
強引かもしれませんが、これでMVVMとなると思います。

最初の一歩を躓かないためには、多少強引にでも概形を捉えておくことが大事だと思います。

すっぴんのXamarin.FormsでMVVM

プロジェクトを作成したときのデフォルトの状態では、MVVMの形を取っていません。
最初のスケルトンがV-VMの形を取ってくれているといいのですが、そうはなっていませんね。
最初の画面がXAMLC#のセットで出来てきます。
これはVの部分だけです。
「Xamarin.FormsはMVVMだ」
とは言うものの、ViewModelはデフォルトでは存在しないのです。
加えて、はじめの一歩のちょっとした実装をしてみるには、Pageにコードビハインドして実装しても事足りてしまったりします。

Xamarin.FormsでMVVMをやろうとして、ViewModelを作ってみようと思って調べてみると…。
下記のようなINotifyPropertyChangedを継承したベースを作って、ViewModelはこれを継承する形とするのが望ましいと…。

すなわち、

  • ケルトンを作る
  • BindableBaseを作る
  • BindableBaseを継承してViewModelを作る
  • 作ったViewModelBindingContextにする

とまでやって初めて、 V-VMの部分が成り立つわけです…。
すっぴんのXamarin.FormsでMVVMというのは、意外に面倒なものです。

やはり素直に何者かの助けを借りた方がいい

仕組みを理解するためにも、一度くらいは上記の手間を踏んでやってみた方がいいですが…。
コピペとは言え、いつもBindableBaseを追加してどうこうは面倒でしかないですね。
Xamarin.FormsにおけるMVVMについては、何者かの助けを借りた方がよさそうです!

有名どころでは、

  • Prism for Xamarin.Forms
  • MVVM Light Toolkit

があるようです。
個人的には、Prism.Formsを利用してみようと思っています!

前回記事でも紹介しましたが。
Prism.Formsに関しては、nuits.jpさんがきちんとまとめて下さっていて、非常に参考になります! www.nuits.jp