Edited at

[iOS]UIStackViewのアニメーションが変!


世間話

今更UIStackViewと戯れているんですが、ちょいちょいわけわからん動きをしますね。

UITableViewと格闘して大嫌いになった人達がUIStackViewを絶賛していましたが、彼らは大丈夫だったんでしょうか。こっちも結構曲者ですよ・・・


Hide/Showアニメーションが変

UIStackViewの良いところは、なんと言ってもisHiddenでViewの表示/非表示が簡単にできるところですよね。

iOSでもAndroidのgoneが使いたいんだ!と何度思ったことが。

ですが、表示/非表示のAnimationをしようとしたところ、変な動きになりました。

UIStackView1.gif

// コードのイメージ

UIView.animate(withDuration: 0.3) {
self.stackView.arrangedSubviews[index].isHidden.toggle()
self.stackView.layoutIfNeeded()
}

一番上と一番下、お前らなんなん!?

ここらへん、ググっても全然出てきませんでした。

ちなみに条件は


  • Alignment Fill

  • Distribution Fill

  • Spacing 0

  • 各Viewにheightのconstraint


ああ、え、そういうこと?

半透明にしたらわかりました。

ezgif-3-d65353ce2ed2.gif

例えば3番目のViewをhiddenにしようとすると、4番目以降のViewが上にせり上がってきて、4番目のViewが3番目のViewに達したあとで3番目のViewが見えなくなります。

何で?(殺意)

これで最下部のViewの動きに合点がいきます。

最上部のViewの動きは何かルールが違っている気がしますが。。


意図した動きにするにはどうすればいいか?

この動きは非常に厄介です。

例えばViewが2つのStackViewを作って、上部をタイトル情報、下部に詳細情報にして、タップしたら開くとか、そういう基本的な動きすらアニメーションができなくなります。


そうだ、ClipToBoundsだ!

天啓を得てUIStackViewのClipToBoundsをtrueにしてみました。

何の成果も得られませんでした。

何で?(殺意)


解1:親Viewを作って、親ViewをClipToBoundsにする

上下左右をAutoLayoutでつないでやります。

これでいけました。

ezgif-4-1e69df04900b.gif


解2:高さのconstraintのPriorityを999にする

ちょっと隠れ方の挙動が違いますがこれでもいけました。

何で?(殺意)

ezgif-4-113a9e3ef94a.gif

すいませんが、これまだ理解できていません。

1000ならダメで、999ならいけるんです。


別の条件でも試してみる


DistributionをFillEqualyにしてみる①

UIStackView自体の高さを縛りました。

999ezgif-3-dc2421260fbe.gif

予想通りですが若干キモいですね、使い所が限られそう?


DistributionをFillEqualyにしてみる②

今度は一番上のViewの高さを縛ってみました

101010ezgif-3-278171c7c1da.gif

良さそうな動きをしますが、基準となる一番上のViewをhiddenにしたところ壊れてしまいました。

どのように高さを設定するか考える必要が出てきそうです。

確実に表示するViewがあればいいんですが。


Spacingがついていたらどうなる?①

Spacingをつけて、各Viewの高さを縛りました。

121212ezgif-3-8f15c3372e53.gif

ああっ!そうなっちゃいますよね。

これはいけません。


Spacingがついていたらどうなる?②

UIStackViewの高さを縛ってみました。

131313ezgif-3-af1f5d76f701.gif

これはちょっと使えそうですね。


Spacingがついていたらどうなる?③

動いてから消えるのが良くないので、消してから動かしてみましょう。

isHiddenは使えないので透明度をいじっています。

141414ezgif-3-80d352b25833.gif

手品っぽくなりました。これはギリギリよさそうです。


UITableViewと連携する

やっぱり同じセルが増えてくるとTableViewに頼らざるを得なくなってきます。

UITableViewCellにUIStackViewを入れて、アニメーションしてみましょう。

AutoLayoutは上下左右をひっつけます。

444ezgif-3-786286a5c695.gif

何で?(殺意)

ちなみにアニメーションは

tableView.beginUpdates()

tableView.endUpdate()

こうか

UIView.animate(withDuration: 1.0) {

tableView.beginUpdates()
tableView.endUpdate()
}

こうです

さっきは、動いてから消えましたよね。

何で今回は消えてから動くんですか????

5分ぐらい考えてみましたがわかりません。保留です。


self sizingを諦める

しょうがないので、UITableView.automaticDimensionを諦めてみます。

UIStackViewとCellを、上左右のみ設定して、UIStackViewの高さのコントロールは上でやったとおりにします。

そしてCellの開閉アニメーションはオーソドックスにheightを変更して行います。

555ezgif-3-ad01298fcf3c.gif

できました。

しかしself sizing好きなので、これには私も憤慨です。

特に今の高さではなくて、1個のViewをhidden/showしたあとの高さを求めるのがだるくてしょうがないです。

一応作りましたけど。スマートではないですね。

func expectedHeight(when viewOfIndex:Int, isHidden:Bool) -> CGFloat {

let height = arrangedSubviews.filter { !$0.isHidden }.reduce(0, { $0 + $1.frame.height })
if isHidden && !arrangedSubviews[viewOfIndex].isHidden {
return height - arrangedSubviews[viewOfIndex].frame.height
}
if !isHidden && arrangedSubviews[viewOfIndex].isHidden {
return height + arrangedSubviews[viewOfIndex].frame.height
}
return height
}


おわりに

内部でAutoLayoutがどうなってるのか見えにくいので、わからないことが多すぎます。

一個一個知識や経験として仲良くなっていくしか無い気がします。

闇が深い・・・

間違いや備考などあったら後ほど追加します。


次回

UIStackViewってちょいちょい言うこと聞かないですよね。

それで「私の本当に欲しかったStackView」を作ったのでその話をします。

よりリッチなものは既にライブラリが存在します。すばらしいです、よく作りますねこんなの。

ただちょっとヘビー?

StackViewController

https://github.com/seedco/StackViewController

AloeStackView

https://github.com/airbnb/AloeStackView