Visual Format Languageを使ったプログラムによるAuto Layoutについて前回説明しました。

プログラムによるAuto Layout
しかし、自分はVisual Format Languageは使わない方向でいきたいと思います。またInterface Builderを嫌って、すべてプログラムによるAuto Layoutで実装することもおすすめできません。その理由を以下に述べます。

2014/3/21更新。Xcode 5以前はInterface BuilderでのAuto LayoutのConstraint設定で不便なことが多く、保守性を考えるとAuto Layoutが使える範囲は限定的ではないかという見方で記事を書いていました。Xcode 5からはInterface Builderも洗練されたので、できるだけInterface Builder + Auto Layoutでレイアウトするべきだと思っています。よって、それに合わせて内容を書き換えました。

リファクタリングに弱い

Visual Format Languageでは、Subviewの変数名を文字列で指定します。変数名が変わってしまったらVisual Format Languageに埋め込まれた変数名(文字列)も合わせて修正する必要があります。もしずれていてもコンパイラではSyntax Errorとして検出できません。個人的にはこれが一番痛いと思うところです。

Objective-Cのようなコンパイラ言語のメリットって、コンパイル時にタイポなどをSyntax Errorとして検出できるところなんですよね。スクリプト言語のような弱さを持ち込みたいくないのです・・・

読解しづらい

前回示したサンプルのようなレベルなら、あとで見返すなり第三者が見るなりしても理解できると思います。ただ、もっと複雑になったときはどうでしょう?読解はかなり困難なように思えます。慣れの問題もあると思いますが、それでも複雑になればなるほど、上記のリファクタリングに弱い問題も大きくなりそうです。

動的なViewの追加に弱い

Auto Layoutは動的なViewの追加配置に弱いということも覚えておくべきです。

前回のサンプルを起動して、With Xibを選択しましょう。

初期状態はxib上で設定したConstraintに従って配置されています。Button1を押すと、Button2の下にButton3が動的に追加されます。これはプログラムでButton3のConstraintを追加して配置・表示しています。

//
// button3
//
constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-[_button3]-|"
                                                      options:0
                                                      metrics:nil
                                                        views:viewsDictionary];
[self.view addConstraints:constraints];

constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[_button2]-[_button3]"
                                                      options:0
                                                      metrics:nil
                                                        views:viewsDictionary];
[self.view addConstraints:constraints];
_button3.hidden = NO;

このように一番下に追加する場合は、既存のSubviewのレイアウトはそのままです。よって新たなConstraintを追加するだけなので、大きな問題はありません。

しかし、もしButton3をButton1とButton2の間に表示するような場合はどうでしょうか?

  • Button1とButton2の間隔を保つためのConstraintを削除
  • Button1とButton3、Button3とButton2のConstraintを追加

といった処理になると思います。

このConstraintの削除というのが面倒です。削除するためには削除するConstraintを特定しなければなりません。UIView#constraintsですべてのConstraintを得ることはできますが、その中のどれが削除すべきConstraintなのかどう判定すればよいでしょう。自分には良い案が浮かびませんでした。NSLayoutConstraintにはnameやtagといったプロパティもありません。

こういう時の対処は以下のようにする方が良いでしょう。

  • すべてのViewをInterface Builderで配置しておく
  • 表示しないときはhidden = YESにする
  • 非表示になったら非表示のViewの上下の間隔のConstraintの値を0にして無駄な間隔が開かないようにする

複雑な配置はframe直接設定で対応することも考える

複雑な表示になるとhiddenや間隔のConstraintを調整では対応しきりない、もしくは複雑になることもあるでしょう。こういう場合はやはりframeを直接設定したほうが良い場合もあります。

iPhone 5を皮切りにこれからは解像度が異なるものも対応しなければならない、だからAuto Layoutは必須。といった認識は正しいとは思えません。なぜなら、frame直接設定、Autoresizing Maskでも異なる解像度の対応はできるからです。そもそも以前から画面を回転に対応させるアプリは、Subviewはそれに合わせたサイズ変更、再配置が必要でした。そういうアプリを開発してきた人は、Autoresizing Maskとframe設定で対応してきたわけです。

複雑なSubview配置に関しては、frame設定で対応した方が、効率的で保守性が高い場合もあると思います。その辺りのノウハウは、以下にまとめてあるので参考になればと思います。

プログラムによるSubview配置

1つのViewの上でAuto Layoutとframe直接設定を混合すると意図した動きにならないことも多いです。よってframe直接設定する場合は、

  • 複雑な部分だけCustomViewとしてクラス化する
  • CustomViewでlayoutSubviewsでframe直接設定してレイアウトする
  • 場合によってはintrinsicContentSizeを実装
  • CustomViewの配置はSuperview上でAuto Layoutで配置

のようにすると、Auto Layoutと喧嘩せずにレイアウトできると思います。