ぴーさんログ

だいたいXamarin.Formsのブログ

Microsoft MVP for Visual Studio and Development Technologies を受賞しました

2017年3月づけ(?)でMicrosoft MVP Awardを受賞しました。

受賞カテゴリはVisual Studio and Development Technologiesです。

今年の2月からMicrosoft MVP Award Programが改定され、年4回だった審査&表彰が毎月になりました。私はその第二陣ということになります。

kogelog.com

どうやら受賞する人数も増えているようですし全人類がMS MVPになるもの時間の問題ですね。

以前は受賞からしばらくするとできると聞いていたMVP受賞者の個人ページもすでに用意されており、Microsoft MVP Award Programの高速に回していくんだという意思が感じられます。

「お前なら受かるやろ」と言ってくださった方々に後押しされての受賞とあいなりました。皆様ありがとうございます!

【Xamarin.Forms 2.3.4-pre】ちゃんとBindableになったPicker

Xamarin.Forms 2.3.4-preでPickerがItemsSourceとItemSelectedをサポートするようになります。すっごーい!

さっそくこんな感じのViewModelを用意してContentPageのBindingContextにセットします。

public class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public IEnumerable<AnimalFriend> Friends { get; }

    AnimalFriend _bestFirend;
    public AnimalFriend BestFriend
    {
        get { return _bestFirend; }
        set
        {
            _bestFirend = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(BestFriend)));
        }
    }

    public ViewModel()
    {
        Friends = new[]{
            new AnimalFriend { Name = "サーバル" },
            new AnimalFriend { Name = "ジャガー" },
            new AnimalFriend { Name = "トキ"},
            new AnimalFriend { Name = "ツチノコ"},
        };
    }
}

public class AnimalFriend
{
    public string Name { get; set; }
}

ContentPageの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"
             x:Class="XFApp44.BindablePickerPage">
    <ContentPage.Content>
        <StackLayout Padding="15, 40, 15, 20">
            <Picker HeightRequest="40"
                    Title="いちばんのともだちをおしえてね"
                    ItemsSource="{Binding Friends}"
                    ItemDisplayBinding="{Binding Name, StringFormat='{0}ちゃん'}"
                    SelectedItem="{Binding BestFriend, Mode=OneWayToSource}"/>
            <Label Text="{Binding BestFriend.Name, StringFormat='{0}ちゃんととっても仲良しなんだね、すっごーい!'}" />
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

ItemsSourceの各アイテムをPickerに表示する際にはPicker.ItemDisplayBindingを使って適切なプロパティPathを指定することが可能です。 Binding構文で指定するのでStringFormatも使えます。かしこーい。

f:id:ticktack623:20170207011818j:plain

【Xamarin.Forms 2.3.4-pre】新しいOnPlatformメカニズム

Xamarin.Forms 2.3.4-preで新しいOnPlatformの仕組みが導入され、従来の物は非推奨となります。

概要

  • 動作プラットフォームを文字列で判別するアプローチに変更(従来はenum)
  • Xamarin.Formsがサポートしていないプラットフォームへの対応が容易に(Tizenとか)
  • XAMLのOnPlatformで複数のプラットフォームに対してまとめて値を設定可能に
  • Device.OnPlatform(), Device.OnPlatform<T>() は実質廃止

RuntimePlatform

これまで、実行中のプラットフォームを取得するにはenum TargetPlatform型のDevice.OSプロパティを使用して来ました。

これを置き換える形でstring型のDevice.RuntimePlatformプロパティが新しく定義されます。

(Xamarin.Forms本体に定義されていない独自のプラットフォームを追加しやすくする意図があるみたい、Tizenとか?)

Xamarin.FormsがサポートするプラットフォームはDeviceクラスの定数として定義されます。

public const string iOS = "iOS";
public const string Android = "Android";
public const string WinPhone = "WinPhone";
public const string Windows = "Windows";
public const string macOS = "macOS";

つまり動作プラットフォームの判別は以下のように変わります。

// 従来のコード(obsolete)
Deviec.OS == TargetPlatform.Android

// これから推奨されるコード
Device.RuntimePlatform == Device.Android

Device.OnPlatformの廃止

Device.OnPlatform()、Device.OnPlatform<T>()がobsoleteとなり、代わりにswitch文とRuntimePlatformを組み合わせて使う事が推奨されます。

// 従来のコード(obsolete)
Device.OnPlatform(
    () => { /* iOS */ },
    () => { /* Android */ },
    () => { /* WinPhone */ },
    () => { /* Default */ });

label.Text = Device.OnPlatform<string>("iOS", "Android", "WinPhone");

