tofucodes diary

にほんごのほう

UITextFieldViewのleftViewに隠された仕様について

TL;DR

  • 1つのViewインスタンスを、複数のUITextFieldViewleftViewに同時に表示することはできない
  • 同時には表示することができないので、leftViewMode.whileEditingに指定すれば1つのViewインスタンスを使い回すことも可能
  • この辺りの仕様は公式ドキュメントには記載されてないのでツライ

やりたかったこと

  • UITableViewCellのsubViewにUITextFieldを配置する
  • UITextFieldleftViewUIImageViewを常に表示する
  • UITextFieldのendEditingのコールバックでsuperViewのUITableViewCellをリロードする

問題

初回表示時は問題なくleftViewが表示されましたが、Cellをリロードした直後にアプリがハングし、以後メモリ使用量が増え続ける問題が発生しました。

仮説1

メモリが増え続けていたことから、まずはメモリリークを疑いました。 XcodeのInstrumentsで調べてみましたが、これといったリーク箇所を見つけることができませんでした。

仮説2

Cellのリロード直後に問題が発生していたことから、再レンダリングされる時にleftViewに指定しているViewのインスタンスが解放されていることを疑いました。 leftViewをstatic変数にするなど試してみましたが解決しませんでした。

デバッグ

UITextFieldleftViewMode.alwaysから.whileEditingに変更してみたところ、問題が発生しなくなることが判明しました。.unlessEditingだと依然として問題は発生しました。

以上のことからUITableViewに起因する問題ではなく、UITextFieldそのものが起因の問題もしくは仕様なのではないかと考えられます。

検証

UITableViewを利用せず、以下のようなシンプルなサンプルを実装して検証してみました。

class ViewController: UIViewController {

    @IBOutlet weak var textField_1: UITextField!
    @IBOutlet weak var textField_2: UITextField!

    let icon = UIImageView(image: #imageLiteral(resourceName: "any"))

    override func viewDidLoad() {
        super.viewDidLoad()

        self.textField_1.leftView = icon
        self.textField_1.leftViewMode = .always

        self.textField_2.leftView = icon
        self.textField_2.leftViewMode = .always
    }
}

案の定、アプリが動かなくなり、メモリが増え続けます。前述した問題と同じ症状です。どうやらUITextFieldに何らかの仕様か問題があることが確定しました。ググりやすくなります。

ググる

https://stackoverflow.com/a/6967456/4834226

A single UIView (or UIImageView) cannot be displayed on screen twice at the same time. I fixed this by creating separate padding views for the UITextFields.

つまり、「1つのUIView(もしくはUIImageView)は同時に2回スクリーン上に表示することはできない」。

公式ドキュメントには書いてないけど・・・。

developer.apple.com

結論

公式ドキュメントには特に記載がありませんが、1つのViewインスタンス複数のUITextField.leftView同時に表示することはできない、という仕様ということが判明しました。

UITableViewCellのケースでは、Cellが再利用される(複数のViewに利用される)ことでアプリがハングしていたと考えられます。