ぴーさんログ

だいたいXamarin.Formsのブログ

Xamarinでの開発はWindowsとMacのどちらが良いのか?

時々聞かれるのですが......

大前提として、WindowsVisual Studioをメインに使っていくにしてもiOSアプリのビルドにはMacが必要です。 モバイルアプリを(クロスプラットフォームで)開発しようというのに、iOSに対応しないという事は基本的に無いでしょう。

よって争点は、WindowsMacを揃えた上で Visual Studio(Windows) で開発するのと、 Xamarin Studio(Mac) で開発するのではどちらが良いのかという話になります。

結論

WindowsをメインにするにしてもMac単体で開発できるようにはしておけ

Visual StudioiOSアプリ開発ではVSがMacと通信しつつ強調動作するのですが、この通信部分の調子が悪くなりやすくVSを再起動する事もしばしば。(部品が多くなると故障発生率が高まるという話ですね)

運悪くハマってVSからビルドできない状態が続く場合に備え、MacのXamarin Studioで開発出来る体制を整えておきましょう。

バージョン管理をTFVCにするとMac側で辛くなるのでGitを選択するのが無難だと思います。

【Xamarin.Forms 2.3.3 -pre2】XAML内でのネイティブView定義とBindingのサポート

Xamarin.Forms 2.3.3 -pre2でXAML内でネイティブプラットフォーム(iOSAndroid、UWP)のコントロールを配置できるようになります。 これはXamarin.Forms 2.2で追加されたNative Embeddingという機能の発展系であると言えます。

ticktack.hatenablog.jp

Native Embedding - Xamarin

おさらい

まずはXF 2.2で追加された時点のNative Embeddingがどんな物だったか確認しましょう。 使用時のコードはこんな感じです。

            // CotentPageのコンストラクタの中
#if __IOS__
            var uiLabel = new UILabel {
                MinimumFontSize = 14f,
                Lines = 0,
                LineBreakMode = UILineBreakMode.WordWrap,
                Text = text + "(iOS)",
            };
            Content = uiLabel.ToView ();
#elif __ANDROID__
            var textView = new TextView(Forms.Context) {
                Text = text + "(Android)",
                TextSize = 14,
            };
            Content = textView.ToView ();
#endif

Sharedプロジェクト内にifディレクティブでプラットフォーム固有のコードを定義します。 ネイティブプラットフォームのコントロールを(ToViewなどの)拡張メソッドでXamarin.FormsのViewに変換してXamarin.Formsのレイアウトの中に組み込んでいます。 UIKitなどのプラットフォーム固有ライブラリへの参照が発生するため、PCLでは使用する事ができずSharedプロジェクト専用となっていました。

XF 2.3.3では

こんなXAMLが動くようになります。

XAML

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"
             xmlns:androidWidget="clr-namespace:Android.Widget;assembly=Mono.Android;targetPlatform=Android"
             xmlns:formsandroid="clr-namespace:Xamarin.Forms;assembly=Xamarin.Forms.Platform.Android;targetPlatform=Android"
             xmlns:win="clr-namespace:Windows.UI.Xaml.Controls;assembly=Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
             x:Class="XFApp38.XFApp38Page">
    <ContentPage.Content>
        <StackLayout VerticalOptions="Center">
            <!-- iOSのコントロール -->
            <ios:UITextView Text="{Binding NativeText, Mode=TwoWay, UpdateSourceEventName=Ended}"
                View.BackgroundColor="{Binding Color}" />
            <ios:UILabel Text="{Binding NativeText}" View.BackgroundColor="Lime" />
            
            <!-- Androidのコントロール -->
            <androidWidget:TextView Text="{Binding NativeText}" x:Arguments="{x:Static formsandroid:Forms.Context}"/>
            
            <!-- UWPのコントロール -->
            <win:TextBlock Text="This is TextBlock"/>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

コードビハインド

using System.ComponentModel;
using System.Runtime.CompilerServices;
using Xamarin.Forms;

