Firebase iOS SDK を使っていると SwiftUI の Preview が機能しない問題(Firebase iOS SDK v6.33.0)

Firebase iOS SDK を使っている SwiftUI のプロジェクトで Preview が表示されなくなった。(Xcode 12.0.1)

とりあえず configure()コメントアウトすれば、Preview は表示される。

// FirebaseApp.configure()

調べたら、この問題について issue が上がっていた。(2020/10/4 現在、修正リリースは出ていないが issue 自体は close されている)

issue の内容を読むと、どうやらこれは v6.33.0 で起きる問題で、その前のバージョン(v6.30.0 とか)では発生しないらしい。なので、前のバージョンを指定してライブラリ追加しておくと、とりあえず回避できる。CocoaPods ならこんな感じ。

pod 'Firebase/Analytics', '6.30.0'

今後は、、
Firebase iOS SDK v6.34.0 のマイルストーンにこの issue はあるので、次のバージョンでは直っている。。。のかな。と。

https://github.com/firebase/firebase-ios-sdk/milestone/70?closed=1

Nominatim(OSM) API を試してみた(iOS)

iOS で Nominatim API の動作モックを作って試してみた。

動作イメージはこんな感じ。

Nominatim は OSM(Open Street Map)のデータからジオコーディングした結果を取得できる API で、たとえば地名から住所(緯度経度)検索ができる。また、緯度経度から地名を取得する逆ジオコーディングの機能も API にある。


以下、2020/9/23 現在の API の感想。(OSM のデータは更新されるため、最新のデータでは状況が変わっているかもしれないので、注意)

良い点

試してみた感じはやはり OSM はデータが膨大なので、結果の候補が多く取得できる。

たとえば「中央区」と検索した時、日本各地の中央区の候補がちゃんと取得できる。

https://nominatim.openstreetmap.org/search?q=%E4%B8%AD%E5%A4%AE%E5%8C%BA&format=json

[
{
"place_id": 235480386,
"licence": "Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright",
"osm_type": "relation",
"osm_id": 1758897,
"boundingbox": [
"35.6403529",
"35.6966147",
"139.758555",
"139.7931533"
],
"lat": "35.666255",
"lon": "139.775565",
"display_name": "中央区, 東京都, 日本",
"class": "boundary",
"type": "administrative",
"importance": 0.6646376901336712,
"icon": "https://nominatim.openstreetmap.org/images/mapicons/poi_boundary_administrative.p.20.png"
},
{
"place_id": 233767601,
"licence": "Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright",
"osm_type": "relation",
"osm_id": 358727,
"boundingbox": [
"34.662424",
"34.6953569",
"135.496732",
"135.5355055"
],
"lat": "34.679846",
"lon": "135.510316",
"display_name": "中央区, 大阪府, 日本",
"class": "boundary",
"type": "administrative",
"importance": 0.615352652067619,
"icon": "https://nominatim.openstreetmap.org/images/mapicons/poi_boundary_administrative.p.20.png"
},
★以下略...

]

気を付けたい点

OSM はデータがローカライズはされていたり、されていなかったりが割と多い印象なので

たとえば「Anchorage」では結果が得られるが

https://nominatim.openstreetmap.org/search?q=Anchorage&format=json

[
{
"place_id": 236481643,
"licence": "Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright",
"osm_type": "relation",
"osm_id": 8867140,
"boundingbox": [
"60.733788",
"61.483938",
"-150.420615",
"-148.460007"
],
"lat": "61.2163129",
"lon": "-149.8948523",
"display_name": "Anchorage, アラスカ州, アメリカ合衆国",
"class": "boundary",
"type": "administrative",
"importance": 0.6801377964834151,
"icon": "https://nominatim.openstreetmap.org/images/mapicons/poi_boundary_administrative.p.20.png"
},

★以下略...

]

「アンカレッジ」では結果が得られない

https://nominatim.openstreetmap.org/search?q=%E3%82%A2%E3%83%B3%E3%82%AB%E3%83%AC%E3%83%83%E3%82%B8&format=json

[]

ということには、気を付けたい。

プロジェクトを Xcode 12 にアップグレードする時の対応あれこれ

CocoaPods で導入したライブラリのターゲットが iOS 8.0 になっている警告

f:id:daisuke-t-jp:20200918100755p:plain:w400

The iOS Simulator deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.0.99.

Xcode 12 からは iOS 9 以降が対象なのでこの警告が出る。

なので Podfile に以下を追加して、明示的に CocoaPods のライブラリのターゲットを 9.0 以降にする。

post_install do |pi|
    pi.pods_project.targets.each do |t|
        t.build_configurations.each do |config|
            config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '9.0'
        end
    end
end

そして Pods を更新する。

pod update

CocoaPods で導入したライブラリ内でダブルクォートで include しているエラー

