🍎

iOS 14対応で気をつけるべきこと

2020/09/23に公開

iOS 14は2020年9月17日 (日本時間) にリリースされました。
正式版のリリース日が9月16日 (日本時間) の #AppleEvent で突然発表されたため、多くのiOSエンジニアの阿鼻叫喚の様子がTLで流れてきて、祭り状態でした。

そんな慌ただしいiOS 14ですが、いくつかの対応を忘れると面倒なことになるバージョンでもありました。そこで自分がiOS 14対応をする中で気になったポイントをピックアップして共有したいと思います。

canOpenURLの使い方に注意

自由入力や外部入稿などによって渡されるURLを開く前に、開けるURLなのか検証するために canOpenURL を使うことはよくあると思います。
iOS 14ではデフォルトブラウザをSafari以外に設定することができるようになりましたが、Safari以外に設定すると canOpenURL が常に false になるアプリが散見されました。

canOpenURLのドキュメントには
「このメソッド渡すURLスキームを必ず LSApplicationQueriesSchemes に追加すること。追加しなければ常に false になります」

you must declare the URL schemes you pass to this method by adding the LSApplicationQueriesSchemes key to your app's Info.plist file. This method always returns false for undeclared schemes, whether or not an appropriate app is installed.

https://developer.apple.com/documentation/uikit/uiapplication/1622952-canopenurl

という記載が以前からあるのですが、httpやhttpsに関してはiOS 13以前は設定に関係なく常に true を返す挙動をしていました。ですが、iOS 14からは設定しないとドキュメント通りの挙動になるため、追加を忘れないようにしましょう。
(デフォルトメールを変更した場合のmailtoも同様でしたのでご注意ください)

Carthageでのビルドエラー

Xcode 12で Carthage を使ってライブラリをビルドするとエラーになることがありました。
これはどうやらXcode 12からApple Silicon対応が入っている関係で、arm64アーキテクチャ用のバイナリが重複して含まれてしまうことが原因のようです。

この問題は下記のissueで議論されており、ワークアラウンドが共有されています。
https://github.com/Carthage/Carthage/issues/3019

共有されている下記のスクリプトを carthage-build.sh のような名前で保存し、

carthage-build.sh
#!/usr/bin/env bash

# carthage-build.sh
# Usage example: ./carthage-build.sh --platform iOS

set -euo pipefail

xcconfig=$(mktemp /tmp/static.xcconfig.XXXXXX)
trap 'rm -f "$xcconfig"' INT TERM HUP EXIT

# For Xcode 12 make sure EXCLUDED_ARCHS is set to arm architectures otherwise
# the build will fail on lipo due to duplicate architectures.
# Xcode 12 Beta 3:
echo 'EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200__BUILD_12A8169g = arm64 arm64e armv7 armv7s armv6 armv8' >> $xcconfig
# Xcode 12 beta 4
echo 'EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200__BUILD_12A8179i = arm64 arm64e armv7 armv7s armv6 armv8' >> $xcconfig
# Xcode 12 beta 5
echo 'EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200__BUILD_12A8189h = arm64 arm64e armv7 armv7s armv6 armv8' >> $xcconfig
# Xcode 12 beta 6
echo 'EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200__BUILD_12A8189n = arm64 arm64e armv7 armv7s armv6 armv8' >> $xcconfig
# Xcode 12 GM (12A7208)
echo 'EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200__BUILD_12A7208 = arm64 arm64e armv7 armv7s armv6 armv8' >> $xcconfig
# Xcode 12 GM (12A7209)
echo 'EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200__BUILD_12A7209 = arm64 arm64e armv7 armv7s armv6 armv8' >> $xcconfig

echo 'EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200 = $(EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200__BUILD_$(XCODE_PRODUCT_BUILD_VERSION))' >> $xcconfig
echo 'EXCLUDED_ARCHS = $(inherited) $(EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_$(EFFECTIVE_PLATFORM_SUFFIX)__NATIVE_ARCH_64_BIT_$(NATIVE_ARCH_64_BIT)__XCODE_$(XCODE_VERSION_MAJOR))' >> $xcconfig

export XCODE_XCCONFIG_FILE="$xcconfig"
carthage "$@"

