soranoba
soranoba Author of soranoba.net
programming

UIKitのisHiddenを二値だと信じてはいけない

先週、なぜかコード的にはおかしくないはずなのに、Viewの表示状態が壊れるという現象に遭遇しました。
どうも調べてみると、isHiddenで代入しても値が反映されないようでした。

(lldb) po centerViewInStackView
▿ Optional<UIView>
  - some : <UIView: 0x103556cf0; frame = (115.333 0; 0 48); hidden = YES; autoresize = RM+BM; layer = <CALayer: 0x28189a0c0>>
(lldb) po centerViewInStackView.isHidden = false
(lldb) po centerViewInStackView
▿ Optional<UIView>
  - some : <UIView: 0x103556cf0; frame = (115.333 0; 0 48); hidden = YES; autoresize = RM+BM; layer = <CALayer: 0x28189a0c0>>

もちろん、MainThreadであることは確認しています。
調べてみると、stack overflowでisHiddenは累積するという指摘がいくつか見つかったので、以下のコードで処理するようにしたら、確かにこの現象は治りました。

if centerViewInStackView.isHidden != newValue {
  centerViewInStackView.isHidden = newValue
}

いやいやいやいや。そんな馬鹿な。
この現象は再現性があるものの(プロダクトコードで再現手順が確立されている)、どうも他のViewで似たようなことを行っても発生しない。ということで、stack overflowの情報を元に再現を試みてみることにしました。

再現方法

結論から言うと、分かりやすい再現はできませんでしたが、一応の再現コードは作れました。

  1. https://github.com/soranoba/uikit-cumulative-hidden をクローンして適当なシュミレータ、もしくは実機で起動する。
  2. この行にbreak pointを設定する
  3. 右90度 -> 左90度で回転させた後、UISegmentedControlの真ん中 (Second) を選択する
  4. 設定したbreak pointで止まる

何回か見た目が壊れる(2色ではなく1色の表示になる)ケースも確認しているので、ここからもう少しこねくり回せば、いい感じの再現コードになりそうです。
そして、どうも以下の3つを満たす場合に発生する節があります。

  • isHiddenを操作する対象のViewが、StackViewの中にあること
  • アニメーション中にisHiddenの操作を行うこと
  • 現状の値と同じ値を設定すること

Objective-Cではpropertyへのアクセスにgetterとsetterを経由する都合上、そこでどんな処理をされているか分からないので、値の設定が反映されないということはあり得ますが、なんとも不思議なものです。
このコードでは確認できませんでしたがプロダクトコードの方では3回trueを設定すれば3回falseを設定しないと戻らなかったので、累積値になる場合があることも確かなようです。

アニメーション中にisHiddenを操作しなければ良いとはいえ、Portrait or Landscapeともう一つの状態によって表示/非表示の操作するケースは割とよくあるので何とも困ったものです。

参考文献

(Updated: )

comments powered by Disqus