- Double-quoted include "pb.h" in framework header, expected angle-bracketed instead
- Double-quoted include "pb_common.h" in framework header, expected angle-bracketed instead

Firebase SDK 内で発生した。

対応方法は、Pods のプロジェクトの BuildSettings で Quoted Include In Framework Header を NO にすると、とりあえずビルドできる。

f:id:daisuke-t-jp:20200918100804p:plain:w400

Firebase iOS SDK の issue をみると、どうやらこの問題は CocoaPods の ver 1.1.0 を使っている場合は解決されているらしい。

ver 1.1.0 未満の場合はこの解決方法をとる必要があるようだ。

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

A fix is at CocoaPods/CocoaPods#9905 and targeted for CocoaPods > 1.10. I'll close this bug in favor of CocoaPods/CocoaPods#9902.

Use the workaround described above with CocoaPods versions before 1.10

fastlane でスクリーンショット撮影するまでのセットアップ

fastlane snapshot を試してみたのでメモ。

資料

fastlane snapshot 導入のメリット

導入のコストはそんなに大きくなく、得られるメリットの方が大きいとおもう。

  • UITests の実行状態をスクリーンショットで記録できる。
    • UITests はパスした上での問題(更新により前回とデザインが変わってしまった、など)を確認できる。
  • AppStore 提出用のスクリーンショットを自動で撮影できる。

セットアップ

Homebrew 経由でインストール(gem でも良い)

$ brew install fastlane

インストール確認

$ which fastlane

インストールされている場合はパスが表示されるハズ。

プロジェクトのルートで fastlane を初期化

$ fastlane snapshot init

fastlane フォルダができる。その中に以下のファイルができる。

  • Snapfile (設定ファイル)
  • SnapshotHelper.swift
    • UITests のターゲットに追加する。

UITests に撮影コードを追加

UnitTests に以下の記載を追加する。

class UITests: XCTestCase {
    func testExample() throws {
            let app = XCUIApplication()
            setupSnapshot(app)  // ⭐️ fastlane snapshot をセットアップ
            app.launch()
            snapshot("launch")  // ⭐️ ファイル名を指定して撮影
    }
}

Snapfile 編集

Snapfile はスクリーンショット撮影の設定ファイル。ここに必要な設定を書く。

とりあえず、以下のようにして iPhone 11 で英語の状態で撮影されるようにする。

# Uncomment the lines below you want to change by removing the # in the beginning

# A list of devices you want to take the screenshots from
devices([
#   "iPhone 8",
#   "iPhone 8 Plus",
#   "iPhone SE",
#   "iPhone X",
#   "iPad Pro (12.9-inch)",
#   "iPad Pro (9.7-inch)",
#   "Apple TV 1080p"
    "iPhone 11", // ⭐️ iPhone 11 シミュレーターで撮影
])

languages([
    "en-US",  // ⭐️ 言語は en-US だけ
#   "de-DE",
#   "it-IT",
#   ["pt", "pt_BR"] # Portuguese with Brazilian locale
])

# The name of the scheme which contains the UI Tests
scheme("⭐️UITests を含んでいるスキームの名前")

# Where should the resulting screenshots be stored?
output_directory("./screenshots")

# remove the '#' to clear all previously generated screenshots before creating new ones
clear_previous_screenshots(true)

# Remove the '#' to set the status bar to 9:41 AM, and show full battery and reception.
override_status_bar(true)

# Arguments to pass to the app on launch. See https://docs.fastlane.tools/actions/snapshot/#launch-arguments
# launch_arguments(["-favColor red"])

# For more information about all available options run
# fastlane action snapshot

実行

プロジェクトのルート(fastlane フォルダの中ではない)で以下を実行。

$ fastlane snapshot run

プロジェクトのビルドが始まり、成功すると screenshotフォルダができて、ファイルが作られる。

fastlane のコマンドではなく、Xcode から直接ユニットテストを実行するとスクリーンショットは作成されないことに注意。

スクリーンショット撮影のコンディションを Snapfile で指定したい

たとえば「広告が非表示の状態」で撮影したいとする。

この場合の対応方法のひとつとして、まず Snapfile でアプリの起動引数が指定できるので、以下のように設定する。

# Arguments to pass to the app on launch. See https://docs.fastlane.tools/actions/snapshot/#launch-arguments
# launch_arguments(["-favColor red"])
launch_arguments(["-hiddenAd"])  // ⭐️ 起動引数設定

そして設定された起動引数でアプリ内で動作を変更すると、広告が消えた状態で撮影できる。

// 起動引数を確認
if ProcessInfo.processInfo.arguments.contains("-hiddenAd") {
    // 広告を非表示にする
}

// CommondLine クラスを使っても起動引数は確認できる
if CommandLine.arguments.contains("-hiddenAd") {
    // 広告を非表示にする
}