// これから推奨されるコード
switch(Device.RuntimePlatform)
{
    case Device.iOS:
        /* iOS */ break;
    case Device.Android:
        /* Android */ break;
    case Device.WinPhone:
        /* WinPhone */ break;
    case Device.Windows:
        /* Windows */ break;
    case Device.macOS:
        /* macOS */ break;
    case "SomethingNewPlatform":
        /* 独自プラットフォームの場合の処理 */ break;
    default:
        /* default */
}

Xamarin.Formsがサポートしていないプラットフォームへも対応しやすくなっていますね。

OnPlatform<T>クラスの仕様変更

XAMLでプラットフォームごとの値を設定するのに使われていたOnPlatform<T>クラスの使い方が変わります。

これまでのOnPlatform<T>のプロパティとして各プラットフォームごとの値を設定していた代わりに、子要素として持たせる形になります。

<!-- 従来のコード(obsolete) -->
<OnPlatform x:TypeArguments="x:Double">
    <OnPlatform.iOS>20.0</OnPlatform.iOS>
    <OnPlatform.Android>42.0</OnPlatform.Android>
</OnPlatform>

<!-- これから推奨されるコード -->
<OnPlatform x:TypeArguments="x:Double">
    <On Platform="iOS">20.0</On>
    <!-- 複数プラットフォームを指定可能 -->
    <On Platform="Android, Windows">42.0</On>
</OnPlatform>

子要素となるOnクラスのOn.Platformにはカンマ区切りで同時に複数のプラットフォームを指定できます。ここに指定する名前はRuntimePlatformのに使われる文字列と対応します。

Xamarin.Forms.TabbedPageのiOS版でタブを上側に変更するサンプル

teratailで回答したやつ。

Xamarin - Xamarin.FormsのTabbedページのUIをiOSとAndroidで揃えたい(62739)|teratail

スクショ

f:id:ticktack623:20170121111728j:plain

f:id:ticktack623:20170121111738j:plain

stackoverflowの回答を参考にしています。

ios - Positioning UITabBar at the top - Stack Overflow

Xamarin.iOS側のプロジェクトに Xamarin.Forms.Platform.iOS.TabbedRenderer の派生クラスを作ります。

TabbedRendererはUITabBarControllerから派生しているので、ViewWillLayoutSubviews() をoverrideして処理を追加します。

using System;
using UIKit;
using Xamarin.Forms;

[assembly: ExportRenderer(typeof(TabbedPage), typeof(TopTabbarSample.iOS.TopTabbedRenderer))]

namespace TopTabbarSample.iOS
{
    /// <summary>
    /// iOSのTabbedPageのタブバーを上側に表示するためのRenderer
    /// </summary>
    public class TopTabbedRenderer : Xamarin.Forms.Platform.iOS.TabbedRenderer
    {
        // stackoverflowの回答を元にタブバーの位置を上に変更 http://stackoverflow.com/questions/29579992/positioning-uitabbar-at-the-top
        public override void ViewWillLayoutSubviews()
        {
            base.ViewWillLayoutSubviews();

            TabBar.InvalidateIntrinsicContentSize();

            var orientation = UIApplication.SharedApplication.StatusBarOrientation;

            nfloat tabSize = 44.0f;

            if(orientation == UIInterfaceOrientation.LandscapeLeft ||
               orientation == UIInterfaceOrientation.LandscapeRight)
            {
                tabSize = 32.0f;
            }

            var tabFrame = TabBar.Frame;
            tabFrame.Height = tabSize;
            tabFrame.Y = View.Frame.Y;
            TabBar.Frame = tabFrame;

            // 強制的にぼかしを再描画する小技らしい
            TabBar.Translucent = false;
            TabBar.Translucent = true;
        }
    }
}

動作確認用ページ

<?xml version="1.0" encoding="utf-8"?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
            xmlns:local="clr-namespace:TopTabbarSample"
            x:Class="TopTabbarSample.TopTabbarSamplePage">
    <TabbedPage.ItemsSource>
        <x:Array Type="{x:Type x:String}">
            <x:String>First</x:String>
            <x:String>Second</x:String>
            <x:String>Third</x:String>
        </x:Array>
    </TabbedPage.ItemsSource>
</TabbedPage>

GitHubにも置いておきます。

Xamarin.Androidでアプリのビルド時にJavaのヒープがあふれる時の対処

Xamarin.Androidでアプリのビルド時に...

java.lang.OutOfMemoryError. Consider increasing the value of $(JavaMaximumHeapSize). Java ran out of memory while executing 'java.exe -jar ~'

のようにJavaのヒープ領域があふれてエラーになったら

対処

Xamarin.Androidプロジェクトの設定からヒープ領域のサイズを変更できます。

Android Options > Advanced > Advanced Android Build Settings > Java Max Heap Size

f:id:ticktack623:20170113142622j:plain