namespace XFApp38
{
    public partial class XFApp38Page : ContentPage
    {
        public XFApp38Page ()
        {
            BindingContext = new ViewModel ();
            InitializeComponent ();
        }
    }

    public class ViewModel : INotifyPropertyChanged
    {
        private string _NativeText = "Hoge text";
        public string NativeText {
            get { return _NativeText; }
            set {
                if (_NativeText == value)
                    return;

                _NativeText = value;
                RaisePropertyChanged ();
            }
        }

        private Color _Color = Color.FromHex("#AAAAAA");
        public Color Color {
            get { return _Color; }
            set {
                if (_Color == value)
                    return;

                _Color = value;
                RaisePropertyChanged ();
            }
        }

       #region INPC
        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged ([CallerMemberName]string propertyName = "")
        {
            PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (propertyName));
        }
       #endregion
    }
}

StackLayoutの中にiOSAndroid、UWP固有のコントロールが配置されています。

ネイティブコントロールの配置方法

Xamarin.Forms用のカスタムコントロールを配置する場合と同じように、xmlnsを宣言して対象クラスが定義されているassemblyを参照します。 通常と異なる点は targetPlatform=〜〜 を追加すること、これにより実行時のプラットフォームと異なるものを無視することができます。 (おおむねC#でのifディレクティブに相当するものと考えて良いでしょう)

ネイティブコントロールのプロパティへのBinding

ネイティブコントロールに対してもBindingを使うことができ、可能ならば2Way Bindingもサポートされます。 変更通知機構を持たないプロパティに対しては、Binding構文に追加された UpdateSourceEventName パラメータでトリガーを指定できます。 ("ios:UITextView"の部分で使用しています)

Xamarin Forms Viewのプロパティへのアクセス

ネイティブコントロールをラップするViewのパラメータに値やBindingをセットすることも可能です。("View.BackgroundColor=〜〜"の部分) ラッパーである NativeViewWrapper クラスは View のサブクラスなので、指定できるプロパティはViewと同じと考えれば良いでしょう。

ここがスゴイ

何と言ってもPCLでもプラットフォーム固有のコントロールを埋め込めることが素晴らしい! XAMLは明示的に指定しない限りテキストファイルとしてアプリに埋め込まれ、実行時に解釈されます。なので動作プラットフォーム以外のコントロールを無視すればエラーにならないんですね。いやー画期的。

さらにUpdateSourceEventNameを利用した任意イベントでの2Way Bindingまで用意されていて隙がありません。

制限事項

XamlC(XAMLの事前コンパイル)との併用ができません。

コンパイル時にプラットフォーム固有クラスへの参照が発生してしまうからでしょうね。 Sharedプロジェクトならばビルドターゲット以外のプラットフォーム部分を削った上でXAMLコンパイルできそうですが...

PCLの場合は....、参照しているプラットフォーム固有クラスの空実装dllを生成して、実行時に本物と差し代わるようにしてやればイケるでしょうか.....

UWPコントロールに対してBindingを設定するとエラーになります。サンプルコードでもUWPのTextBlockだけはリテラルだったので現時点では未対応なのかも?

【Xamarin.Forms】XAMLでViewの縦横比を一定に保つ

teratail.com

Teratailの"Xamarin Studioで幅は画面と同じ大きさ、高さが画面の幅に対して50%のViewを作りたい"(iOS)という質問に回答した時に、Aspect RatioのConstraint便利だなーと思ったのでXamarin.Formsでも同じようなことをやってみましょう。

C#でイベントハンドリングすれば実現できることは自明なので、XAMLで行いきます。

Bindingで縦横サイズを同じにすることができるので、ここにConverterをかませて比率を変えてやります。

こんな感じのConverterを定義、比率はConverterParameterで指定します。

using System;
using System.Globalization;
using Xamarin.Forms;

namespace XFApp34
{
    public class DoubleMultiplierConverter: IValueConverter
    {
        public object Convert (object value, Type targetType, object parameter, CultureInfo culture)
        {
            double multiplier;

            if (!Double.TryParse (parameter as string, out multiplier))
                multiplier = 1;

            return multiplier * (double)value;
        }

        public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException ();
        }
    }
}

