Xamarin.iOS/Android で Bonjour サービスを検索する

ちょっと気になってやってみたら簡単にできた。NuGet から Zeroconf 入れるだけ。ライセンスは Ms-PL。

www.nuget.org

このライブラリはZeroconfResolverというクラスを提供していて、メソッドResolveAsyncBrowseDomainsAsyncというstaticなメソッドを提供している。アプリのプロジェクトにもパッケージを追加しておけば、PCL からも使える。

Android の場合は、[assembly: UsesPermission(Manifest.Permission.ChangeWifiMulticastState)]を書いておく必要あり。

サンプルコードとして MvvmCross で使用するときの ViewModel だけはっておく。

続きを読む

Xamarin + MvvmCrossでViewとViewModelの対応付けを変更する必要なんてある?

MSDNXamarin + MvvmCrossでViewとViewModelの対応付けを変更するというコードレシピがあったのですが、少し疑問に思ったので少し調べてみました。

ViewModelからViewを生成する仕組み

以前、MvvmCrossのiOS UniversalアプリでiPadで画面分割を作るでも書いたとおり、ViewModelから対応するViewを生成しているのはIMvxViewPresenterです。ですので、ViewModelとViewの関連づけを変えたい場合はここからの動きを変更するのが正攻法で、MvxAndroidViewPresenter.Show(MvxViewModelRequest) や MvxAndroidViewsContainer.ViewModelFromRequest、MvxAndroidViewsContainer.GetIntentFor などでフックする形の方が本来のやり方です。

おそらくコードレシピで書かれている内容では、画面遷移を発生させるとおかしくなってしまうのではと思いました(未検証ですが)。

そもそも、このような方法をとらないといけない場面は通常起こりえません。検証コードを書いていましたが、手間の割に得られるものが少なすぎると判断して中止しました。この方法が本当に必要なのか再度検討してください。

MainLauncher = false にしないと動かない?

Xamarin.Android のプロジェクトを新規作成して MvvmCross をプロジェクトに導入すると、その時点でプロジェクトには3つの Activity が存在することになります。

このうち、Xamarin Studio のテンプレートが作成する MainActivity と、MvvmCross の起動ポイントである SplashScreen は、ActivityAttribute にアプリの最初の Activity であることを示す MainLauncher = true というプロパティが指定されています。このため、二つ MainLauncher があると、同じアプリの中に 2 つアプリが存在するという形になり、ランチャに 2 つアイコンが登録されます。

つまり、MainActivity.cs が以下の場合

// using

namespace Test02.Droid
{
    [Activity(
        Label = "Test02.Droid (Main)"
        , MainLauncher = true
        , Icon = "@drawable/icon")]
    public class MainActivity : Activity
    {
        // ...
    }
}

そして、SprashScreen.cs が以下となっている場合

// using

namespace Test02.Droid
{
    [Activity([f:id:iseebi:20150301171745p:plain]
        Label = "Test02.Droid (Splash)"
        , MainLauncher = true
        , Icon = "@drawable/icon"
        , Theme = "@style/Theme.Splash"
        , NoHistory = true
        , ScreenOrientation = ScreenOrientation.Portrait)]
    public class SplashScreen : MvxSplashScreenActivity
    {
        // ...
    }
}

実際にアプリがインストールされると1つのapkに対して2つのアプリがランチャに登録されます。

f:id:iseebi:20150301171745p:plain

この状態だと、Xamarin Studioからは実行するときに SplashScreen から起動しなければならないはずが、MainActivity から起動してしまいます。

MvvmCross を Android プロジェクトに導入した後は、Xamarin Studio のテンプレートで生成される以下のファイルは不要になるので削除しましょう。

  • Resources/layout/Main.axml
  • MainActivity.cs

また、この ActivityAttribute などは最終的に AndroidManifest.xml に変換されてアプリに埋め込まれます。どのようなものが使えるかについてはa. AndroidManifest.xml ファイル - ソフトウェア技術ドキュメントを勝手に翻訳Working with AndroidManifest.xml | Xamarinなどを一読した方がいいと思います。

ヨドバシ梅田の夜間受け取り試してみた

最近あまりに寒くてちゃんとお風呂浸かってるんですが、ちょっとつまらなくて、radikoでラジオでも聴きたいと思い、Bluetooth防水スピーカーを買うことにした。

たまたま会社から見える範囲にヨドバシ梅田があって、ちょうど早めに帰れそうだったので、帰りに買って帰ろうと思ったら、結局遅くなってしまった。
 
