モノトーンの伝説日記

Apex Legends, Splatoon, Programming, and so on...

AutoLayout で Disclosure Indicator を日付などの時刻表示の中心に配置するようにする

 iOS & UWP 開発をしているモノトーンです。

 Xamarin.iOS with C# での記事です。Objective-C や Swift の場合は読み替えてください。

メッセージアプリやメールアプリのように,Disclosure Indicator を上に寄せる

 f:id:mntone:20180509114334p:plain:w375

 メールアプリやメッセージアプリでは,このように Disclosure Indicator が上方向に寄せられています。これを AutoLayout でたとえ dynamic type で文字サイズが変わったとしてもある要素の中心に貼り付けます。

Disclosure Indicator は Frame による絶対指定でレイアウトされている

 Disclosure Indicator は UIView.Frame で直接位置が指定されているので,そのまま AutoLayout を反映してしまうとその座標に引っ張られてしまうため,その座標を無効にします。

_accessoryButton.TranslatesAutoresizingMaskIntoConstraints = false;

 あとは AutoLayout で配置するだけです。サンプルは,相対時間のラベルの中心 Y座標に固定,相対時間のラベルの右側から +8pt の位置に固定,するです。

 以下のコードは MITライセンス にて使用してください。C# でのヘルパークラス例です。

using System;
using System.Linq;
using UIKit;

namespace Mntone.SampleCode.Controllers.Infrastructures
{
    public sealed class TopDisclosureIndicatorHelper : IDisposable
    {
        private NSLayoutConstraint _centerYConstraint, _rightConstraint;

        public TopDisclosureIndicatorHelper() { }

        public void Dispose()
        {
            if (_centerYConstraint != null)
            {
                _centerYConstraint.Active = false;
                _centerYConstraint.Dispose();
                _centerYConstraint = null;
            }
            if (_rightConstraint != null)
            {
                _rightConstraint.Active = false;
                _rightConstraint.Dispose();
                _rightConstraint = null;
            }
            GC.SuppressFinalize(this);
        }

        public void UpdateConstraints(UIView[] subviews, UIView targetObject)
        {
            var accessoryButton = subviews.OfType<UIButton>().FirstOrDefault();
            if (accessoryButton != null && targetObject != null)
            {
                if (_centerYConstraint != null)
                {
                    targetObject.RemoveConstraint(_centerYConstraint);
                    _centerYConstraint.Active = false;
                    _centerYConstraint.Dispose();
                    _centerYConstraint = null;
                }
                if (_rightConstraint != null)
                {
                    targetObject.RemoveConstraint(_rightConstraint);
                    _rightConstraint.Active = false;
                    _rightConstraint.Dispose();
                    _rightConstraint = null;
                }

                accessoryButton.TranslatesAutoresizingMaskIntoConstraints = false;

                _centerYConstraint = targetObject.CenterYAnchor.ConstraintEqualTo(accessoryButton.CenterYAnchor);
                _centerYConstraint.Active = true;

                _rightConstraint = targetObject.RightAnchor.ConstraintEqualTo(accessoryButton.LeftAnchor, -8.0F);
                _rightConstraint.Active = true;
            }
        }
    }
}

 使用例

using Foundation;
using Mntone.SampleCode.Controllers.Infrastructures;
using ReactiveUI;
using System;
using System.Reactive.Disposables;
using UIKit;

namespace Mntone.SampleCode.Controllers.Components
{
    public partial class TestTableViewCell : ReactiveTableViewCell<TestViewModel>
    {
        public static readonly NSString Key = new NSString(nameof(TestTableViewCell));

        private readonly CompositeDisposable _disposables = new CompositeDisposable();

        private TopDisclosureIndicatorHelper _helper;

        public TestTableViewCell(IntPtr handle) : base(handle) { }

        public override void AwakeFromNib()
        {
            base.AwakeFromNib();

            _helper = new TopDisclosureIndicatorHelper().AddTo(_yourDisposables);

            SetNeedsUpdateConstraints();
        }

        public override void UpdateConstraints()
        {
            _helper.UpdateConstraints(Subviews, timeLabel);

            base.UpdateConstraints();
        }
    }
}

追記: 2018年5月25日,Constraints 更新処理に記述するように変更しました。SplitViewController 利用時,画面回転にて表示非表示した際に Accessory Button のオブジェクトは毎回作り直されるので,毎回 Accessory Button を探索するように変更し,また制約の解除も適切に行うようにしました。