使い方はこんな感じ。

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XFApp34"
             x:Class="XFApp34.XFApp34Page">
    <ContentPage.Resources>
        <ResourceDictionary>
            <local:DoubleMultiplierConverter x:Key="doubleMultiplier" />
        </ResourceDictionary>
    </ContentPage.Resources>

    <StackLayout VerticalOptions="Center">
        <BoxView x:Name="box"
                 Color="Lime"
                 HorizontalOptions="Center"
                 WidthRequest="100"
                 HeightRequest="{Binding Width, Source={x:Reference box},
                  Converter={StaticResource doubleMultiplier}, ConverterParameter=0.5}"
                 />
        <Slider Minimum="0" Maximum="500" Value="{Binding WidthRequest, Source={x:Reference box}, Mode=TwoWay}" />
        <Label Text="{Binding Width, Source={x:Reference box}, StringFormat='Width:{0:f3}'}" HorizontalOptions="Center" />
        <Label Text="{Binding Height, Source={x:Reference box}, StringFormat='Height:{0:f3}'}" HorizontalOptions="Center" />
    </StackLayout>
</ContentPage>

x:Referenceで自分自身をBindingのSourceにするのがポイント。

f:id:ticktack623:20160820122633g:plain

はいできました。

JXUGC #16 Xamarin.Forms Custom Renderer ハンズオン を開催してきました

さる2016/8/10、渋谷のdots.さん会場にて「JXUGC #16 Xamarin.Forms Custom Renderer ハンズオン」というイベントをやってきました。先生役で。

Twitterの反応を見る限り、おおむね好評だったようで何よりです。

イベントページ

JXUGC #16 Xamarin.Forms Custom Renderer ハンズオン - connpass

JXUGC #16 Xamarin.Forms Custom Renderer ハンズオン - dots. [ドッツ]

資料

ハンズオン手順

iOSAndroid、UWP(!) の3プラットフォームに対応しています。

趣旨

Xamarin.FormsのRendererを理解して自由に新規コントロールを定義したり、既存コントロールをカスタムできるようになろう。そのための要点を押さえます。

進め方

必要なコードを全て資料に載せておいて、それをコピペして動かしていくスタイル。先生(私)も実際に作りながら所々に解説を入れていきました。

私は壇上で好きに喋りながらコードをコピペしてるだけなので楽でしたが、詰まってる人を助けるサポートスタッフの方々は大変だったと思います。お疲れさまでした。<(_ _)>

今回の資料は使いまわしできるので需要があればリバイバルとかありかも知れませんねー。

【Xamarin.Forms】ViewRendererと仲良くなるための簡易チュートリアル

この記事はXamarin.Formsの標準コントロールだけでは対応しきれなくなった時、ViewRendererを駆使した独自コントロールで乗り越えるためのチュートリアルです。

目次

Xamarin.Formsコントロールの仕組み

最初にXamarin.Formsのコントロールがどのような仕組みで成り立っているか確認しておきましょう。

Xamarin.Formsでは各プラットフォームのコントロールをラップし、抽象化したコントロールを定義する事でView層コードの共通化を可能としています。 (XAMLで記述できますがWPFのように自由なUIレンダリングができるわけではありません)

この記事内では抽象化されたコントロールの事を Formsコントロール 、各プラットフォームのコントロールの事を Nativeコントロールと呼ぶ事にします。そして、FormsコントロールとNativeコントロールを結びつけるのがRendererです。

Rendererの主な役割はNativeコンロールの生成、Formsコントロールのプロパティ値変更をNativeコントロールへ伝達すること、逆にNativeコントロールのイベトをFormsコントロールに伝播させることなどです。

独自のコントロールを作るには、PCLプロジェクトでFomrsコントロールを定義、各プラットフォームプロジェクトでRendererを定義し、必要に応じて各プラットフォームプロジェクトでカスタムコントロールを定義します。

