iOS11のツールバーAutoLayoutについて

どうも、ご無沙汰してます
iOS11対応どうですか、疲れますよね。

今回は対応中出会ったツールバーのレイアウト崩れに関するtipsです。
NDAがうんたらかんたらでほんとはスクショ張って説明したいんですがどこまでがセーフでアウトかわからないんでとりあえず
テキストで説明しようと思います。

#そもそもツールバーのAutoLayoutってなんや
https://developer.apple.com/videos/play/wwdc2017/204/
このビデオのだいたい30分くらいのところでさらっと「ナビゲーション周りAutoLayou対応しといたで」って言ってます(超意訳
だからなんや、とかどうしたこうしたとかは特に触れられてません(英語わかる人にはわかるのかも)

#つまりどうなる
表示崩れしました。
スクショ貼ることができないのですごい説明がわかりにくくて申し訳ないんですが
僕が出会ったのは画面幅を６等分した幅のボタンをツールバーに置いていた場合表示崩れを起こしました。
もしくは画面遷移時にフリーズを起こしました。

#原因
言わずもがなAutoLayoutです。
ツールバーのAutoLayoutですが表示中のView Hierarchyを見るとツールバーのviewの上にStackViewの拡張クラスがいることがわかります(非公開クラス)
UIToolber > _UIToolbarContentView > _UIButtonBarStackView
てな具合です。
UIBarButtonItem(customView:)でviewを配置するとこのStackViewにview配置されていくことになります。

#なぜ表示が崩れるのかその１
みなさん、storyboardやxibでオブジェクトを配置したことがある人はわかると思いますがデフォで左右に16pxのマージンが入る制約がついてます
謎の16pxについてはたくさんの先人がすでに考察していますので読んでみてください
例： http://qiita.com/yimajo/items/10f16629200f1beb7852

さて、つまりどういうことかというとこれまで画面幅を表示するボタンの数で割ってツールバーのボタンの幅を決めてcustomviewを作り
UIBarbuttonItemU(customView:)してた場合
この左右の16px、つまり32px分のズレが生じるわけです。
toolbarはAutoLayoutによってボタンをうまいこと表示しようとするので表示するどれかのボタンがしわ寄せで小さくなったり
最悪、描画のタイミングでメモリを食いまくってフリーズに陥ります。

なのでiOS11でボタンを均等な幅でツールバーに敷き詰めるときは36px分を画面幅に対して考慮する必要があります。
一応制約なのでtoolbarからsubviewsを漁りまくってStackViewを見つけて、constraintを弄れば16pxを0pxにして回避はできそうですが非公開クラスなのであまりアクロバティックなことをするのはやめたほうがいいと思います。

#なぜ表示が崩れるのかその２
その１の表示崩れが治ったと思ったらつかの間、また表示ズレが起きました。
ツールバーを表示するたび(ツールバーのボタンを敷き詰めない画面とない画面を行ったり来たり)マージンが変わってしまっていました。
なぜだろうと思い調査しました。

表示崩れを起こしていた部分は画面の左端と右端にボタンがあり真ん中に空白があるレイアウトでした
<img width="749" alt="スクリーンショット 2017-09-15 1.49.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/162318/5cdf183e-2cd1-a24f-6c91-b3eaab479101.png">
これを実現するために僕が業務で扱っているアプリでは等分した透明のcustomViewを貼り付けたUIBarButtonItemでその間を埋めるという手法をとってました
<img width="739" alt="スクリーンショット 2017-09-15 1.53.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/162318/3595c8e2-5927-d373-c7e7-fe3a8a08fd57.png">
こんな感じ(幅が揃ってなくてガバガバですが脳内補完してください:bow:)
結論からいうとこれがいけなかったです。

##UIButtonBarStackViewの設定
非公開だろうが丸裸にしました
設定ですが
axis: horizontal
alignment: center
distribution: fill
でした

このうちdistributinの情報が重要でした
http://qiita.com/yucovin/items/ff58fcbd60ca81de77cb

StackViewのdistributionがfillとはどういう状態かというと
StackView内のViewをStackViewいっぱいまで引き伸ばして表示する設定です。

##StackViewと制約の競合
なので幅を固定にしたViewをtoolbar常に置いた場合(というかそうするしかない)
StackViewの引き伸ばそうとする制約と競合して表示崩れを起こします。ぱっと見で起きていなくてもView Hierarchyを見ると!マークの警告が出てるかもしれません。

##どう解決するか
UIBarButtonItem(barButtonSystemItem: .flexibleSpace,~~)を使います
こいつはツールバーに柔軟な余白を設けるために使われるものです(いい説明が思いつかん)
言われてみればそりゃそうかとなるんですが固定幅でうまく行かないんなら
可変幅で埋めよう！ってことですね。

試してみたのですが例えばツールバーの幅を全然埋め切らない幅のボタンを2つsetToolbarした場合：[button,button]
ボタンは画面の両サイドに設置されました。
この場合警告は出ません。

次に同じようにツールバーの画面幅を埋め切らない幅のボタン３つをsetToolbarした場合：[button,button,button]
以下の絵のような配置になりました
<img width="738" alt="スクリーンショット 2017-09-15 2.08.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/162318/079097c1-6c9c-df58-acb3-bf2fabdd43ea.png">
StackViewが頭の悪い配置をしてくれたわけです。
さあこのボタンを左側2つ、右側1つにする場合隙間に固定値透明なviewを置くと警告表示崩れしますのでflexibleSpaceのbuttomItemを
[button,button,flexible,button]でならべてsetToolbarItmesすると

<img width="737" alt="スクリーンショット 2017-09-15 2.11.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/162318/50161f05-9ff1-253e-6842-654871fd07d5.png">

こんな感じになってくれて警告もなく表示してくれます。
正しいスペースの開け方のようです。

#これで終わりじゃないぞ
警告も消えたし表示崩れもなくなったしよかったよかったと思ったら終わりじゃないです。
画面遷移前にviewcontrollerのnavigationbarにsetToolbar、などアニメーションが絡むタイミングでsetToolbarすると起きますが(もしかしたら他でも起きるかも)
ボタンの個数が変わる場合、autolayoutの調整が走る時にアニメーションとなって目に見えてしまうことがあります。
なのでsetToolBarItemをした後に場合によっては
viewController.navigationController.toolBar.layoutIfNeeded()
をしてAutoLayoutの反映をさっさとしてしまったほうがいいかと思います。

#あとがき
そもそも実装がよろしくなかった疑惑がありますがハマりやすい気もするのでQiitaに残しました
なんかフォーラムだといろいろ語られてるっぽいので同じようなところでハマってる人もいるのかも
https://forums.developer.apple.com/thread/80075
iOS11が終わったと思ったら次はiPhoneX対応かなぁ・・・
とりあえずもうすぐ行われるLINE DEVELOPER DAY 楽しんでこようと思います

ではでは・・・またネタがあれば