UserDefaults と Keychain を使ったカスタムクラスの永続化

https://github.com/daisuke-t-jp/UserDefaultsAndKeychainSample/

サンプルを作った。以下メモ。

Swift の Codable を使えば、プロパティそれぞれをエンコード、デコードのコードを書く必要なく楽だ。

Keychain の場合は

感じ。

iOS 14 以降の Google AdMob 対応(AppTrackingTransparency Framework)

資料

前提

iOS 14 では AppTrackingTransparency Framework が追加され、ユーザのトラッキング収集はこのフレームワークを介して、ユーザに承認を得る必要がある。

もし iOS 13 以前に IDFA を利用して広告を表示していたアプリがあったとして、iOS 14 で AppTrackingTransparency を使ってユーザの承認を得ない場合は、IDFA が無効になった状態で広告が表示されることになる。 (=収益が下がることが予想される)

Google AdMob で必要な変更

Google AdMob のバージョンを更新する

  • Google Mobile Ads SDK は 7.64.0 以降を使用する

ラッキング利用の承諾をリクエス

Info.plist にトラッキング利用の説明文を追加する

NSUserTrackingUsageDescription を Info.plist に追加する。

<key>NSUserTrackingUsageDescription</key>
<string>This identifier will be used to deliver personalized ads to you.</string>

ラッキングの承認をリクエストして、その結果 Google AdMob を表示する。

import AppTrackingTransparency
import AdSupport
...
func requestIDFA() {
  ATTrackingManager.requestTrackingAuthorization(completionHandler: { status in
    // Tracking authorization completed. Start loading ads here.
    // loadAd()
  })
}

ユーザへのトラッキング利用の許諾アラートに説明文が表示される。 f:id:daisuke-t-jp:20200918092200p:plain:w300

iOS 14 でトラッキング利用をユーザに拒否されたら?

Google AdMob ではトラッキング広告は表示できない。

そういう時は、かわりに Google AdMob は SKAdNetwork を使用して、アプリインストールをアトリビューションすることができる↓

https://developers.google.com/admob/ios/ios14?hl=ja#skadnetwork

Apple の住所(地名)検索 API を比較する(iOS)

テキストから住所(地名)を得たい場合 (たとえば、"嵐山" という入力から "京都府京都市右京区" という結果が欲しい)

その用途に使用できそうな AppleAPI は、以下がある。

これらの違いや使い道を確認したかったので、テキスト入力をしてそれぞれの API の結果を一覧表示するサンプルを作った。


調べて使ってみてわかったこと

CLGeocoder のリクエスト制限

CLGeocoder は短時間に大量リクエストを行うと失敗する。(CLError.Code.network

この動作はドキュメントにも明記されている。

Apps must be conscious of how they use geocoding. Geocoding requests are rate-limited for each app, so making too many requests in a short period of time may cause some of the requests to fail. (When the maximum rate is exceeded, the geocoder returns an error object with the CLError.Code.network error to the associated completion handler.) Here are some rules of thumb for using this class effectively:

ただし、以下の記載があるので、ユーザアクションに紐づいた頻度程度の API 呼び出し自体は考慮されているようだ。

• Send at most one geocoding request for any one user action.

アプリ側で呼び出すタイミングを気を付ければ、あまりエラーになることは無さそうだ。(逆に言えば、ユーザー入力など関係なく、アプリの動作として大量リクエストを送ったりする用途には使えない)

MKLocalSearch

CLGeocoder とは違い、MKLocalSearch はリクエスト制限でエラーにはならない(連続リクエストをしてエラー発生を観測できなかった)

こちらは、現在地と範囲を指定して地図上にある"近場"の Placemark を検索するのが本来の用途のようだ。(LocalSearch なので)

CLGeocoder とはちがい、住所だけではなく、店舗の名前を知りたいならばこちらを使う。

ただし、MapKit の機能なので、地図表示がないアプリで住所検索をしたいために MKLocalSearch だけ使う、というのはレギュレーションに反していないのか、疑問である。

CLGeocoder では複数の結果が得られない?

f:id:daisuke-t-jp:20200822185109p:plain:w300

iOS に入っている Apple の天気アプリではこのように、テキストに対して住所候補が複数あれば、複数表示される。

このような動作を期待して CLGeocoder を使ってみたが、CLGeocoder はレスポンスは配列であるが、結果は常に単一になっていた。

たとえば "中央区" の場合、iOS の天気アプリのように、日本国内にある中央区を複数候補を返して欲しいが、ひとつの中央区しか CLGeocoder は返さない(現在位置から一番近い "中央区" になる?)

この CLGeocoder のレスポンスが一つしか得られない問題は StackoverFlow でも散見されるので、どうにかなる話では無さそうだ。

「候補が複数欲しい」場合は、AppleAPI は使わず他のサービス(GoogleAPI など)を使う必要があるようだ。