Xamarin
Xamarin.Forms

[Xamarin.Forms]ListView・TableViewのセル高さの更新(iOS)

はじめに

この記事は [初心者さん・学生さん大歓迎!] Xamarin その1 Advent Calendar 2017 の18日目の記事です。

Xamarin.FormsのiOSでListViewやTableViewは1.3.4からセルの高さを自動調節してくれるようになりました(だいぶ前の話)。ですが、BindingしているItemの値がかわったときなどに、セルの高さが動的に更新されてくれません(Androidでは勝手にセル高さが変わってくれます)。

ListViewでよく出てくるパターンなのにベストプラクティスがよくわからなかったのでまとめてみました。

ForceUpdateSize()を呼ぶ

Xamarinの公式sampleを見るとセルの高さを計算し、HeightRequestを設定してForceUpdateSize()を呼んでいます。

image.HeightRequest = image.Height + 100;
viewCell.ForceUpdateSize ();

Forms側だけで書けるので、シンプルです。

ただし、ForceUpdateSize()がTableViewでは効かなかったり、セル内にカスタムレンダラやEffectでちょっと複雑なことをしていたときに不安定なことがあります。これでつまずいていたときに他の方法を教えてもらいました(参考)。↓

iOSのカスタムレンダラーでReloadData()を呼ぶ

セルの高さがかわる瞬間にiOSのカスタムレンダラーでReloadData()を呼んでやるとリフレッシュされます。

まずはListViewを継承したクラスを作り、Rendererへの橋渡しの準備をします。

    public class ResizableListView : ListView
    {
        public event EventHandler<EventArgs> DataChanged;

        public void OnDataChanged()
        {
            var handler = this.DataChanged;
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }
    }

次に先ほど作ったDataChangedイベントをカスタムレンダラーでハンドルしてReloadData()を呼んでやります。

public class ResizableListViewRenderer : ListViewRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
    {
        base.OnElementChanged(e);
        if (e.OldElement == null)
        {
            ((ResizableListView)e.NewElement).DataChanged += (object sender, EventArgs args) => { Control.ReloadData(); };
        }
    }
}

あとは、Formsのセル高さを変更したいタイミングで、作ったListViewのカスタムクラスのOnDataChanged()メソッドを呼び出してやるだけです。

TableViewでも同じことができます。TableViewの継承クラスを作り、TableViewRendererを継承したカスタムレンダラーでReloadData()を呼びだすだけですので全く同じです。

まとめ

Forms側だけで簡潔に書けるので、ForceUpdateSize()で試してみて、うまくいかなかったらカスタムレンダラーのReloadData()を呼ぶようにしてみたらよいかと思います。他にもいいやり方があればコメント欄にて教えてください。