anz blog

viewForHeaderInSectionがめっちゃ呼ばれる

2019-04-23 #Swift

ざっくりまとめ。

  • UITableView で style を grouped にする
  • そして header を非表示にするべく
    • height は .leastNormalMagnitude を返す
    • view は nil を返す
  • そうするとスクロールするたびに viewForHeaderInSection が呼ばれ続ける
  • そして Storyboard で実装した場合とコードだけで実装した場合で挙動が変わる
    • 意味がわからない

です。

環境

  • Xcode v10.2.1
  • Swift v5.0

問題

Storyboard 上で UITableView を貼り付けて画面いっぱいになるように制約をつける
UITableView.style = .grouped にスタイル設定をしている状態にする
tableView(_:heightForHeaderInSection:)CGFloat.leastNormalMagnitude を返しつつ、
tableView(_:viewForHeaderInSection:) では nil を返す。

こういう状態にしておくと、スクロールするたびに tableView(_:viewForHeaderInSection:) が呼ばれるということに。

func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    print("heightForHeaderInSection: \(section)")
    return .leastNormalMagnitude
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    print("viewForHeaderInSection: \(section)")
    return nil
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
    print("scrollViewDidScroll")
}

こういうことをしておいてログを出すと、

viewForHeaderInSection: 0
scrollViewDidScroll
viewForHeaderInSection: 0
scrollViewDidScroll
viewForHeaderInSection: 0
scrollViewDidScroll
viewForHeaderInSection: 0
scrollViewDidScroll
viewForHeaderInSection: 0
scrollViewDidScroll

という感じで出力される。
スクロールしていって、ヘッダーが完全に画面領域外になると一応 viewForHeaderInSection の方は止まるのだけれど、
画面領域内に居るであろう(もちろん肉眼では見えない)場所があるうちはまた同じ様に出続ける。

上記の例では nil を返しているだけなので別に構わないと言えば構わないのだけれど、
section:0 のときはなにかだして section:1 のときは出さないとかという分岐があると
呼ばれるだけその分岐処理が行われるので nil を返しているとはいってもやはり気分の良いものではない。

回避策

その1

nil を返すのではなくなんでも良いから View を返すとスクロールされるたびに呼ばれるということはなくなる。
nil で気軽にできていたものが、UIView() なり dequeueReusableHeaderFooterView を利用するなりで少しだけ手間だけれど。
とはいえ、もっとも妥当な回避策。

その2

style = .plain にする。
これは、意味があって .gouped にしていると思うので現実的ではないと思う(笑)

解せない話

上記までで一応問題の回避はできるし、なんとなく呼ばれるものかもな..と思ったりもするのだけれど、
ここで1つ解せない挙動の違いというものあるのです。

上記の例で「Storyboard 上で UITableView を貼り付けて」とわざわざ書いているのだけれど、
これを Storyboard を使わずにコード上で UITableView を生成して addSubview などセットアップをすると...
この「高さを leastNormalMagnitude して view で nil を返すと tableView(_:viewForHeaderInSection:) が呼ばれ続ける問題」が発生しなくなるのです。
本当に、変更箇所はコードで生成するようにするだけです。それだけで発生しなくなる。

これ...なんですか??🤔

height を leastNormalMagnitude という限りなく小さい値にしても、そこにViewが居ることに変わりはないだろうから、
nil を返されると何かが生成されるまでは呼ばれるづける...
というのはなんとなくわかるのです、理解できるのです。
なので、なにか View を返すことでそれが止まるのも腑に落ちます。
納得できるし腑に落ちていたのに!(笑)
Storyboard 上でつくった UITableView とコードで作った UITableView でここらの挙動が変わられると、「一体何なの?」と急にすべてが腑に落ちなくなるという...😇

なにかプロパティ設定とかでここらの挙動を制御できたりするのでしょうか...🤔
助けて Swift つよつよマン...いやこの場合は UIKit つよつよマン?

検証用プロジェクトあげてみたので、暇で気になった人はみてみてください。
そしてどこがアレなのか教えてください mm

参考