Xamarin.Forms公開当初は、既存コントロールのちょっとしたカスタマイズでもCustom Rendererが推奨されていました(それしか方法がなかったとも言う)、しかし現在ではそういった場合にはEffectsを利用するのが良いでしょう。

独自のコントロールを作る

さて、概要を把握したところで実際に独自のコントロールを作って表示してみましょう。 今回はBoxViewっぽいもの作ります。

※以降の解説コードではPCLプロジェクト構成を前提として、iOSで実装したサンプルを示します。

Formsコントロールの定義

PCLプロジェクトに Xamarin.Forms.View を継承した MyBoxView を定義。中身はありません。

using System;
using Xamarin.Forms;

namespace ViewRendererTutorial
{
    public class MyBoxView : View
    {
    }
}

Rendererの定義

iOSプロジェクトに MyBoxViewRenderer を定義します。

using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using UIKit;
using ViewRendererTutorial;
using ViewRendererTutorial.iOS;

// FormsコントロールとRendererの対応を宣言
[assembly: ExportRenderer (typeof (MyBoxView), typeof (MyBoxViewRenderer))]

namespace ViewRendererTutorial.iOS
{
    // ViewRendererの型引数にFormsコントロールとNativeコントロールを与える
    public class MyBoxViewRenderer : ViewRenderer<MyBoxView, UIView>
    {
        protected override void OnElementChanged (ElementChangedEventArgs<MyBoxView> e)
        {
            // Nativeコントロールのインスタンス生成はココ!
            if (Control == null) {
                var nativeControl = new UIView ();
                nativeControl.BackgroundColor = Color.Lime.ToUIColor ();
                SetNativeControl (nativeControl);
            }

            base.OnElementChanged (e);
        }
    }
}

Nativeコンロールのインスタンス生成は OnElementChanged 内で行います。 ButtonRendererやImageRendererなどから派生する場合はこの処理は実装済みですが、ViewRendererから派生する場合は自分で書かなくてはなりません。

これまでの実装で独自のコントロールを表示する事ができます。

f:id:ticktack623:20160611114508j:plain

<StackLayout VerticalOptions="Center">
    <local:MyBoxView x:Name="myBoxView" HeightRequest="100" />
    <Label Text="ViewRenderer tutorial" VerticalOptions="Center" HorizontalOptions="Center" />
</StackLayout>

Binding可能なプロパティでNaitiveコントロールと連携する

続いて、FormsコントロールにBindablePropertyを追加してみましょう。(BindingはXAMLの華ですからね!)

BoxViewらしくColorPropertyを追加する事にします。 (実は前段までの実装で、BackGroundColorを変えるだけで同じ事ができますが説明のために目を瞑ってください)

Formsコントロールの定義

中身が空だったMyBoxViewにBindablePropertyを追加します。

using System;
using Xamarin.Forms;

namespace ViewRendererTutorial
{
    public class MyBoxView : View
    {
        // BindablePropertyを追加
        public static readonly BindableProperty ColorProperty =
            BindableProperty.Create (nameof (Color), typeof (Color), typeof (MyBoxView), default (Color),
             propertyChanged: (bindable, oldValue, newValue) =>
                    ((MyBoxView)bindable).Color = (Color)newValue);

        public Color Color {
            get { return (Color)GetValue (ColorProperty); }
            set { SetValue (ColorProperty, value); }
        }
    }
}

Rendererの定義

using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using UIKit;
using ViewRendererTutorial;
using ViewRendererTutorial.iOS;

[assembly: ExportRenderer (typeof (MyBoxView), typeof (MyBoxViewRenderer))]

namespace ViewRendererTutorial.iOS
{
    public class MyBoxViewRenderer : ViewRenderer<MyBoxView, UIView>
    {
        protected override void OnElementChanged (ElementChangedEventArgs<MyBoxView> e)
        {
            if (Control == null) {
                var nativeControl = new UIView ();
                SetNativeControl (nativeControl);
            }

            if (e.NewElement != null) {
                // Formsコントロールのプロパティ値を反映
                UpdateColor ();
            }

            base.OnElementChanged (e);
        }

        protected override void OnElementPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged (sender, e);
            
