【Swift】AutoLayoutのPriorityを使ってアニメーション風のView制御をする


AutoLayoutに関する記事は星の数ほどあるので今更感がありますが、自らの確認も兼ねて投稿します。

下準備


画面の上下に適当な大きさのUIViewを2つ配置し、下側のViewに高さの制約をつけてOutlet接続しました。
@IBOutlet private var bottomViewHeightConstraint: NSLayoutConstraint!

touchesMoved(_:with:)をオーバーライドして、多少の処理を追加します。
処理内容は、画面上のタッチイベントのY座標の変化量に合わせて画面下部のViewの高さの制約を変えています。
これで画面上で指を動かすと、動きに合わせて下側のViewが伸び縮みするようになりました。
本記事の趣旨ではないので特に深堀りはしません。

Viewに追従するImageView

下部のViewに合わせて動くImageViewを配置します。
GoogleMapアプリで使われているフローティングアクションボタンのようなものです。

配置した結果がこのような感じ。
フローティングボタンにしては主張が激しい気もしますが、今の所無視します。
画像素材はいらすとや様から拝借しました。

ImageViewの制約はX座標が画面中央、またImageViewBottomが下部のViewTopから10ptの位置となっています。
この10ptの制約のおかげで、下部のViewが伸び縮みした際に追従するようになります。
ただし、このままではImageViewの高さや上部に対してレイアウト上の制約が存在しないため、上部のViewをImageViewが突き抜けてしまいます。

View同士の重なり順を変更して、ImageViewが上部のViewの下に隠れるようにしてもいいですが、AutoLayoutで解決することが目的なので実装を進めます。

Priorityを使ったImageView制御

ImageViewの上端が上部のViewの下端に達した際の動作を2パターン作ってみます。

1.ImageViewが縮小する

ImageViewに対して制約を追加します。

制約の内容は、上部のViewBottomからImageViewTopの距離が0pt以上としました。
-の値を許容しないのでImageViewが上部のViewまで突き抜けることはなくなります。
また、ImageViewに対して高さの制約を設けていないため、実行するとImageViewが拡縮します。

ただし、このままでは縦方向の制約2種が衝突してしまうという問題があるので次のパターンで同時に解説します。

2.ImageViewが上端に留まる

パターン1ではImageViewに高さの制約がなかったため拡縮しました。次は、ImageViewのサイズを固定してその上にViewが重なるように作ってみます。

AutoLayoutの制約には各々にPriority(優先度)という設定項目があり、制約同士が衝突した場合にPriorityの高い順に優先して処理されます。
パターン2を実現するために、以下のような優先順位をつけました。

①.ImageViewの高さ(ImageViewのサイズを固定することを最優先とする)
②.ImageViewと上部Viewの距離(ImageViewが上部Viewまで突き抜けないことを次に優先する)
③.ImageViewと下部Viewの距離(上記2つの制約が満たされている場合動作する)

Priorityのつけ方は何通りかありますが、インスペクタで制約を表示させて右端のEditボタンから編集するのが簡単かと思います。

②のPriorityを999、③のPriorityを998に設定しました。

また①に関してはContent Compression Resistance Priorityで設定してみます。

Content Compression Resistance Priority

これはUILabelやUIImage等、何らかのコンテンツを内包するViewが持つPriorityです。
本記事中、Viewに追従するImageViewの項で、

ImageViewの制約はX座標が画面中央、またImageViewBottomが下部のViewTopから10ptの位置となっています。

と書きましたが、これが例えばImageViewでなくUIViewだと、ストーリーボード上で警告が表示されます。
この問題はUIViewのサイズをAutoLayoutで決定できないために引き起こされます。
一方、UILabelやUIImageViewでは表示するテキストや画像によって自動的に固有サイズが割り当てられるので、明示的にサイズを指定せずともAutoLayoutが動作するようになっています。

んで、Content Compression Resistance Priorityって何? といわれると、内包するコンテンツの小さくなりにくさ、潰れにくさ、とでもいいましょうか。この値が高ければ高いほど、もともとのコンテンツサイズを維持しようとします。
ちなみにDefaultではPriorityが750で設定されています。

話を戻して、①のImageViewの高さを制約上の最優先事項としましたので、Content Compression Resistance Priorityを1000に設定します。

これで以下のように設定できたので実行してみます。
①.ImageViewの高さ(Priority@1000)
②.ImageViewと上部Viewの距離(Priority@999)
③.ImageViewと下部Viewの距離(Priority@998)

ImageViewのサイズが固定され、更に上部のViewに突き抜ける事もなくなりました。

終わりに

今回はImageViewが縮小する、ImageViewが上端に留まるという2パターンを実装してみました。
同じ要領で一つ一つ制約を重ねていけばAutoLayoutのみで記事冒頭のgifのような動きが表現できます。
また、説明するためにPriorityに優先順位をつけましたが、制約同士が衝突しない場合は当然ながらPriorityを考慮する必要はありません。

参考

AppleのAutoLayoutGuidです。ちょい古ですが
Documentation Archive