欲しい気が止まらない上に、前々から気になってたのもあったので、夜間受け取り行ってみることにした。
 
ヨドバシのネットサイトから店頭受け取りでマルチメディア梅田を選んで注文して、確認メール届くところまでは普段のネットショッピングと変わりなし。
その後20分もすると、商品の確保ができたとメールが届く。
 
夜間受け取りの入り口は、グランフロント側、駐車場の入り口あたりにある。

MvvmCrossのiOS UniversalアプリでiPadで画面分割を作る

Xamarin Advent Calendar 2014 の 15 日目です。

4 日目には MvvmCross のプラグインの作り方を紹介しましたが、今回は MvvmCross の View 表示まわりに割り込みをして高度な表示機能を追加する IMvxViewPresenter の使い方をご紹介します。

MvvmCrossのView表示の仕組み

ところで、MvvmCross では ViewModel で ShowViewModel を実行するとサクッと View が表示されますが、実際は下で以下のような動きをしています。

  • MvxViewModelRequest が作られて、IMvxViewDispatcher.ShowViewModel が呼ばれる。
  • IMvxViewPresenter.Show にリクエストが転送される
  • IMvxViewPresenter の中で対応する View が生成されて表示される

IMvxViewPresenter には ViewModel から View を表示する以外にも、表示の変更要求メッセージである PresentationHint を受け取って処理することができます。MvvmCross では ViewPresenter を実装してカスタマイズすることで高度な画面表示の要求に応えることができます。

ViewPresenter 実装例

今回は以下のような、iPhone アプリをベースに考えます。メニュー(FirstView)があり、支店の一覧(BranchListView)があって、支店の詳細(BranchView)を表示しています。

スクリーンショット 2014-12-14 3.24.20.png

今回、このアプリの iPad 版を作ることになり、メニューはそのまま出すけど、支店の一覧と詳細は画面分割で 1 つの画面に表示という要求になっています。

スクリーンショット 2014-12-14 3.26.42.png

本来であれば UISplitViewController の出番なのですが、MvvmCross はデフォルトでルートが UINavigationController であり、SplitViewController が NavigationController の中に入るのは iOS プログラマとしてはきな臭さ MAX なので絶対やめた方がいいという考えに至ります。

そこで、iPad 用には画面分割するコンテナ ViewController を用意し、その中に ViewController を ChildViewController として追加する方法で対応することにします。

今回は以下の手順で対応していきます。

  • コンテナとなる ViewController とその中に入る ViewController の親子関係を定義するインターフェイスの作成
  • 親となる ViewController の作成
  • 子となる ViewController の設定
  • MvxViewPresenter の作成
  • MvxViewPresenter を MvvmCross に登録

インターフェイスの定義

まず、親子関係を定義するためのインターフェイスを用意します。

まず、子の ViewController には IPadContainerChild というインターフェイスを用意します。親となる ViewController の型を返します。

using System;

namespace SplitContainerSample.Touch.Views
{
    public interface IPadContainerChild
    {
        Type ContainerType { get; }
    }
}

親となる ViewController には IPadContainer というインターフェイスを用意します。

using System;
using Cirrious.MvvmCross.Views;

namespace SplitContainerSample.Touch.Views
{
    public interface IPadContainer
    {
        bool ShowView(IMvxView view);
    }
}

親となる ViewController の作成

支店詳細と支店一覧を格納する ViewController を作ります。BranchPadContainerなどとします。

スクリーンショット 2014-12-14 2.46.36.png

Xcode で画面分割された View を作ります。xib ファイルで View を 2 枚並べて、Autolayout でいい感じに分割されるようにし、左側の支店リストに当たる部分を BranchListViewContainer、右側の詳細に当たる部分を BranchViewContainer を名前をつけてアウトレット接続しています。(下の図はわかりやすいように色をつけています)

スクリーンショット 2014-12-14 2.48.44.png

using System;
using System.Drawing;

using MonoTouch.Foundation;
using MonoTouch.UIKit;
using Cirrious.MvvmCross.Views;

namespace SplitContainerSample.Touch.Views
{
    public partial class BranchPadContainer : UIViewController, IPadContainer
    {
        public BranchPadContainer() : base("BranchPadContainer", null)
        {
        }

        protected override void Dispose(bool disposing)
        {
            BranchListView = null;
            BranchView = null;
            base.Dispose(disposing);
        }

        public override void ViewDidLoad()
        {
            base.ViewDidLoad();
        }

        #region ChildView Controller