            // プロパティ値の変更を反映
            if (e.PropertyName == MyBoxView.ColorProperty.PropertyName) {
                UpdateColor ();
            }
        }

        private void UpdateColor ()
        {
            if (Element == null)
                return;

            Control.BackgroundColor = Element.Color.ToUIColor ();
        }
    }
}

OnElementChanged で最初のプロパティ値を反映、 OnElementPropertyChanged で変更されたプロパティ値をFormsコントロールに反映させればOKです。

f:id:ticktack623:20160611115325g:plain

<StackLayout VerticalOptions="Center">
    <local:MyBoxView x:Name="myBoxView" HeightRequest="100"
            Color="Lime" />
    <Label Text="ViewRenderer tutorial" VerticalOptions="Center" HorizontalOptions="Center" />
    <Button Text="Change Color" Clicked="ButtonClicked" />
</StackLayout>
Random random = new Random();
void ButtonClicked (object sender, System.EventArgs e)
{
    myBoxView.Color = Color.FromRgb (
        random.Next (255),
        random.Next (255),
        random.Next (255));
}

BindablePropertyの変更が反映されていますね!

NativeコントロールからFormsコントロールにメッセージを送る

MyBoxViewにクリック機能を追加してみましょう。 FormsコントロールにClickedイベントを追加し、Naitiveコントロールがハンドルしたユーザー操作を伝播させます。

クリック対応自体はTapGestureRecognizerを使うとFormsコントロール側だけで完結できますが、これから解説する方法は他にも応用が効きますよ。

Formsコントロールの定義

using System;
using Xamarin.Forms;
using System.Runtime.CompilerServices;

// 指定したassemblyにinternal要素へのアクセスを許可する
[assembly: InternalsVisibleTo ("ViewRendererTutorial.iOS")]

namespace ViewRendererTutorial
{
    public class MyBoxView : View
    {
        public static readonly BindableProperty ColorProperty =
            BindableProperty.Create (nameof (Color), typeof (Color), typeof (MyBoxView), default (Color),
             propertyChanged: (bindable, oldValue, newValue) =>
                    ((MyBoxView)bindable).Color = (Color)newValue);

        public Color Color {
            get { return (Color)GetValue (ColorProperty); }
            set { SetValue (ColorProperty, value); }
        }
        
        // イベントを追加
        public event EventHandler Clicked;

        // Rendererからのシグナルを受け取る
        internal void SendClicked ()
        {
            Clicked?.Invoke (this, EventArgs.Empty);
        }
    }
}

Formsコントロールにイベントを追加、Rendererから発火してもらうために SendClicked メソッドを用意します。 また、internalメソッドを呼んでもらうために InternalsVisibleToAttribute でNativeプラットフォームAssemblyからのアクセスを許可します。

このアプローチはXamarin.Forms標準のButton、ButtonRendererのそれとほぼ同じです。

Rendererの定義

using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using UIKit;
using ViewRendererTutorial;
using ViewRendererTutorial.iOS;

[assembly: ExportRenderer (typeof (MyBoxView), typeof (MyBoxViewRenderer))]

namespace ViewRendererTutorial.iOS
{
    public class MyBoxViewRenderer : ViewRenderer<MyBoxView, UIView>
    {
        protected override void OnElementChanged (ElementChangedEventArgs<MyBoxView> e)
        {
            if (Control == null) {
                var nativeControl = new UIView ();
                SetNativeControl (nativeControl);
                
                // NativeコントロールがタップされたらFormsコントロールにシグナルを送る
                nativeControl.AddGestureRecognizer (
                    new UITapGestureRecognizer (() => Element?.SendClicked ()));
            }

            if (e.NewElement != null) {
                UpdateColor ();
            }

            base.OnElementChanged (e);
        }

        protected override void OnElementPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged (sender, e);

            if (e.PropertyName == MyBoxView.ColorProperty.PropertyName) {
                UpdateColor ();
            }
        }

        private void UpdateColor ()
        {
            if (Element == null)
                return;

            Control.BackgroundColor = Element.Color.ToUIColor ();
        }
    }
}