(使いやすいように一部改変)

下記のようなコマンドで実行するとビルドできるケースが多かったです。

sh ./carthage-build.sh bootstrap --platform iOS --cache-builds

CIでfastlaneを使っている場合は下記のようなlaneを作ると便利です。
(./scripts/carthage-build.shに↑のスクリプトを置いた場合)

desc "CarthageのXcode 12対応ワークアラウンド"
private_lane :carthage_workaround do |options|
  `cd .. && sh ./scripts/carthage-build.sh bootstrap --platform iOS --cache-builds`
  UI.build_failure! "Carthageビルドエラー" if $?.exitstatus != 0
end

戻るボタン長押し

iOS 14ではナビゲーションバーの戻るボタンを長押しすることで、一気に前の画面に戻れるようになりました。

ここで表示されているテキストはUIViewControllerのtitlenavigationItem.backButtonTitleから取得されていますが、デザインのために戻るボタンのテキストに空文字を入れていると、下記のように空白のメニューが表示されてしまいます。

ユーザーが迷わないように分かりやすいテキストを設定することが推奨されているため、できる場合は対応したいですね。
また、戻るタイミングなどで処理を入れている場合に一気に前のページに戻ってしまうと不都合が起きるケースがあるため、仕様や実装の見直しが必要になることもあります。

ちなみにiOS 13以前のデザインを維持したい場合のワークラウンドとして、戻るボタンを非表示にしたい場合は文字色を .clear にしたり、そもそもこのメニューを無効にしたりする方法もあります。

https://sarunw.com/posts/what-should-you-know-about-navigation-history-stack-in-ios14/
https://developer.apple.com/forums/thread/653913

サブスクリプションのファミリー共有

サブスクリプションを家族 (管理者1人、その他5人まで) で共有できるようになりました。この設定はApp Store ConnectでONにした場合のみ有効になります。

注意点としては 一度でもONにしてしまうと二度とOFFにできない ということです。
会社で独自管理しているIDとレシートの original_transaction_id などを紐付けている場合は、サーバー側の実装もこれによって変更する必要が出てくる事が多いため、気軽にONを試してみようとは思わず、必要な時にONにするようにしましょう。

https://developer.apple.com/videos/play/wwdc2020/10651/

クリップボード通知

iOS 14からアプリ外でコピーした内容にアクセスすると画面上部に通知が出るようになりました。

機能提供のため意図的に読み取っている場合は問題ないですが、外部ライブラリなどの影響で意図しないタイミングで上記の通知が出てしまうことがありました。

中でも有名なのはFirebaseのDynamic Linksです。
ディープリンクの機能のために UIPasteboard が利用されており、通知が出てしまうようです。

通知を出さないようにするためには、SDKバージョンを6.28.0以上にして、Info.plistにFirebaseDeepLinkPasteboardRetrievalEnabled = NO を設定する必要があります。
ただし下記のissueで説明があるように、deferred deep-linkingが動かなくなったり、アプリのインストールを過小評価してしまうようなので、注意が必要です。

Deferred deep-linking will not work as reliably. At best, your app receives weak matches for deep-links.
App install attribution stats will be less accurate (potentially undercounting app installs).

https://github.com/firebase/firebase-ios-sdk/issues/5893

実装の事情を知らないユーザーからすると結構怖い通知なのでできるだけ出さない方向で調整したいところです。

UIStackView

iOS 14から UIStackView の背景に色を付けられるようになりました。

let makeLabel: () -> UILabel = {
    let label = UILabel()
    label.text = "🐱"
    return label
}

let stackView = UIStackView(arrangedSubviews: [
    makeLabel(), makeLabel()
])
stackView.frame = .init(x: 0, y: 0, width: 200, height: 100)
stackView.backgroundColor = .red
view.addSubview(stackView)
iOS 14 iOS 13以下

iOS 14とそれ未満ではこのように動作が異なるため、意図せず背景が塗りつぶされてないか動作確認をしっかりしたほうが良いと思います。

DatePicker

Xcode 12でビルドするとDatePickerの見た目が変わります。

Before After

元のスタイルに戻したい場合は preferredDatePickerStylewheels を指定します。