        public BranchListView BranchListView
        {
            get { return _branchListView; }
            set
            {
                SwitchChildViewController(ref _branchListView, value, BranchListViewContainer);
            }
        }
        BranchListView _branchListView;

        public BranchView BranchView
        {
            get { return _branchView; }
            set
            {
                SwitchChildViewController(ref _branchView, value, BranchViewContainer);
            }
        }
        BranchView _branchView;

        /// <summary>
        /// 子 ViewController を切り替える
        /// </summary>
        /// <typeparam name="T">対象の子ViewControllerの型</typeparam>
        /// <param name="assignViewController">ViewController を格納している変数</param>
        /// <param name="newViewController">新しい ViewController</param>
        /// <param name="containerView">ViewController を表示する View</param>
        private void SwitchChildViewController<T>(ref T assignViewController, UIViewController newViewController, UIView containerView)
            where T : UIViewController
        {
            if (assignViewController != null)
            {
                assignViewController.WillMoveToParentViewController(null);
                assignViewController.View.RemoveFromSuperview();
                assignViewController.RemoveFromParentViewController();
                assignViewController.Dispose();
            }
            assignViewController = newViewController as T;
            if (assignViewController != null) {
                AddChildViewController(assignViewController);
                assignViewController.View.Frame = BranchListViewContainer.Bounds;
                containerView.AddSubview(assignViewController.View);
                assignViewController.DidMoveToParentViewController(this);
            }
            return assignViewController;
        }
          
        #endregion

        #region IPadContainer implementation

        public bool ShowView(IMvxView view)
        {
            if (view is BranchListView)
            {
                BranchListView = view as BranchListView;
                return true;
            }
            if (view is BranchView)
            {
                BranchView = view as BranchView;
                return true;
            }
            return false;
        }

        #endregion
    }
}

ここでのポイントは、子となる ViewController の追加や取り外しにはお作法があることです。ここでは SwitchChildViewController でまるっとまとめていますが、アニメーションなども考慮してこういう形になっています。詳しい内容は Objective-C の記事を childViewController などと調べてみてください。

また、IPadContainer の実装ですが、自分に格納できる View の場合は画面に追加して true を返し、そうでなければ false を返すようにしています。

子となる ViewController の設定

子となる ViewController では IPadViewContainerChild を実装して、親となる ViewController を返します。

namespace SplitContainerSample.Touch.Views
{
    public class BranchListView : MvxTableViewController, IPadContainerChild
    {

// ---- snip ---

        #region IPadContainerChild implementation

        public Type ContainerType
        {
            get { return typeof(BranchPadContainer); }
        }

        #endregion
    }
}

MvxViewPresenter の作成

今回は iPad だけの処理なので、iPad 専用の MvxViewPresenter のみ用意し、iPhone についてはデフォルトのものを使う方針で進めたいと思います。PadViewPresenter を作成します。

using Cirrious.MvvmCross.Touch.Views;
using Cirrious.MvvmCross.Touch.Views.Presenters;
using MonoTouch.UIKit;
using SplitContainerSample.Touch.Views;
using System;

namespace SplitContainerSample.Touch
{
    public class PadViewPresenter : MvxTouchViewPresenter
    {
        protected IPadContainer PadContainer
        {
            get
            {
                IPadContainer value = null;
                _padContainerHolder.TryGetTarget(out value);
                return value;
            }
            set
            {
                _padContainerHolder.SetTarget(value);
            }
        }
        WeakReference<IPadContainer> _padContainerHolder = new WeakReference<IPadContainer>(null);

        public PadViewPresenter(UIApplicationDelegate appDelegate, UIWindow window) : base(appDelegate, window)
        {
        }

        public override void Show(IMvxTouchView view)
        {
            if (view is IPadContainerChild)
            {
                var container = PadContainer;
                var child = view as IPadContainerChild;

                // 現在表示中のコンテナがあり、表示しようとしている View と一致している
                if ((container != null) && (container.GetType() == child.ContainerType))
                {
                    if (container.ShowView(view))
                    {
                        return;
                    }
                }

                // コンテナを作る
                container = child.ContainerType.GetConstructor(new Type[0]).Invoke(null) as IPadContainer;
                if (container == null)
                {
                    throw new InvalidOperationException();
                }
                var containerViewController = container as UIViewController;

                // View プロパティにアクセスしないと View が作成されないので先に呼んでおく
                containerViewController.View.ToString(); 

                if (container.ShowView(view))
                {
                    // 作成したコンテナで表示できれば NavigationController に作成したコンテナを追加
                    PadContainer = container;
                    this.MasterNavigationController.PushViewController(containerViewController, true);
                }
                else
                {
                    // ここまできて表示できなければそのまま表示してしまう
                    containerViewController.Dispose();
                    base.Show(view);
                }
            }
            else
            {
                base.Show(view);
            }
        }
    }
}