取り合えず「1G」にして足りなかったら上げればOK(そこまで必要な場合あるのかな?)

【Xamarin.Forms 2.3.3】 Platform Specifics

Xamarin.Forms 2.3.3で Platform Specifics という機能が追加されました。

これはPCLなどの共通コードからプラットフォーム固有の機能を呼び出したりする類のものです。(ただしコードビハインド限定)

Forumでの紹介文によると...

Introducing Platform Specifics! Features or behaviors that apply to one platform only can now be implemented without requiring custom renderers. These new features/behaviors can then be accessed easily via a fluent code API or XAML.

Vendors can easily add their own Platform Specifics by attaching Effects to them (see 63a924d and 1f9482e for complete example).

This feature implements the framework that enables the new API and also includes several examples of platform specific features, which can be previewed using the Platform Specifics gallery page:

  • Blur support for any VisualElement on iOS
  • Translucent navigation bar on iOS
  • Partially collapsed navigation bar (with icons!) on MasterDetailPage on Windows
  • Toolbar placement options on Windows
  • AdjustResize/AdjustPan on Android (known issue: AdjustResize disables status bar color)

とのこと。

Custom RendererやEffectsを使ってやるようなことが簡単にできる風に書かれていますね。

サンプル

さっぱり分からないと思うのでサンプルコードを用意しました。

using System;
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;

namespace PratformSpecificsSample
{
    public class MyNaviPage : Xamarin.Forms.NavigationPage
    {
        public MyNaviPage()
        {
            BackgroundColor = Color.Pink;

            var button = new Button
            {
                Text = "Toggle Translucent",
                BackgroundColor = Color.Silver,
            };

            button.Clicked += (sender, args) => On<iOS>()
                .SetIsNavigationBarTranslucent(!On<iOS>().IsNavigationBarTranslucent());

            var content = new ContentPage
            {
                Title = "iOS Translucent Navigation Bar",
                Content = new StackLayout
                {
                    VerticalOptions = LayoutOptions.Center,
                    HorizontalOptions = LayoutOptions.Center,
                    Children = { button },
                }
            };

            PushAsync(content);
        }
    }
}

このコードを実行するとこんな感じになります。

f:id:ticktack623:20170113004240g:plain

ボタンをタップする度に、ナビゲーションバーの透過を切り替えるというiOS固有の効果が発動しています。

Platform Specifics が使われているのはこの部分、

button.Clicked += (sender, args) => On<iOS>()
    .SetIsNavigationBarTranslucent(!On<iOS>().IsNavigationBarTranslucent());

やっていることは On<iOS>().SetIsNavigationBarTranslucent() が透過ON/OFFのセット、 On<iOS>().IsNavigationBarTranslucent() が現在の透過状態の取得ですね。

Element.On<T>() を入り口として、「対象Elementとプラットフォームの組み合わせという文脈に固有な機能をPCLから呼び出せる」というのが Platform Specifics の意義といえるでしょう。

BoxViewの例

例えば、BoxViewに続けて .On<iOS>(). と入力するとインテリセンスから GetBlurEffect()UseBlurEffect() が呼び出せて...

f:id:ticktack623:20170113004413j:plain

.On<Android>(). には呼び出せるものが無い、といった塩梅です。

f:id:ticktack623:20170113004436j:plain

使い方まとめ

1. usingを追加

using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
using Xamarin.Forms.PlatformConfiguration.WindowsSpecific;

2. だいたいクラス名衝突が発生するので修正する

これはPlatform Specificsの実装が悪い。

なぜNavigationPageに対する拡張メソッドを定義するクラスを「NavigationPage」にしてしまうのか……

3. Element(ApplicationとかPageとかViewのこと)の .On<TIConfigPlatform>() を呼ぶ

Xamarin.Forms 2.3.3時点でTIConfigPlatformに入れられるのは以下の3種類。(GitHubのコード上ではTizenも追加されているので2.3.4には入ってくるかも)

public sealed class Android : IConfigPlatform { }
public sealed class iOS : IConfigPlatform { }
public sealed class Windows : IConfigPlatform { }

4. インテリセンスで使いたい機能を選ぶ

TElement.On<TIConfigPlatform>() の戻り値は IPlatformElementConfiguration<TIConfigPlatform, TElement> のインスタンスとなっており、プラットフォーム固有機能は拡張メソッドで実装されています。つまり後付けなので自分で実装するのもアリ。

ライブラリ開発者向け情報

Xamarin.Formsのカスタムコントロールなどを配布する場合、IElementConfiguration<TElement>を実装してPlatform Specificsのエントリポイント用意してあげるといいかもしれない。

(プラットフォーム固有機能は拡張メソッドで生やせるけども、エントリポイントはコントロールで実装しなければならないため)