-
Notifications
You must be signed in to change notification settings - Fork 0
Home
このドキュメントは参考先のめちゃ意訳や私の今までの開発経験に基づく Tips などを記述してます。
さらっと読んで頭の片隅にうろおぼえながらでも入ってると、後の開発中のハマりが少なくすむかもしれないです。
アプリがクラッシュしても Logcat に有用な情報が出力されない場合、adb で下記コマンドを入力することにより原因解析ができます。
pull /data/anr/traces.txt.bugreport C:\Users\xxx\Desktop
pull /data/anr/traces.txt C:\Users\xxx\Desktop
ANR (Application Not Responding) が発生すると上記ファイルに随時書き込まれていくので手がかりになります。
Xamarin ではすべてのプラットフォームにおいて、Mono C# (または F#) コンパイラーが C# または F# コードを Microsoft Intermediate Language (MSIL: 中間言語) にコンパイルします。 さらに、ターゲットデバイス上の .NET Common Language Runtime (CLR) が Just in Time (JIT: 実行時) コンパイラーを使い、MSIL をネイティブコードにコンパイルして実行します。
ただし、iOS の場合はセキュリティー上の制約があるため、デバイス上で動的生成されたコードを実行することが出来ません (シミュレーターの場合は JIT コンパイラーによって実行されます)。 この制約を守った上で動作させるため、Xamarin.iOS では MSIL を Ahead of Time (AOT: 事前) コンパイラーによってコンパイルします。 これにより、必要に応じ LLVM で最適化されたネイティブの iOS バイナリーが生成されます。
AOT により、いくつかの制限があります。詳細は Limitations - Xamarin を参照してください。 また、JIT に比べて起動時間の短縮など様々なパフォーマンスの最適化によるメリットもあります。
Windows 上で開発を行っている場合、ビルドを行ったときは C# から MSIL へのコンパイルのみ Windows 上で行われます。 MSIL から iOS バイナリーへの AOT コンパイル、バンドルへのパッケージング、署名などがリモートの Mac 上で行われます。 そのため、ビルドパフォーマンスに影響する箇所は、Windows 上でのコンパイル、Mac 上でのコンパイルから署名、そしてそれらをつなぐネットワークのいずれかになります。
参考
ウィンドウタイトルにソリューションがあるフォルダー名が表示されます。
ファイル保存時の自動フォーマットや、プロジェクトごとにエディターのタブの色を変えるなど、細かなツールの集合体です。
XAML (ザムル) は Xamarin.Forms アプリケーションのユーザーインターフェースを定義することが出来るマークアップ言語です。コードでユーザーインターフェースを定義することも出来ますが、XML ベースの言語である XAML で定義したほうがぱっと見わかりやすいです。また、 MVVM (Model-View-ViewModel) アプリケーションを作成するために、データバインディングによって ViewModel と連携させた View を定義できます。
XAML には GUI デザイナーがまだ存在しないので手書きする必要があります。ただし、Visual Studio による補間機能などの支援があるので単純なエディターでプレーンテキストを編集するより少しは楽です。
CSS でいうところの content-box と同じです。WidthRequest と HeightRequest の値に Padding の値は含まれません (つまり、実際に表示される領域は WidthRequest/HeightRequest + Padding になります)。
XAML 上で System.String.Format メソッドを使った文字列処理を書くための構文が用意されています (内部的にはマークアップ拡張 (Markup extension) という機能を利用しています)。
次のようにバインディング時に StringFormat
属性を記述するだけです。
<Slider x:Name="slider" />
<Label Text="{Binding Source={x:Reference slider},
Path=Value,
StringFormat='The slider value is {0:F2}'}" />
参考
ValueConverter とは、View と ViewModel 間のデータバインディングで値が送られる際に呼ばれる処理です。これを実装してバインディング時に宣言することで、表示のための適切な値に変換したり型を変換したりします。
参考
Messaging Center はメッセージの送受信を行うためのシンプルなサービスです。メッセージングベースの設計にすることで、コードの結合を減らすことができます。
MessagingCenter
は静的クラスであり、Subscribe
メソッドと Send
メソッドを持っています。
-
Subscribe
シグネチャーを指定してメッセージを待ち、受信したときに何らかの処理を行います。 複数のサブスクライバーが同じメッセージを受信することもできます。
-
Send
サブスクライバーへメッセージを送ります。サブスクライバーが存在しない場合、そのメッセージは無視されます。
メソッドにはアドレスとなる message
パラメーターを指定します。また、配信方法をより詳細に制御するためにジェネリックパラメーターも指定します。例え 2 つのメッセージが同じ message
であっても、ジェネリックパラメーターが異なる場合はそれぞれ別のサブスクライバーに送られます。
MessagingCenter
の API はシンプルです。
-
Subscribe (object subscriber, string message, Action callback, TSender source = null)
-
Subscribe<TSender, TArgs> (object subscriber, string message, Action<TSender, TArgs> callback, TSender source = null)
-
Send (TSender sender, string message)
-
Send<TSender, TArgs> (TSender sender, string message, TArgs args)
-
Unsubscribe<TSender, TArgs> (object subscriber, string message)
-
Unsubscribe (object subscriber, string message)
メッセージングはおそらくユーザーインタラクション (例えばボタンのタップ) やシステムイベント (例えばコントロールの状態の変化)、またはほかの事象 (例えば非同期ダウンロードの完了) として送ることになるでしょう。そしてサブスクライバーはたぶんユーザーインターフェースの見た目の変化やデータの保存、または他の操作などのタイミングを監視するでしょう。
以下に message
パラメーターに単純な文字列を渡す例を示します。この例では送信元が MainPage
型であることを期待しています。ソリューション内の任意のクラスは次のようにして監視することができます。
MessagingCenter.Subscribe<MainPage> (this, "Hi", (sender) => {
// do something whenever the "Hi" message is sent
});
MainPage
クラスに次のような記述をすることで上記のサブスクライバーにメッセージを送ることができます。
MessagingCenter.Send<MainPage> (this, "Hi");
上記のように書くことで、なにかのイベントのタイミング (アップロードが完了した場合など。この例だと Hi
イベント😆) だけを知ることができます。
引数を渡す場合は Subscribe
のジェネリックパラメーターに引数の型を指定します。
MessagingCenter.Subscribe<MainPage, string> (this, "Hi", (sender, arg) => {
// do something whenever the "Hi" message is sent
// using the 'arg' parameter which is a string
});
送信元も同様に Send
メソッドにジェネリックパラメーターで引数の型を指定します。
MessagingCenter.Send<MainPage, string> (this, "Hi", "John");
この例では string
型を渡していますが C# のどんな型でも渡すことができます。
Unsubcribe
メソッドに対象のシグネチャー (アドレスとジェネリックパラメーター) を指定することで、一致する監視を解除することができます。
MessagingCenter.Unsubscribe<MainPage> (this, "Hi");
MessagingCenter.Unsubscribe<MainPage, string> (this, "Hi");
参考
XAML 上で宣言的にイベントまたはプロパティーの変化と連動してコントロールの見た目を変更することが出来ます。
トリガーには 4 種類あります。
-
プロパティートリガー
コントロールのプロパティーが特定の値に変化したタイミングと連動することができます。
-
データトリガー
他のコントロールのプロパティーと連動させることができます。
-
イベントトリガー
コントロールのイベントが発生したタイミングと連動させることができます。
-
マルチトリガー
複数のトリガーを条件にすることができます。
シンプルなトリガーは素の XAML で、対象のコントロールに Trigger
要素を追加することで表現できます。次の例ではフォーカスを受け取ったときに Entry
の背景色を変更します。
<Entry Placeholder="enter name">
<Entry.Triggers>
<Trigger TargetType="Entry"
Property="IsFocused" Value="True">
<Setter Property="BackgroundColor" Value="Yellow" />
</Trigger>
</Entry.Triggers>
</Entry>
次にトリガーの定義で重要な部分を示します。
-
TargetType
トリガーを適用するコントロールの型を指定します。
-
Property
適用するコントロールが監視するプロパティーを指定します。
-
Value
監視するプロパティーがこの値になったタイミングで Setter 要素の内容が反映されます。
-
Setter
トリガーの条件を満たしたときに
Setter
要素のコレクションが反映されます。Property
とValue
属性を指定する必要があります。 -
EnterActions と ExitActions
Setter の代わり、もしくは追加で反映するコードを記述できます。詳しくは後述します。
アプリの ResourcesDictionary
、ページ、またはコントロールの Style
要素にもトリガーを適用することができます。次の例ではページ内のすべての Entry
コントロールに対してスタイルを反映します。
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Entry">
<Style.Triggers>
<Trigger TargetType="Entry"
Property="IsFocused" Value="True">
<Setter Property="BackgroundColor" Value="Yellow" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</ContentPage.Resources>
他のコントロールのプロパティー値の変化を監視して、その変化に連動してトリガーを反映することが出来ます。プロパティートリガーでの Property
属性の代わりに Binding
属性に指定した値と連動します。
次の例の {Binding Source={x:Reference entry}, Path=Text.Length}
の部分でどのようにバインディングすれば良いかわかると思います。entry
(x:Name
属性が一致する要素) の Text.Length
プロパティーの値が 0 になったときにトリガーが反映されます。つまり、入力欄が空になったときにボタンが無効化されます。
<!-- the x:Name is referenced below in DataTrigger-->
<!-- tip: make sure to set the Text="" (or some other default) -->
<Entry x:Name="entry"
Text=""
Placeholder="required field" />
<Button x:Name="button" Text="Save"
FontSize="Large"
HorizontalOptions="Center">
<Button.Triggers>
<DataTrigger TargetType="Button"
Binding="{Binding Source={x:Reference entry},
Path=Text.Length}"
Value="0">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Button.Triggers>
</Button>
上記の例のような場合、監視対象のプロパティーにデフォルト値を指定してください。指定しない場合 null になってしまうためトリガーが反映さません。
Setter
要素を追加することにより EnterActions
と ExitActions
も適用することができます。
EventTrigger
要素は Clicked
などの Event
プロパティーのみ指定する必要があります。
<EventTrigger Event="Clicked">
<local:NumericValidationTriggerAction />
</EventTrigger>
Setter
要素はありませんが、ページに xmlns:local
を定義して local:NumericValidationTriggerAction
を参照していることに注意してください。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:WorkingWithTriggers;assembly=WorkingWithTriggers"
NumericValidationTriggerAction
クラスは TriggerAction
クラスを implement し、トリガーイベントが発生するたびに呼ばれる Invoke
メソッドを実装するべきです。
トリガーアクションは以下の実装を行ってください。
-
TriggerAction<T>
を implement する際に、トリガーを適用するコントロールの型を指定します。VisualElement
を指定することでさまざまなコントロールで動作するトリガーアクションを作成したり、Entry
のような特定のコントロールの型を指定することができます -
トリガーの条件が満たされるたびに呼ばれる
Invoke
メソッドをオーバーライドします -
必要に応じて、XAML 上でトリガーを定義する際に設定できるプロパティーを公開します (この例に追加するとすれば、文字数を制限するために
MinLength
プロパティーをpublic
で定義することになるでしょう)
public class NumericValidationTriggerAction : TriggerAction<Entry>
{
protected override void Invoke (Entry entry)
{
double result;
bool isValid = Double.TryParse (entry.Text, out result);
entry.TextColor = isValid ? Color.Default : Color.Red;
}
}
上記の例では追加していませんが、もし MinLength
プロパティーを公開した場合は次のようにトリガーアクションを定義します。
<EventTrigger Event="TextChanged">
<local:NumericValidationTriggerAction MinLength="10" />
</EventTrigger>
ResourceDictionary で複数のコントロールにトリガーを適用した場合、すべてのコントロールに同じ変更が反映されてしまうので注意してください。
また、イベントトリガーでは EnterActions
と ExitActions
がサポートされていないので気をつけてください。
マルチトリガーは単一の条件のときはトリガーやデータトリガーに似ています。条件が複数ある場合はそれらすべてが満たされない限り反映されることはありません。
次の例では 2 つの入力 (email と phone) に対してボタンのトリガーをバインドしています。
<MultiTrigger TargetType="Button">
<MultiTrigger.Conditions>
<BindingCondition Binding="{Binding Source={x:Reference email},
Path=Text.Length}"
Value="0" />
<BindingCondition Binding="{Binding Source={x:Reference phone},
Path=Text.Length}"
Value="0" />
</MultiTrigger.Conditions>
<Setter Property="IsEnabled" Value="False" />
<!-- multiple Setter elements are allowed -->
</MultiTrigger>
Conditions
コレクションは次のような PropertyCondition
要素を持つことができます。
<PropertyCondition Property="Text" Value="OK" />
マルチトリガーはすべての条件が満たされたときだけ反映されます。
すべての入力欄のテキストの長さが 0 かどうかを判定する場合、XAML では表現できないためコードで条件を書く必要があります。
その場合、IValueConverter
を implement することになります。次のようにコンバーターでテキストの長さが 0 であるかどうかを bool
に変換してください。
public class MultiTriggerConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if ((int)value > 0) // length > 0 ?
return true; // some data has been entered
else
return false; // input is empty
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotSupportedException ();
}
}
マルチトリガーでこのコンバーターを使用する場合、次のようにリソースディクショナリーをページに追加してください (対応する xmlns:local
名前空間も)。
<ResourceDictionary>
<local:MultiTriggerConverter x:Key="dataHasBeenEntered" />
</ResourceDictionary>
XAML は次のようになります (最初のマルチトリガーとして挙げた XAML の例とは少し違うことに注意してください)。
-
ボタンはデフォルトで
IsEnabled="false
に設定されます -
マルチトリガーの条件として
Text.Length
を真偽値に変換するコンバーターを使用します -
すべての条件が満たされたとき、ボタンの
IsEnabled
プロパティーがtrue
になります
<Entry x:Name="user" Text="" Placeholder="user name" />
<Entry x:Name="pwd" Text="" Placeholder="password" />
<Button x:Name="loginButton" Text="Login"
FontSize="Large"
HorizontalOptions="Center"
IsEnabled="false">
<Button.Triggers>
<MultiTrigger TargetType="Button">
<MultiTrigger.Conditions>
<BindingCondition Binding="{Binding Source={x:Reference user},
Path=Text.Length,
Converter={StaticResource dataHasBeenEntered}}"
Value="true" />
<BindingCondition Binding="{Binding Source={x:Reference pwd},
Path=Text.Length,
Converter={StaticResource dataHasBeenEntered}}"
Value="true" />
</MultiTrigger.Conditions>
<Setter Property="IsEnabled" Value="True" />
</MultiTrigger>
</Button.Triggers>
</Button>
EnterActions と ExitActions コレクションを追加し、TriggerAction<T>
を実装したクラスを指定することで、トリガーが反映される際の変更内容をコードで表現することができます。
EnterActions
と ExitActions
と共に Setter
も指定することができますが、EnterActions
や ExitAction
とは違って即座に実行されます (EnterActions
や ExitActions
の完了を待ちません)。とはいえ、Setter
で出来ることはすべてコードで表現することができます。
<Entry Placeholder="enter job title">
<Entry.Triggers>
<Trigger TargetType="Entry"
Property="Entry.IsFocused" Value="True">
<Trigger.EnterActions>
<local:FadeTriggerAction StartsFrom="0" />
</Trigger.EnterActions>
<Trigger.ExitActions>
<local:FadeTriggerAction StartsFrom="1" />
</Trigger.ExitActions>
<!-- You can use both Enter/Exit and Setter together if required -->
</Trigger>
</Entry.Triggers>
</Entry>
XAML でクラスを参照する際は xmlns:local
などのようにして使用する名前空間を指定する必要があります。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:WorkingWithTriggers;assembly=WorkingWithTriggers"
FadeTriggerAction
の実装は次のようになります。
public class FadeTriggerAction : TriggerAction<VisualElement>
{
public FadeTriggerAction() {}
public int StartsFrom { set; get; }
protected override void Invoke (VisualElement visual)
{
visual.Animate("", new Animation( (d)=>{
var val = StartsFrom==1 ? d : 1-d;
visual.BackgroundColor = Color.FromRgb(1, val, 1);
}),
length:1000, // milliseconds
easing: Easing.Linear);
}
}
EnterActions
と ExitActions
はイベントトリガー上では無視されることに注意してください。
参考
Xamarin.Forms が提供している Gesture Recognizer はイベントのバブリングがうまく行われません。例えば、子孫要素にハンドラーを指定すると親要素のハンドラーは実行されないため、共通的な処理が書きにくいです。必ずバブリングさせたい場合、有償の MR.Gestures というライブラリーの使用を検討してみてください。このライブラリーではきちんとバブリングしてくれます (宣伝したいわけじゃないですが、それしか 2018-03-17 時点では選択肢がないようにみえます)。
これらの対策方法はパフォーマンスの向上に役立ちますが、もしあなたのプロジェクトで発生している問題が別の要因にある場合、大した改善は得られません。
また、Jason Smith's Xamarin Forms Performance Tips · Kent Boogaart の内容も非常に役に立つと思います。
Xamarin.Forms 2.5 から導入された Layout Compression を使うことで、ビジュアルツリーから指定されたレイアウトを削除し、ページレンダリングのパフォーマンスが改善されます。この機能は iOS と Android の両方で効果があります。
Xamarin.Forms がレイアウトを決定する際の走査範囲は 2 種類あります。 1 つめは、ビジュアルツリーの頂点から下っていって走査するすべての要素です。 もうひとつは、サイズや位置が変更されて最新の情報を取得できていない要素とその先祖要素です。
Xamarin.Forms がどのようにレイアウトを決定するかの詳細は Creating a Custom Layout - Xamarin を参照してください。
レイアウト処理を行うとネイティブコントロールの階層が作成されます。ただし、この階層には Xamarin.Forms がコンテナーやラッパーとして作成した層が余分に含まれています。ネストが深くなればなるほど、Xamarin.Forms がページを表示する際の計算量が増えてしまいます。レイアウトを複雑にしてビューの数が多い場合も同様です。
XAML の場合、CompressedLayout.IsHeadless
プロパティーに true
を指定することで Layout Compression が有効になります。
<StackLayout CompressedLayout.IsHeadless="true">
...
</StackLayout>
C# の場合、CompressedLayout.SetIsHeadless
メソッドの最初の引数にレイアウトインスタンスを渡すことで有効にできます。
CompressedLayout.SetIsHeadless(stackLayout, true);
Layout Compression はビジュアルツリーからレイアウトを削除するため、視覚的な役割がある場合やタッチ入力を受け付ける場合は適切ではありません。
そのため、 VisualElement プロパティー (BackgroundColor、IsVisible、Rotation、Scale、TranslationX、TranslationY など) またはジェスチャーを入力させるレイアウトの場合は推奨しません。しかし、その場合でもビルドやランタイムエラーにはならず、ただ単に視覚効果が適用されないということに注意してください。
試してみた感じ、VerticalOptions
、HorizontalOptions
、Spacing
はあっても問題なく動いてるように見えます。
Fast renderers は、ネイティブビューの階層を平坦化することにより Android 上の Xamarin.Forms コントロールの inflation とレンダリングコストを削減します。これによってビジュアルツリーの複雑さとメモリーの使用量を減らしパフォーマンスを改善します。Fast renderers の詳細については Fast Renderers - Xamarin を参照してください。良いケースだとビューの数が半分程度になります。
参考
Xamarin.Forms ではネイティブのレイアウトエンジンとは別に Xamarin.Forms のレイアウトエンジンが挟まるため、重くなりやすいレイアウトの指定を避けておいたほうが得策です。幸いなことに先人たちが陥ったハマりポイントに関する知見がいくつかあるので事前に頭に入れておいたほうが良いと思います。その場では問題が起きていなくても、後から入ったデザインの変更や画面回転の対応など、思いもしないところで問題が発生する場合があります。
やむを得ない限り AutoSize を指定するのはやめたほうが良いです。
レイアウトが少し複雑になっただけでも描画がちらついたりする場合があり、もしそうなってしまった場合、自力でサイズ計算をして絶対値指定するように修正する羽目になります (私の場合、問題にならないだろうと甘く見ていたおかげで、後々この修正作業をしなければならない状況を体験することが出来ました👼)。そうなる前に、あらかじめ AutoSize を指定しなくて済むデザインを考えたほうが労力は少なく済みます。
画面回転に対応する場合、AbsoluteLayout/RelativeLayout はうまく再レイアウトしてくれない場合があることに注意してください。おそらく複雑なレイアウトになるほど発生しやすい現象です。 この問題を回避するためには、AbsoluteLayout/RelativeLayout が表示された (IsVisible が false から true になった)、および画面が回転した (ビューの OnSizeChanged もしくは ContentPage の OnSizeAllocated が呼ばれた) タイミングで、その AbsoluteLayout/RelativeLayout の ForceLayout() メソッドを呼び出してください。多分回避できると思います。
Windows から Mac Agent 経由でデバッグしてる場合、Mac 側にも同じバージョンの Live Inspector をインストールする必要があります。
XAML のコンパイルがうまくいってないようです。PCL プロジェクトを Unload してから、Clean、Build すると直ります。
The "User7ZipPath" parameter is not supported by the "XamarinDownloadArchives" task. Verify the parameter exists on the task, and it is a settable public instance property.
ビルドタスクのバージョンが古いと色々問題があるらしいです。NuGet でソリューションにインストールされた Xamarin.Build.Download をアップデートし、Visual Studio を再起動すると直ります。
XAML の記述に誤りがあると、このメッセージが出る場合があります。使用している名前空間をちゃんとインポート出来ているか (XAML ファイル内の最初に定義する xmlns:~
ってやつ) を確認してみてください。