まず、表示中のコンテナがあり、表示しようとしている View が要求しているコンテナであればその中に表示しようとします。

表示中のコンテナがなかったり、表示中のコンテナが表示を拒否した場合は新規にコンテナを作ってその中に表示します。先に View プロパティを呼んでおかないと View が初期化されないので、先に呼ぶところまでを初期化の一環としています。(Objective-C では view を呼び出すだけで OK ですが、C# だと最適化で消えちゃうかもなあ、みたいなことを思ってとりあえず ToString を空打ちしてます。)

MvxViewPresenter を MvvmCross に登録

作成した ViewPresenter は MvvmCross によってデフォルトで作られている Setup クラスにある CreatePresenter メソッドをオーバーライドして返すことで使うことができます。

iPad 版のみ差し替えなので、UIDevice.CurrentDevice.UserInterfaceIdiom プロパティで iPad かどうかチェックしています。

using MonoTouch.UIKit;
using Cirrious.CrossCore.Platform;
using Cirrious.MvvmCross.ViewModels;
using Cirrious.MvvmCross.Touch.Platform;

namespace SplitContainerSample.Touch
{
    public class Setup : MvxTouchSetup
    {
        public Setup(MvxApplicationDelegate applicationDelegate, UIWindow window)
            : base(applicationDelegate, window)
        {
        }

        protected override IMvxApplication CreateApp ()
        {
            return new Core.App();
        }

        protected override Cirrious.MvvmCross.Touch.Views.Presenters.IMvxTouchViewPresenter CreatePresenter()
        {
            if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad)
            {
                return new PadViewPresenter(ApplicationDelegate, Window);
            }
            return base.CreatePresenter();
        }
        
        protected override IMvxTrace CreateDebugTrace()
        {
            return new DebugTrace();
        }
    }
}

しあげ

さて、iPad で画面分割のサポートがあるアプリは、一番最初の項目が選択されている場合がほとんどです。

今回も画面表示時に支店一覧の最初の項目が選択されているようにしました。

public override void ViewWillAppear(bool animated)
{
    base.ViewWillAppear(animated);

    if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad)
    {
        var indexPath = MonoTouch.Foundation.NSIndexPath.FromRowSection(0, 0);
        TableView.SelectRow(indexPath, false, UITableViewScrollPosition.None);
        TableViewSource.RowSelected(TableView, indexPath);
    }
}

最後に

今回のプロジェクトはgithubにあげてあります。この記事中の内容はかなり端折っているので、こちらを実際に動かしてもらった方がわかるとおもいます。

https://github.com/iseebi/MvvmCross-SplitContainerSample

MvvmCrossのプラグインを作ってプラットフォーム依存コードを再利用する

Xamarin Advent Calendar 2014の4日目です。

MvvmCrossガチ勢としては書かざるを得ないと思っていたけど忙しくてかけていなかったMvvmCrossのプラグインの作り方をこの機会を借りて紹介したいと思います。

そもそもMvvmCrossプラグインとは

MvvmCrossプラグインは、MvvmCrossで作っているプロジェクトにカメラやバイブレーションといったプラットフォーム依存コードを再利用できる形で提供します。プラグインを組み込むと、IoCコンテナプラグインが提供しているクラスが登録されます。

公式でも多くのパッケージが公開されており、NuGetでMvvmCross Pluginで検索すると大量に出てきます。NuGetで公開されているものはプラグインを追加するとすぐに使い始めることができます。

また、プラグインは簡単に作ることができるので、自分たちでもよく使う機能はプラグイン化しておくことで再利用することができます。

プラグインの作り方

今回は簡易的なTextToSpeechの機能を作るという想定のもと、プラグインを作成してみようと思います。

続きを読む

iOS8においてカスタムキーボードの「フルアクセスを許可」をすると何が起こるのか

ATOK for iOS の設定手順で、キーボードを追加した後「フルアクセスを許可」を有効にするようにと案内されている。 この「フルアクセスを許可」というのは有効にした際に「開発元に送信することを許可します」とか書いてあって、ユーザーにとってはかなりきな臭い設定なわけだけど、実際にどういう設定なのかちょっと調べてみた。

続きを読む