たまたま作業中に疑問に思うことがあってStackoverflowをググっていると、このような質問を見かけました。
Does a UITextField with first responder status in a UITableViewCell prevent cell re-use?
この記事では、「UITableViewCellに載っているUITextFieldがfirstResponderの場合、UITableViewセルの再利用がされないのでは?」という質問が投げかけられており、解答としては「SDKのドキュメントで言及はないので、その挙動に頼るべきではないでしょう。」となっています。
この記事では実際にこの挙動を検証してみます。
検証目的
isFirstResponder
なviewをサブビューに持つUITableViewCell
は、再利用されずに保持されるのか?
検証方法
再利用される場合はtableView(_:cellForRowAt:)が呼ばれるはずなので、セル上のviewに一度becomeFirstResponderを実行して完全にスクロールアウトし、セルと関連付いたオブジェクトの値の時だけブレークする条件付きbreakpointを貼る。その後に対象のセルをスクロールインさせて処理がブレークするかを見る。
テーブルに対してセルの高さが1/10になるように設定する。データは配列のUUIDを30件分用意する。こうすると、初期表示のセルを最下部までスクロールした際に通常なら確実に再利用させられる。(UICollectionViewはかなり多くをreusableQueueに持つが、UITableViewは表示数+1程度しか持たない)
UITextFieldだけではなく、どのViewの時もfirstResponderであればこの挙動は成り立つのか?を確かめるため、今回はUITextField, UITextView, カスタムUIViewの3種類を切り替えられるようにして検証する。
検証結果
どのViewを利用した場合でもbreakしなかった=セルをdequeueするためのtableView(_:cellForRowAt:)
が呼ばれていなかった。
総括
筆者は当初「UITextViewなどをサブビューで載せていてそれが編集中であったとしても、didEndDisplayが呼ばれ画面外に出てreuseQueueに入るのでresignFirstResponder()が呼ばれ編集状態が終わるだろう」と想定していましたが、実際はisFirstResponderなビューをサブビューに持つセルはreuseQueueに追加されず、別途ストアされている?(実際の処理は分からない)ことが観測されているため、スクロールを任意の距離実行して元の場所に戻ってきてもUITextViewは元のキャレットの位置と編集状態を保っています。
冒頭で掲載したStackoverflowの質問から10年が経過してもその挙動は変わっていないため、おそらく内部的に何らかの意味を持って実装された仕様だと想像します。
もし、「編集状態を終わらせたいのに終わらない」という方は、tableView(_:didEndDisplaying:forRowAt:)などでUITextViewやUITextFieldが編集状態の場合はendEditing(true)を呼ぶ。などの実装を行うと良いかもしれません。
ソースコード
ソースコードはbreakpointも含めてコミットしています。