みかづきメモ

学習したことのメモとか、日記とか、備忘録。

Xamarin.Forms で iOS/Android だけの UI

タイトルではちょっとわかりにくいかもですが、 Xamarin.Forms のいいところは、
iOSAndroid で UI を共通化させることで、コードの記述量がへることです。

そういうことで、私も iOSAndroid で共通の UI を使っているのですが、
iOS もしくは、 Android だけで表示させたいパーツが有るときの方法みたいな。


前に上げた記事において、ツールバーの左側にアイテムを追加しました。

mikazuki.hatenablog.jp

私の場合、モーダルダイアログで使用しているのですが、 iOS だと戻るボタンはありませんが、
Android だと、ハードウェアで戻るボタンがすでに存在しているため、必要ありません。

こういった、Android や WP ではいるけど、 iOS では必要ないといった場合の制御を行います。

必要なのは、 Behavior と Attached Property の2つ。
GridDockPanel みたいな感じで使えるようにしたいと思います。
仕組みとしては、 Attached Property で設定された値を、親コントロールへ通知し、
該当コントロールを Children から削除します。

ということでまずは Behavior から
なお、どちらも Windows Phone は省いていますが、同様に実装できます。

public class PlatformBehavior : Behavior<Layout<View>>
{
    private Layout<View> _layout;
    private IDisposable _disposable;
    private bool _flag;

    protected override void OnAttachedTo(Layout<View> bindable)
    {
        this._layout = bindable;
        this._disposable = Observable.Timer(TimeSpan.Zero, TimeSpan.FromMilliseconds(10))
            .Select(_ => this._flag)
            .DistinctUntilChanged()
            .Where(w => w)
            .Repeat()
            .Subscribe(_ => this.DoAction());
        bindable.ChildAdded += Bindable_ChildAdded;
        base.OnAttachedTo(bindable);
    }

    protected override void OnDetachingFrom(Layout<View> bindable)
    {
        base.OnDetachingFrom(bindable);

        bindable.ChildAdded -= Bindable_ChildAdded;
        this._disposable.Dispose();
    }

    private void Bindable_ChildAdded(object sender, ElementEventArgs e)
    {
        this._flag = true;
    }

    private void DoAction()
    {
        var children = this._layout.Children;
        foreach (var child in children)
        {
            if (Device.OS == TargetPlatform.Android)
            {
                if (!(bool)child.GetValue(PlatformControl.AndroidProperty))
                {
                    this._layout.Children.Remove(child);
                }
            }
            if (Device.OS == TargetPlatform.iOS)
            {
                if (!(bool)child.GetValue(PlatformControl.iOSProperty))
                {
                    this._layout.Children.Remove(child);
                }
            }
        }

        this._flag = false;
    }
}


次に Attached Property

public class PlatformControl
{
    // iOS
    public static bool GetiOS(BindableObject obj)
    {
        return (bool)obj.GetValue(iOSProperty);
    }

    public static void SetiOS(BindableObject obj, bool value)
    {
        obj.SetValue(iOSProperty, value);
    }

    public static readonly BindableProperty iOSProperty = 
        BindableProperty.CreateAttached("iOS", typeof(bool), typeof(PlatformControl), true);


    // Android
    public static bool GetAndroid(BindableObject obj)
    {
        return (bool)obj.GetValue(AndroidProperty);
    }

    public static void SetAndroid(BindableObject obj, bool value)
    {
        obj.SetValue(AndroidProperty, value);
    }

    public static readonly BindableProperty AndroidProperty =
        BindableProperty.CreateAttached("Android", typeof(bool), typeof(PlatformControl), true);
}

これで完成です。


XAML では、下のようにして使います(この程度なら OnPlatform でもいけます)。

<Grid>
    <Grid.Behaviors>
        <behaviors:PlatformBehavior />
    </Grid.Behaviors>
    <Label Text="iOS Only" HorizontalOptions="StartAndExpand"
        e:PlatformControl.iOS="True" e:PlatformControl.Android="False" />
    <Label Text="Android Only" HorizontalOptions="StartAndExpand"
        e:PlatformControl.iOS="False" e:PlatformControl.Android="True" />
</Grid>

e:PlatformControl.iOSTrue にすると表示、 False にすると非表示です。
デフォルトは True なので、表示する場合は省略できます。

いつもながら、もしかしたら標準 API で提供されているかもしれません。
ということで、ではでは。