RendererはFormsコントロールの事を知っているのでメソッドを叩くのも容易です。

サンプルでは省略していますが、本番ではDisposeのタイミングでイベントハンドラの解除などをきちんと行いましょう。

f:id:ticktack623:20160611115857g:plain

<StackLayout VerticalOptions="Center">
    <local:MyBoxView x:Name="myBoxView" HeightRequest="100"
            Color="Lime"
            Clicked="MyBoxViewClicked" />
    <Label Text="ViewRenderer tutorial" VerticalOptions="Center" HorizontalOptions="Center" />
</StackLayout>
void MyBoxViewClicked (object sender, System.EventArgs e)
{
    DisplayAlert ("ViewRendererTutorial", "MyBoxView Clicked.", "OK");
}

クリックイベントをハンドルできました!

FormsコントロールからNativeコントロールにメッセージを送る

先程とは逆にFormsコントロールからNativeコントロールにメッセージを送る場面を考えてみます。

例えばNativeコントロールを操作するAPIを抽象化するケースがそれにあたります。

サンプルとして簡単なMapコントロールを定義し、地図の表示位置を移動させるAPIを実装してみましょう。 (これはXamarin.Forms.Mapsの実装を簡略化したものです)

Formsコントロールの定義

using System;
using Xamarin.Forms;

namespace ViewRendererTutorial
{
    public class MyMap : View
    {
        public void MoveToResion (double latitude, double longitude)
        {
            // Rendererにメッセージを送る
            MessagingCenter.Send (this, "MyMapMoveToRegion", new Tuple<double, double> (latitude, longitude));
        }
    }
}

Formsコントロールに抽象化したAPIを定義、内部でMessagingCenterを利用してRendererにメッセージを送ります。

Rendererの定義

using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using UIKit;
using MapKit;
using CoreLocation;
using ViewRendererTutorial;
using ViewRendererTutorial.iOS;

[assembly: ExportRenderer (typeof (MyMap), typeof (MyMapRenderer))]

namespace ViewRendererTutorial.iOS
{
    public class MyMapRenderer : ViewRenderer<MyMap, MKMapView>
    {
        protected override void OnElementChanged (ElementChangedEventArgs<MyMap> e)
        {
            if (Control == null) {
                var nativeControl = new MKMapView ();
                SetNativeControl (nativeControl);
            }

            // Formsコントロールからのメッセージを受け取る
            MessagingCenter.Subscribe<MyMap, Tuple<double, double>> (this, "MyMapMoveToRegion",
                                                                     (sender, args) =>  MoveToRegion(args.Item1, args.Item2), Element);
            base.OnElementChanged (e);
        }

        private void MoveToRegion (double latitude, double longitude)
        {
            var mapRegion = new MKCoordinateRegion (
                    new CLLocationCoordinate2D (latitude, longitude),
                    new MKCoordinateSpan (1.0d, 1.0d));
            Control.SetRegion (mapRegion, true);
        }
    }
}

OnElementChanged でFormsコントロールからのメッセージを購読します。 (本当はElement更新時のSubscribe、UnSubscribe、Dispose時のUnSbscribeをしないとダメですよ)

f:id:ticktack623:20160611120428g:plain

FormsコントロールからRenderer経由でNativeコントロールを操作できていますね。

さらなるステップアップ

Xamarin.FormsはOSSなので既存のソースコードを見て勉強することができます。

github.com

FormsコントロールはXamarin.Forms.Core、RendererはXamarin.Forms.Platform.【プラットフォーム】/Renderers にあります。

Xamarin.Formsのソースを読んでクールなコントロールを作りましょう。

【修正版リリース済み】Xamarin.Forms 2.2.0.43を使うとiOSで死ぬっぽい

※本件の修正版(Xamarin.Forms 2.2.0.45)がリリース済みです。そちらを使えば問題ありません。

フォーラムでのアナウンスによると...

Thanks for all of your reports. There was a problem with the build/packaging. The issues are now fixed. Please update to 2.2.0.45