let picker = UIDatePicker(frame: .init(x: 0, y: 200, width: view.bounds.width, height: 200))
picker.preferredDatePickerStyle = .wheels
view.addSubview(picker)

https://developer.apple.com/documentation/uikit/uidatepicker/3526124-preferreddatepickerstyle

写真へのアクセス

iOS 14から端末内の写真にアクセスする際に一部の写真のみ読み書きができるパーミッションが増えました。

今までの許諾確認実装では「写真を選択」をタップした場合でも authorized というステータスが返ってきてしまうため、ユーザーに適切な案内ができない可能性があります。

PHPhotoLibrary.requestAuthorization { status in }
let status = PHPhotoLibrary.authorizationStatus()

iOS 14からは limited が正しく返ってくる引数付きのAPIに移行しましょう。

PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in }
let status = PHPhotoLibrary.authorizationStatus(for: .readWrite)

また、「写真を選択」を選んだ場合、写真取得時に下記のアラートが表示されます。(アプリ起動ごと)

たくさんの写真を扱うことが前提でないアプリの場合はそのままでも問題ありませんが、フルアクセスを期待しているアプリの場合は設定アプリに遷移させて「すべての写真」を許可してもらうほうが良さそうです。
その場合はこのアラートは不要なので、info.plistに PHPhotoLibraryPreventAutomaticLimitedAccessAlert = YESを追加して、自動で表示されないようにすると良いです。

そもそもユーザーが選択した写真のみを取得するだけのアプリの場合は PHPickerViewController を使うと パーミッションなしで 写真を取得できます。

var config = PHPickerConfiguration()
config.selectionLimit = 1 // デフォルト1枚、0にすると無制限に選択できる
let picker = PHPickerViewController(configuration: config)
picker.delegate = self
present(picker, animated: true)
extension ViewController: PHPickerViewControllerDelegate {
    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        // 選択された画像の取得
    }
}

https://developer.apple.com/documentation/photokit/phpickerviewcontroller

追跡型広告

当初iOS 14ではIDFAの取得前に許諾アラートを出して許可を得ない限り、値を取得できない仕様でした。しかしそれは2021年の初旬に延期されました。
https://developer.apple.com/jp/news/?id=hx9s63c5

そのため、現在 (2020/9/23) はiOS 14でも下記の実装でIDFAを取得できます。

let id = ASIdentifierManager.shared().advertisingIdentifier.uuidString

ただし ASIdentifierManager.shared().isAdvertisingTrackingEnabled に関しては、iOS 14ではdeprecatedとなり、常に false になっています。

https://developer.apple.com/documentation/adsupport/asidentifiermanager/1614148-isadvertisingtrackingenabled

広告ライブラリに内包されていて直接制御することは少ないかもしれませんが、もし上記のプロパティを利用している場合は下記のように移行先のAPIをラップしたメソッドを作ると便利です。

func isAdvertisingTrackingEnabled() -> Bool {
    if #available(iOS 14, *) {
        let status = ATTrackingManager.trackingAuthorizationStatus
        switch status {
        case .notDetermined, .restricted, .denied:
            return false
        case .authorized:
            return true
        @unknown default:
            return false
        }
    } else {
        return ASIdentifierManager.shared().isAdvertisingTrackingEnabled
    }
}

https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/3547038-trackingauthorizationstatus

位置情報

iOS 14ではアプリに渡す位置情報を大幅に曖昧にするプライバシー保護機能が増えました。

かなり網羅的なTipsを下記の勉強会でシェアしましたので、資料をぜひ読んでみてください。 (iOS 14が一般公開されたので今、資料公開しました)

https://yj-meetup.connpass.com/event/182526/

最後に

iOS 14はアプリ開発者的にどったんばったん大騒ぎなバージョンでしたが、ホーム画面設置型のウィジェットやApp Clipsなどのユーザーのニーズに応えた機能が追加されていて今のところお気に入りです。
(iPadでもウィジェットを好きなところに置けるようになったらいいなぁ)

続編は書ききれなかった他のTipsやXcode 12/iOS 14で便利になったこと、WidgetsのTipsとかにしようかな ✏️

Discussion