LoginSignup
25
18

More than 3 years have passed since last update.

アプリのiOS13対応でハマったこと

Last updated at Posted at 2019-09-17

私は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 {
}

representedObjectDateKeyNameKeyのオブジェクトを入れて取り出すときに、

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のバグではないかと思っているのですが、うまい解決方法がなく、以下の方法で暫定回避しました。

  1. 翻訳が失敗しているUILabelObjectIDの値をAccessibilityIdentifier(IB上で、ObjectIDのちょっと下にあるやつです)に手動でコピー
  2. その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で検索しても見つからなかったので、何故こんなエラーが出るのか途方に暮れていましたが、実はありました。

スクリーンショット 2019-09-17 22.37.20.png

どうやら、検索機能を開発する際に試行錯誤して追加した実際には使われていないものが残っていたようです。この記述はUISearchDisplayControllerで検索しても見つからず、searchDisplayControllerで検索する必要があるので注意しましょう。

これを削除したら解決しました。

メインスレッド以外からのUIコンポーネントアクセスチェックの厳格化

元々画面描画関連の処理はメインスレッドから行わないといけませんでしたが、直接的に画面描画を伴う処理ではなくてもUIコンポーネントに対するアクセスのチェックが厳しくなったようです。私のアプリの場合は、UIViewControllersplitViewControllernavigationControllerに別スレッドからアクセスしている処理があり、チェックに引っかかりました。
単にそのViewControllerクラスで保持している自前のプロパティにアクセスしたいだけだったので、splitViewControllernavigationControllerを経由せずに直接目的の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対応している方の何かの役に立てば幸いです。

25
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
18