私はiOSのアプリの開発を行っているのですが、そのままではiOS13で動作しませんでした。iOS13対応で必要だったこと、ハマったことについて書いておきたいと思います。
Xcode11でストーリーボードとソースコードの両表示方法がわからない
(2019/9/21追記)
まず最初につまずいたのはここでした。XcodeのストーリーボードでUIコンポーネントをソースコードに対応づけるために隣り合わせて表示させるアレです。Xcode10までは右上の方に◯印2つが重なり合ったようなアイコンを押していたのですが、Xcode11ではそれがなくなってしまい、どうやって表示するのかわからなくなりました。
「アレ」はアシスタントエディタというのが正式名称のようで、ストーリーボードが表示された状態で、EditorメニューのAssistantを選ぶことで無事に表示されました。
UISearchBar
内のテキストフィールドの公式サポート
従来、UISearchBar内のテキストフィールドへのアクセスは公式にはサポートしておらず、非公式のsearchBar.value(forKey: "_searchField") as? UITextField
等として無理やり取得して使っていました。
iOS13ではこのテキストフィールドへのアクセスが公式APIとして提供され、
let searchField = searchBar.searchTextField
で取得できるようになりました。しかもこのクラスはUISearchTextField
というクラスで、token表示機能(iOS標準のメールアプリの検索機能とかで、カードっぽい表示になるやつです)をサポートしています。
これだけなら既存アプリへの影響はなさそうですが、これに伴って2つハマったことがありました。
_searchField
のアクセス禁止問題
元々非公式だったので問題というのも何ですが、テキストフィールドへのアクセスの公式化に伴って、アプリからの_searchField
へのアクセスが禁止されました。このため、この方法でテキストフィールドを取得していた場合には公式APIに変更が必要になりました。
Any?
からプロトコルへのキャスト問題
この問題は直接的にiOS13と関係あるわけではないのですが、自分のアプリでは元々_searchField
で取得したテキストフィールドを使って、無理やりtoken表示っぽいことを行っていました。
今回、token機能が公式にサポートされたので、ありがたく使ってみることにしました。
UISearchTextField
にトークン表示を追加する場合は、UISearchToken
というクラスを使うことになります。この時、表示上のアイコン・文字列もそうですが、何を検索するのか(例えば日時を検索するとか)も合わせて覚えておく必要があります。そのために、UISearchToken
にはrepresentedObject
というAny?
型のプロパティがあり、ここにその情報を詰め込んでおくことで後から取り出して検索することが可能になります。
で、検索するキー項目が複数ある場合に(日時と名前とか)、自然とrepresentedObject
に詰め込むクラスは何かの共通プロトコル(ここでは仮にSearchKey
とします)を実装したクラスになると思います。
以下のような状況を想定します。
// 検索キー用の共通プロトコル
protocol SearchKey {
}
// 日付検索用のキー
class DateKey : SearchKey {
}
// 名前検索用のキー
class NameKey : SearchKey {
}
representedObject
にDateKey
やNameKey
のオブジェクトを入れて取り出すときに、
let searchKey = token.representedObject as? SearchKey
のように取り出したくなりますが、何とこの方法ではnil
になってしまい取り出せません。
上記URLにその情報があり、無事解決したのですが、SwiftではAny?
型をプロトコル型に直接キャストすることができないようです。一度AnyObject
型にすると良いという解決策があるとのことだったので、以下のようにして回避しました。
let searchKey = token.representedObject as AnyObject as? SearchKey
検索バーのキャンセルボタンにまつわる問題
(2019/12/19に追記しました。iOS13対応直後は気付きませんでしたが、この問題が発生していたようです)
私のアプリでは検索した結果の一覧画面に編集ボタンがあり、編集押下時には元々検索入力エリアを非活性にするとともにキャンセルボタンを消していたので、iOS13対応では以下のようにコードを書き換えました。
let searchBar = parentVC.searchController.searchBar
searchBar.setShowsCancelButton(false, animated: true)
searchBar.searchTextField.isEnabled = false
ところが、setShowsCancelButton
を実行してもキャンセルボタンは消えず、編集のキャンセルボタンで編集モードを抜けた後(この際、上記と逆のコードを実行しています)、検索のキャンセルボタンで元の一覧画面に戻ると、検索のキャンセルボタンが残るという事象が発生しました。
この問題の解決方法は現時点でわかっていませんが、上記のsetShowsCancelButton
を行わないと発生しなくなることがわかりました。setShowsCancelButton
はキャンセルボタンを消すために呼んでいるはずなのですが、消えない上に上記のような問題を引き起こすだけなので、暫定処置としてスキップするようにしました。
キャンセルボタンは消えないままですが、一覧画面に戻った時にボタンが残る問題は回避できました。
UITableViewCell
のラベルの翻訳失敗問題
この問題はXcode11.3では解消しているようです(2019/12/19確認。11.2で直っていた可能性あり)
私のアプリではstoryboardを使用して画面を開発しています。英語・日本語両対応させているので、まずは英語で画面を作成し、Main.stringsで該当するObjectIDに対して日本語を設定するというやり方です。
ところが、Xcode11 GM Seedで開発していたところ、いつの間にか翻訳されないようになってしまいました。私のアプリの場合は、staticなUITableView
に配置しているUITableViewCell
のタイトルが一律翻訳されなくなりました。それ以外のコンポーネントは普通に翻訳されているので、おそらくXcodeのバグではないかと思っているのですが、うまい解決方法がなく、以下の方法で暫定回避しました。
- 翻訳が失敗している
UILabel
のObjectID
の値をAccessibility
のIdentifier
(IB上で、ObjectID
のちょっと下にあるやつです)に手動でコピー - その
UITableView
のDataSourceクラスに対して、以下を追加実装
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = super.tableView(tableView, cellForRowAt: indexPath)
if let label = cell.textLabel, let id = label.accessibilityIdentifier, id.count > 0 {
let key = id + ".text"
let localizedString = NSLocalizedString(key, tableName: "Main", comment: "")
if key != localizedString {
label.text = localizedString
}
}
return cell
}
要は、Accessibility Identifier
が存在して、Main.stringsに対応する翻訳が存在する場合に、テキストを置き換えるというのを自前で実装しています。
これで無事翻訳されるようになりました。
注意点としては、手動でObjectID
をコピーする際に、間違えないようにすることでしょうか。間違って他のIDを貼り付けてしまうと、別の訳語が表示されてみっともないことになります。
なお、Stack overflowで同じ問題を見つけたので、上記の回避策を投稿してみました。
https://stackoverflow.com/questions/57905965/xcode-11-ios-13-localization-issue
UISearchDisplayController
廃止によるアプリクラッシュ問題
一通り対応を終えて、AppStoreにアップロードしてTestFlightで動作確認しようとしたところ、起動時にアプリがクラッシュする現象が発生しました。シミュレータでのデバッグや、ローカルでの実機デバッグではうまく動いていたので調査は難航しましたが、
3 UIKitCore 0x1a3227148 UISearchDisplayControllerNoLongerSupported + 247
このクラッシュログを手がかりに、以下の情報にたどり着きました。
https://github.com/mapbox/mapbox-gl-native/issues/15559
かなり前から非推奨になっていたUISearchDisplayController
がiOS13で完全に廃止されたため、使っているとクラッシュするという問題のようです。
問題は、こんなクラスを使用している覚えがなく、ソース全体をUISearchDisplayController
で検索しても見つからなかったので、何故こんなエラーが出るのか途方に暮れていましたが、実はありました。
どうやら、検索機能を開発する際に試行錯誤して追加した実際には使われていないものが残っていたようです。この記述はUISearchDisplayController
で検索しても見つからず、searchDisplayController
で検索する必要があるので注意しましょう。
これを削除したら解決しました。
メインスレッド以外からのUIコンポーネントアクセスチェックの厳格化
元々画面描画関連の処理はメインスレッドから行わないといけませんでしたが、直接的に画面描画を伴う処理ではなくてもUIコンポーネントに対するアクセスのチェックが厳しくなったようです。私のアプリの場合は、UIViewController
のsplitViewController
やnavigationController
に別スレッドからアクセスしている処理があり、チェックに引っかかりました。
単にそのViewController
クラスで保持している自前のプロパティにアクセスしたいだけだったので、splitViewController
やnavigationController
を経由せずに直接目的のViewControllerにアクセスするようにして回避しました。
Xcode11 GM Seed2でiOS13のシミュレータが動かない
(2019/9/21追記)
Xcode11のGM Seed2にして以降、iOS13のシミュレータを起動しようとするとThe iOS 13.0 simulator runtime is not available.
というメッセージが出て起動しなくなってしまいました。詳細表示するとruntime path not found
と出ていました。しばらく理由がわからなかったのですが、いろいろ検索した結果、以下の記事が当たりでした。
https://stackoverflow.com/questions/53261256/xcode-10-simulator-runtime-is-not-available-runtime-profile-not-found-error
$ sudo killall -9 com.apple.CoreSimulator.CoreSimulatorService
これでシミュレータサービスを再起動することで問題が解消しました。
以上、iOS13対応している方の何かの役に立てば幸いです。