What happened? One of our build machines was set to beta channel and it was 'picked' by our CI to build the Nuget packages. This caused the iOS API mismatch.

Why didn't tests catch this? Our UI tests ran on an OSX machine that was correctly set to the stable channel. We are discussing mistake-proofing guards to prevent mismatches in the future.

https://forums.xamarin.com/discussion/comment/199961/#Comment_199961

どうやらNuGetパッケージのビルドマシンがbetaチャンネルになってた所為で、新しいXamarin iOSをターゲットにしてしまったため、iOS APIを正しく呼べなかったという事みたい。


2016/5/30 現在、Xamarin.Forms 2.2のhotfixである2.2.0.43がリリースされているのですが、 何やら問題があるらしく、iOSで「Method 'CGSize..ctor' not found.」例外を吐いて死ぬ場合があります。

自分が把握している範囲では、どうやらLabelを使うと件の例外が発生する模様。

※追記 ScrollViewも死ぬみたい

f:id:ticktack623:20160530233005j:plain

謎のエラーに苦しめられる人々

対処法

Xamarin.Formsのバージョンを一つ前の2.2.0.31に落としましょう。

Visual Studioの場合、UIでバージョン指定ができるので簡単です。

一方、MacのXamarin Studio(Stable)ではUIでバージョン指定できません。(alfaチャンネルで使える次世代Xamarin StudioではVS同様にUIで選択可能)

検索キーワードに「version:x.y.z」付加すると過去のバージョンをインストールできます。(単に「version:」だけ付加すると全てのバージョンが列挙されるようなのでこっちでもいい)

f:id:ticktack623:20160530213341j:plain

バージョン指定方法を忘れて迷走する人々

早くhotfixのhotfixが来るといいですね。

Realm Xamarinを試してみた

5/10にRealmのXamarin対応版が公開されたので試してみました。

Realm Xamarinを公開! - Realm is a mobile database: a replacement for SQLite & Core Data

RealmはSQLiteやCoreDataから置き換わることを目標とするモバイルデータベースです。

Realm Xamarin自体はまだベータ版といったところですが、データベースエンジン自体は先にリリースされているJava版、Objective‑C版、Swift版と同じらしいので安心ですね。

という訳でRealm Xamarinを試すにあたってSQLiteが使われているTodoアプリのサンプルをRealmバージョンに改造してみました。

ソースコードGitHubに置いてあります。

P3PPP/xamarin-todo-with-realm

使ってみた感想

現時点ではRealmへのLinqクエリサポートが不完全なため満足なフィルタリングが使えません。実践投入するのは少なくともWhererがサポートされてからが良いでしょう。

Realmが管理中のオブジェクトはトランザクション外での変更が禁止されているので、うっかり双方向Bindingに繋げると死にます。編集画面を開いている間中トランザクションを開きっぱなしにするか、編集用のViewModelを用意する事になりそう。

サンプルの改造点

各プロジェクトでNuGetパッケージを追加、更新。 (Xamarin.Forms 2.2.0、Realm 0.74.1)

PCLプロジェクトを修正。

TodoItem.cs

SQLite版ではIDプロパティをオートインクリメントにしていますが、現時点ではRealmがオートインクリメントに対応していないそうなので、intからstringに変更してGUIDを使うことにしました。

TodoItemDatabase.cs

基本的にSQLite DBの操作をRealmに置き換え。

ただし、現時点ではRealmへのLinqクエリでWherer等のサポートが不完全なため、いったんToList()してから改めてフィルタリングしています。

App.cs

TodoItem.IDをstring型に変更した関係でApp.csも一部修正。

Views/TodoItemListX.xaml.cs

SQLite版を踏襲すると TodoItemListX.xaml.csでTodo編集ページのBindingContextにTodoItemを渡すことになります。 そのまままでは、双方向BindingでプロパティSetterが呼ばれて死ぬので一工夫必要です。 (Realmが管理中のRealmObjectはトランザクション外での編集禁止)

今回は編集用のコピーを作ってBindingContextにセットしています。