※この記事はRettyアドベントカレンダー16日目の記事です。
昨日は諏訪さんのGraphQLでの認可に関する記事でした、こちらも併せてどうぞ。
はじめに
はじめまして、Rettyはらみチームの @imaizume です。
今年11月からRettyにJOINし主にiOSの開発をやっています。
好きな食べ物は沖縄料理、お酒はビール・泡盛・ワインが好きです、おすすめのお店をぜひ教えてください!
ではさっそく本日の内容に入っていきましょう!!
Retty iOSアプリのお店検索について
今回のテーマはRettyの重要な機能の一つであるお店検索です。
Rettyのお店検索では「場所」と「目的」の2つのクエリを入力することができ、これによってより最適なお店をユーザーさんにサジェストするようになっています。
各検索バー(UISearchBarTextField
)には文字を自由入力できますが、入力途中で候補をサジェストする機能があり、その中から値を選んだ場合は選択値が検索バー内に表示されるようになっています。
場所については、Rettyが用意している地域カテゴリで検索するため、自由テキストではなく選択値のみがクエリとして有効になります。
また2つの検索バーの種別を直感的に認識してもらえるよう、左側にそれぞれマップとフォークのアイコンを表示させています。
このようにアイコンを表示させる領域は UISearchBarTextField.leftView
をカスマイズして実装されています。
そして目的の検索バーに2つの値が入力されるか、または検索バー下のボタンをタップすることで、お店検索が実行され自動的に結果画面へと遷移します。
iOS 13で突然お店検索ができなくなる
これらはXcode 10でのビルド時は問題なく動作していました。
ところが、Xcode 11 (iOS 13 SDK)でビルドしたところ、両検索バーにおいて、テキストカーソルや入力中の値、サジェストの候補が一切表示されない不具合が発生しました。
また もう一方のテキストフィールドをタップしてもフォーカスが切り替わりません。
以下はその時のスクリーンショットです。
検索後の画面でも、場所の候補選択ができていないためクエリが送られていない様子がわかります。
幸いにも、Xcode 11でビルドしたバージョンはリリース前だったため、事前の検証で発見し大事故を防ぐことができていました。
原因はiOS 13 SDKからのUISearchの仕様変更
そこでView Hierarchyを使って検索バー周辺のViewのサイズを確かめてみました。
すると... なんと、アイコンの表示領域である leftView
が検索バーの幅いっぱいに広がっているではありませんか!!
正しい状態のleftViewと比較すると一目瞭然ですね!
でも、どうしてこんなことが起きてしまったのでしょうか。
どうやら調査の結果、このバグはiOS 13 SDKからの leftView
の仕様変更に起因しているようでした。
iOS 12までは、 leftView
のサイズを決めるのにはframeサイズを直接指定しかつその値は不変でした。
しかしiOS 13からは、サイズ決定時に leftView
に代入されるViewの systemLayoutSizeFitting
を呼び出す仕様に変わったようです。
systemLayoutSizeFitting
とは、レシーバーとなるViewについている制約を考慮して計算されたサイズを返すメソッドです。
Rettyのアプリを確認したところ、leftView
にはダイレクトにサイズを指定していました。
leftView?.frame = CGRect(x: 0, y: 0, width: 44, height: frame.size.height)
つまり
というUIKitの挙動の違いによって、Rettyでは後者に正しく対応できておらず leftView
の表示崩れが起きていたのでした。
両OSで動作させるにはframe指定とAuto Layout両方の指定が必要
そこで、frameサイズ指定している行を削除してAuto Layout指定に変えてみることに。
するとiOS 13では正しく動作したものの、今度はiOS 12以下では逆に動作しなくなってしまいました。
つまり逆も同様で、iOS 12ではframeサイズを指定しないとAuto Layoutでの制約通りには描画されないということのようでした。
ということで、最終的には iOS12以下向けに初期化時にframeによるサイズ指定を、iOS 13以降向けにAuto Layoutの制約を設定するコードを共存させるに至りました。
コードは以下のようになりました。
class SearchTextField: UITextField { ... // leftViewに対する幅制約 // iOS 13以降でサイズを決定するのに必要 private var widthAnchorForLeftView: NSLayoutConstraint? override func awakeFromNib() { ... let width: CGFloat = 44.0 widthAnchorForLeftView = leftView?.widthAnchor.constraint(equalToConstant: width) widthAnchorForLeftView?.isActive = true } func updateView() { let width: CGFloat = 44.0 // iOS 12以下ではframeサイズで指定 leftView?.frame = CGRect(origin: .zero, size: .init(width: width, height: frame.size.height)) // iOS 13以上ではNSLayoutConstraintで指定 widthAnchorForLeftView?.constant = width setNeedsLayout() layoutIfNeeded() } ... }
こうして無事Xcode 11でビルドしたアプリでもお店検索が動くようになりました!!
今回の変更は地味に影響範囲が大きい割に公式の1次情報が見当たらなかったため、個人的にはもっとしっかり周知をしてほしいなぁと思った次第です(汗)
まとめ
UISearchTextField
のleftView
とrightView
のサイズ計算の挙動がiOS 13で変わった- iOS 12ではサイズを直接かつ静的に指定するがiOS 13からはAuto Layoutで動的に指定する
- 両OSに対応して動かすためにはViewの初期化時に直接指定とAuto Laayoutの指定を両方行う
明日は二見さんによるデータ分析の記事になります、ぜひこちらもご覧ください!
そして、私と一緒にRettyで働きたいiOSエンジニアの方もぜひお気軽にご連絡ください!!