Xamarin.iOS/Androidアプリで、バンドルリソース処理を完全共通化できそうな仕様 – Qiita
http://qiita.com/kochizufan/items/69d69f37cf991d452226#comment-172bac67ec6257dc81c2
Embedded Resource | Xamarin
http://docs.xamarin.com/content/EmbeddedResources/
多分、PCL を使うと共通できるだろうなぁ、とは思っていたのですが、これといってよい方法が思いつかなかったのです。iOS/Android それぞれのプロジェクトに入れるしかないか(コピーあるいは共有プロジェクト)とは考えていたものの。なるほど、PCL のアセンブリから直接リソースを読みだす技がありましたね。
https://github.com/xamarin/mobile-samples/blob/master/EmbeddedResources/SharedLib/ResourceLoader.cs
のソースをざってと見ていたのですが、iOS/Android 側で GetEmbeddedResourceStream メソッドを呼び出さないといけないのが難点です。と言いますか、PCL の .NET ライブラリには、Assembly クラスの static メソッドが非常に制限されていて、なんともしようがないのです。と思っていたのですが、typeof(ResourceLoader).GetTypeInfo().Assembly という技で、通常の Assembly を取れる技を知りました。
いやいや、よくよくリソースファイル Resources.Designer.cs を覗いてみれば、
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("XamarinPortableRes.Lib.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly);
なる書き方がされていて、なーんだ。普通に使える方法じゃないですか。
■文字列リソースを追加する
Embedded Resource の例では、文字列リソースをテキストファイルから読み込んでいますが、これは普通のリソースにしたいですよね。普通に「プロジェクト」→「リソース」で文字列を追加できます。
まだ試してはいませんが、国際化による言語切り替えもこれでやると楽かと。
■文字列リソースを使う
使う側も簡単です。string 型で返してくれるので、そのまま Text プロパティなどに設定できます。
Android の場合
FindViewById(Resource.Id.textView1).Text = XamarinPortableRes.Lib.Properties.Resources.Message1;
iOS の場合
this.text1.Text = XamarinPortableRes.Lib.Properties.Resources.Message1;
■画像リソースを追加する
PCL プロジェクトのリソース追加は「文字列」のみに制限されています。制限されている理由はわからないのですが、無理やり突っ込んでも *.resx ファイルから *.desinger.cs に変換でダメになるので、そのままでは使い勝手が悪いです。
なので、Resources というフォルダを作って、画像ファイル(pngファイルなど)を入れます。プロパティウィンドウでビルドアクションを「埋め込まれたリソース」にしておきます。こうすると、先の「バンドルリソース処理を完全共通化~」に書いてあるように「XamarinPortableRes.Lib.Resources.MarkBlue.png」のように、ドットつながりでリソースが取れます。
このリソース名をそのまま使ってもいいのですが、文字列指定を間違えると実行エラーになるし、どうせならばインテリセンスが効くようにしたいので、少しだけ工夫します。
こんな風に、リソースを取り出すための ResourceLoader クラスを作ります。ResourceManager みたいなものです。リソース自体は1回しか読み込まないので、static で十分です。名前から検索するのは Embedded Resource の借用ですが、初回のみ GetManifestResourceNames メソッドでリソース名の一覧を取得します。
public static class ResourceLoader { static internal string[] Names { get; set; } static internal Assembly Assembly { get; set; } public static System.IO.Stream GetObject(string resourceName) { if (ResourceLoader.Assembly == null) { ResourceLoader.Assembly = typeof(ResourceLoader).GetTypeInfo().Assembly; ResourceLoader.Names = ResourceLoader.Assembly.GetManifestResourceNames(); } try { string path = ResourceLoader.Names.First(x => x.EndsWith(resourceName, StringComparison.CurrentCultureIgnoreCase)); return ResourceLoader.Assembly.GetManifestResourceStream(path); } catch { return null; } } }
インテリセンスが効くように Resources クラスに「手動」でプロパティを作ります。このあたりは T4 で作ってもいいし、まあ色々。これで、Stream までは取得できます。
public class Resources { public static System.IO.Stream MarkBlue { get { return ResourceLoader.GetObject("MarkBlue.png"); } } public static System.IO.Stream MarkGreen { get { return ResourceLoader.GetObject("MarkGreen.png"); } } public static System.IO.Stream MarkNone { get { return ResourceLoader.GetObject("MarkNone.png"); } } public static System.IO.Stream MarkOrange { get { return ResourceLoader.GetObject("MarkOrange.png"); } } public static System.IO.Stream MarkPurple { get { return ResourceLoader.GetObject("MarkPurple.png"); } } public static System.IO.Stream MarkRed { get { return ResourceLoader.GetObject("MarkRed.png"); } } }
■画像リソースを使う
画像リソースから取ってくると、System.IO.Stream を取得できますが、そのままだと扱いにくいので System.Drawing.Bitmap にしたいところなのですが、iOS/Android とそれぞれ異なる(UIImage, Android.Graphics.Bitmap)ので、ヘルパーメソッドを作っておきます。
Android の場合は、Stream から Bitmap を返すまで。
global::Android.Graphics.Bitmap ToBitmap(System.IO.Stream st) { using (var mem = new System.IO.MemoryStream()) { st.CopyTo(mem); st.Close(); var data = mem.ToArray(); var bmp = global::Android.Graphics.BitmapFactory.DecodeByteArray(data, 0, data.Length); return bmp; } }
こんな風に画像設定をします。
this.imageBlue = FindViewById(Resource.Id.imageView1); imageBlue.SetImageBitmap(ToBitmap(XamarinPortableRes.Lib.Resources.MarkBlue));
iOS の場合は、UIImage を作成します。
UIImage ToBitmap(System.IO.Stream st) { var data = NSData.FromStream( st ); st.Close(); var bmp = UIImage.LoadFromData(data); return bmp; }
そして、設定
this.imageBlue.Image = ToBitmap(XamarinPortableRes.Lib.Resources.MarkBlue);
本来は ToBitmap まで共通化したいところですが、それぞれ画像の扱いが異なるので、ここぐらいまで。更に Windows Store, Windows Phone を追加すると、より汎用的になるはずですが、これはあとから。
# github で、ToBitmap を Stream クラスの拡張メソッドにする方法に変えたので、説明も後で変更します。
■サンプルコード
https://github.com/moonmile/XamarinPortableRes
■実行結果