酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

【未解決】SwiftUIのListに配置したTextEditorにフォーカスを当てても自動でスクロールされない

iOS 14になってからTextFieldにフォーカスを当てると適切なポジションに自動でスクロールするようになった。iOS 13ではKeyboardObservingなどのライブラリを利用する必要があったので、標準機能として実装されたのはとても嬉しい。

その一方で、SwiftUIのList上に配置したTextEditorにフォーカスを当てても自動でスクロールされない。

SwiftUIのTextFieldはUIKitでのUITextFieldに相当する1行入力用のViewで、TextEditorはUITextViewに相当する複数行入力用のViewである。

UITextViewをUIViewRepresentableでラッピングしたView(ここでは仮にCustomTextViewとする)にフォーカスを当てても同様に自動でスクロールされない。CustomTextViewでも uiView.becomeFirstResponder() を呼んでいるが自動でスクロールしてくれないようだった。

再現環境

  • Xcode 13.1
  • iPhone X / iOS 14.7
  • iPhone 13 Pro / iOS 15.1

再現コード

最小限のコードを作成してみた。

アプリ固有の設定のせいでおかしいのかと思ったが、導入しているライブラリ等を除外しても発生したのでデフォルトで発生する問題のようだ。

import SwiftUI

struct ContentView: View {
    
    @State private var singleLineText: String = ""
    @State private var multipleLineText: String = ""
    
    var body: some View {
        List {
            Section {
                ForEach(0 ..< 20) { _ in
                    Text("ああああああ")
                }
            }

            Section {
                TextField(
                    "プレースホルダー",
                    text: $singleLineText
                )
                TextEditor(
                    text: $multipleLineText
                )
            }
            
            Section {
                ForEach(0 ..< 20) { _ in
                    Text("ああああああ")
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {

    static var previews: some View {
        ContentView()
    }
}

「プレースホルダー」と表示されたTextFieldにフォーカスを当てると、ソフトキーボードが表示されてListが自動でスクロールされる。

f:id:ch3cooh393:20211113220053p:plain
TextFieldにフォーカスを当てると自動でListがスクロールされる

しかしTextEditorにフォーカスを当てても自動でスクロールしてくれない。

isScrollEnabledをfalseに設定しておくと、フォーカスが当たったタイミングで自動でスクロールしてくれる

UITextViewをUIViewRepresentableでラッピングしたViewの場合は、makeUIView(context:)のタイミングで textField.isScrollEnabled = false に設定しておくと、フォーカスが当たったタイミングで自動スクロールしてくれることがわかった。

SwiftUI-Introspectを使って、TextEditorの内部に使われているUITextViewを取り出して textView.isScrollEnabled = false を指定すれば良いと思うが、将来的にメンテナンスしていくアプリでSwiftUI-Introspectは使いたくない。SwiftUI-Introspectを使った場合はこんな感じで対応できると思う。

TextEditor(text: $multipleLineText)
    .introspectTextView { view in
        view.isScrollEnabled = false
    }

TextEditorの中身を取り出す方法はほかにも「[Swift] SwiftUI の TextView から UITextfield を取り出す - Qiita」もあるが、SwiftUI-Introspectと同様の理由が使いたくない。

Xcode 13.1でAdHocバイナリのエクスポート中にXcodeがクラッシュする問題 〜無限ビルド編〜

M1チップ(Apple Silicon)搭載のMacBook Pro (16-inch, 2021)に移行した。社内の開発環境の足並みを合わせるため、すべてのアプリで Rosettaを使わずに実機およびiOSシミュレータでデバッグ実行できるようにしたかった。

使用しているライブラリやソースコードの量の少ない個人開発アプリでは手間が掛かったものの、比較的簡単にM1 Macの実機・iOSシミュレータでデバッグ実行ができるようになった。

お仕事で開発しているアプリは、ほとんどが年代物な上にたくさんのライブラリを導入している関係で、M1で動かすのに試行錯誤するハメになった。.xcframeworkを使いたいので、Carthageに対応しているライブラリはすべてCarthageでインストールするように切り替えていった。

実機でのデバッグ実行とアーカイブの対応が終わったが、どうしてもiOSシミュレータでの実行ができなくて頭を捻っていた。翌日同僚に助けてもらってiOSシミュレータで実行できるように設定を見直してもらった🌻🌷

これでM1 Macへの移行が完了したと思っていたのだが、AdHocバイナリをエクスポート中にXcodeがクラッシュする問題が発生してしまい、文化の日は一日ビルドして過ごすことになった。

AdHocバイナリのエクスポート中にXcode.appがクラッシュする問題が発生した

現象としては以下の通りである。

  • 実機デバッグできる
  • iOSシミュレータで実行できる
  • アプリのアーカイブに成功する
  • AppStoreバイナリのエクスポートができる
  • AdHocバイナリのエクスポート中にXcode.appがクラッシュする ← なぜ?

クラッシュ時のログが表示されるが何が悪いのかまったくわからない。

Crashed Thread:        9  Dispatch queue: ConcurrentQueue: -[IDEDistributionPackagingStepViewController viewDidInstall]_block_invoke

Exception Type:        EXC_CRASH (SIGABRT)
Exception Codes:       0x0000000000000000, 0x0000000000000000
Exception Note:        EXC_CORPSE_NOTIFY

Application Specific Information:
UNCAUGHT EXCEPTION (NSInvalidArgumentException): -[NSNull path]: unrecognized selector sent to instance 0x21ff58340
UserInfo: (null)
Open FDs: 61/9472
Hints:
0: Calling block provided by:
0   DVTDispatchAsync (in DVTFoundation)
1   DVTAsyncPerformBlock (in DVTFoundation)
2   -[IDEDistributionPackagingStepViewController viewDidInstall] (in IDEKit)
3   -[DVTViewController _viewDidInstall] (in DVTViewControllerKit)
4   -[_DVTViewController_ViewLifecycleInterpositions viewDidMoveToWindow] (in DVTViewControllerKit)
5   -[NSView _setWindow:] (in AppKit)
6   -[NSView addSubview:] (in AppKit)
7   -[NSView setSubviews:] (in AppKit)
8   -[DVTBorderedView setContentView:] (in DVTUserInterfaceKit)
9   -[IDEDistributionAssistantWindowController setDistributionStepViewController:] (in IDEKit)
10   -[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:] (in Foundation)
11   -[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:] (in Foundation)
12   _NSSetObjectValueAndNotify (in Foundation)
13   -[IDEDistributionAssistantWindowController next:] (in IDEKit)
14   __79-[IDEDistributionAutomaticSigningAssetsStepViewController _locateSigningAssets]_block_invoke_2 (in IDEKit)
15   __DVT_CALLING_CLIENT_BLOCK__ (in DVTFoundation)
16   ___DVTAsyncPerformBlockOnMainRunLoop_block_invoke (in DVTFoundation)
17   __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ (in CoreFoundation)
18   __CFRunLoopDoBlocks (in CoreFoundation)
19   __CFRunLoopRun (in CoreFoundation)
20   CFRunLoopRunSpecific (in CoreFoundation)
21   RunCurrentEventLoopInMode (in HIToolbox)
22   ReceiveNextEventCommon (in HIToolbox)
23   _BlockUntilNextEventMatchingListInModeWithFilter (in HIToolbox)
24   _DPSNextEvent (in AppKit)
25   -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] (in AppKit)
26   -[DVTApplication nextEventMatchingMask:untilDate:inMode:dequeue:] (in DVTKit)
27   -[NSApplication run] (in AppKit)
28   -[DVTApplication run] (in DVTKit)
29   NSApplicationMain (in AppKit)
30   start (in dyld)

Stack Overflowで調べてみると、AdHocバイナリをexoprt中にXcodeがクラッシュする不具合は昔からよくあるみたい。ただ「これ」をしたら治るって解決策がなにひとつ掲示されていない。

思い当たることをひとつずつ対応してビルド&エクスポートできるかを確認していくことになった。無限ビルド編である。

解決編(?)

色々試したが本不具合に対しては関係がなかったようで、AdHocエクスポート中にXcodeがクラッシュした。

  • Carthageのキャッシュをすべて削除して、.xcframeworkを作成しなおす
  • pod deintegrate してからの pod install を実行する
  • DerivedDataを削除して、macOSの再起動する
  • Provisioning Profileをすべて削除、現在使っている証明書をrevokeして、新しく証明書を作成、Provisioning Profileも作り直す

最終的に.xcframeworks の依存関係を修正することで問題が解決した。

当該アプリではプッシュ通知の管理にOneSignalを使っている。OneSignalは、アプリにNotification Service Extensionを追加して利用することになっている。

documentation.onesignal.com

アプリでは実行時にOneSignal SDKに対してユーザーIDを設定する必要がある。よって依存関係として下記のように設定していた。

  • app
    • (Embedded Frameworkとして) OneSignal.xcframework
  • notification_service_extension
    • (Embedded Frameworkとして) OneSignal.xcframework

しかし実際にはextensionはappにEmbedded Frameworkとして含まれるので、下記のような依存関係になっていた。

  • app
    • (Embedded Frameworkとして) OneSignal.xcframework
    • (Embedded Frameworkとして) notification_service_extension
      • (Embedded Frameworkとして) OneSignal.xcframework

このためAdHocエクスポート時にXcodeがクラッシュする謎の現象が発生していた。AppStoreエクスポートや実機でのデバッグ時になぜクラッシュしないのかは不思議である。下記のように依存関係を修正することでエクスポート時にXcodeがクラッシュする現象を回避することができた。

  • app
    • (Embedded Frameworkとして) OneSignal.xcframework (Embed & Sign)
    • (Embedded Frameworkとして) notification_service_extension
      • (Embedded Frameworkとして) OneSignal.xcframework (Do Not Embed)

無限ビルド編、終わり。

2021/11/09追記

anzさんも同様の問題に引っかかっていたみたいで、類似ケースとしてリンクを置いておきます。

blog.anzfactory.xyz

ankurp/DollarはCarthage経由でインストールするとApp Store Connectへのアップロード時にERROR ITMS-90056が発生する

Swiftで配列を操作するのに ankurp/Dollar を使っている。

利用しているライブラリをxcframeworkに変更する一連の対応のなかで、DollarをCarthage経由でインストールするように変更したところ、出力したipaファイルをApp Store Connectへアップロードする段階になってからエラーが発生するようになった。

*** Error: Error uploading '/Users/vagrant/deploy/MY_APPLICATION.ipa'.
*** Error: ERROR ITMS-90056: "This bundle Payload/MY_APPLICATION.app/Frameworks/Dollar.framework is invalid. The Info.plist file is missing the required key: CFBundleVersion. Please find more information about CFBundleVersion at https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleversion" (-18000)

調査したところ、DollarのInfo.plistに書かれているCFBundleVersionの値は下記のようになっていた。

 <key>CFBundleVersion</key>
    <string>${CURRENT_PROJECT_VERSION}</string>

Xcode 12時点での正しい記述は下記の通りと思われる。

 <key>CFBundleVersion</key>
    <string>$(CURRENT_PROJECT_VERSION)</string>

同じ問題がissuesにすでに存在しておりPull Requestも作成されたようだがクローズされていた。特にPR作者とライブラリ作者が議論を交わした様子もなかったので、なぜクローズされたかは不明である。ひょっとしたかこの方法だけではなんらかの問題があるのかもしれない。

そもそもとしてDollarは公式にはCarthage経由でのインストールは推奨されていない。CocoaPods経由でインストールした方が良さそうなので、うちのアプリではCarthage経由でインストールするのはやめた。

ASO、何をやれば良いかわからん (2021年1月版)

つい先日App Storeに掲載しているスクリーンショットを更新したばかりですが「ASOをきちんとした方が良い」と言われた。ストアのスクショを更新しただけではASOをしたことにならないらしい。

ASOとは「Application Store Optimization」の頭文字をとった略称でアプリストア最適化のこと。いわゆるアプリストア版のSEO*1のようだ。

SEOにまったく触れてこなかった人生だったけど、Twitter上の個人アプリ開発者が「ASO!ASO!」と言っているのに対して、「そんなに効果あるものなの?」と興味を持って着手する気になった。

本エントリでは「ASOって何をすればいいのかまったくわからん」状態のさくさんが、2021年1月にASOをするにあたって実施したことをメモっていきたいと思う。

効果が出るのにしばらくかかるとのことなので、3ヶ月後か半年後くらいにどうなったのか結果も貼りたいと思う。

ASOってなんだろう?

「まずASOってなに?」から入りたいと思う。

僕はASO専門家ではないしSEOはからっきしなので対比しながらのうまい説明はできないけれど、ASOにおいて重要視されるのは下記の2点のようだ。

  • 特定のキーワードでアプリストアの検索にひっかかりやすくする
  • ユーザーにインストールしてもらいやすくする

SEOと違ってASOは表現方法の選択肢がない。

Apple (またはGoogle)から指定されているフォーマット(テキスト or 画像)で、文字数制限・サイズ制限もあるため、SEOをかじったことがなくても手をつけやすそうだと感じた。

ASOとは 文章を変えて検索ランキングをチェックを繰り返すこと

後者はユーザーの行動なので簡単に改善するのは難しいかもしれない。

前者についてはキーワードを盛り込み、まずは検索対象にすること。次に検索ランキングをあげることが重要。こちらは定量的に評価が可能なのでとっつきやすそうです。つまりはこういうこと?

ASOとは 「掲載する文章を変えて検索ランキングのチェックを繰り返すこと」 と雑に理解した。

現状を把握する

今回も「Four Cropper」を題材にしたいと思います。

「Four Cropper」は大きな写真をTwitterに掲載するのに適切なアスペクト比に切り抜いて四分割する超画期的なアプリなのですが、毎日使うアプリではないので1日10人が使うか使わないかみたいなレベルのユーザー数しかいません。でも繰り返し利用するタイプのアプリではないので仕方がない

Four Cropperのアクティブユーザー数のグラフを赤裸々に公開します。2020年6月1日から現在までの集計となっています。今回の対策でここがギュイーンって増えてたら対応の方向性は合っていたと思って良いかな。

ユーザー維持率 (維持率コホート)です。右上の濃い青色になれば継続してアプリを使ってもらえているという判断ができます。常用するアプリではないので低い維持率は仕方がないかな……。

さらにApp Storeでの検索結果の順位もメモっておきます。思ったよりも自分のアプリが検索に引っかからないことに驚いた。「4分割」では検索順位で1位をとっているけれど、他の単語では検索にも引っかからない。

  2021/01/19
4分割 1
四分割 検索対象外
画像 4分割 検索対象外
写真 4分割 検索対象外
画像 四分割 検索対象外
写真 四分割 検索対象外
Twitter 分割 3
Twitter 写真 検索対象外
Twitter グリッド 検索対象外
Twitter 4分割 3
Twitter 四分割 検索対象外
split twitter 6
twitter split 6

検索順位で1位を取るのが目的よりかは、「検索対象外」の項目を減らしていくのが今回の主目的になりそうだ。Appleは「4分割」と「四分割」を別単語として認識しているようだ。そりゃそうか……

App Storeでの入力欄

App StoreでASOに関わってくる入力欄は以下の通りです。すべてフォーマットは テキスト or 画像 となっています。

  • タイトル
  • サブタイトル
  • キーワード
  • アイコン
  • スクリーンショット
  • 概要 (検索順位への影響度は低いらしい)

現在Four Cropperではどのように入力しているのかを下図に示します。

今回の一番の目的は「検索結果にひっかかりやすくする」なのでアイコン・スクショのことは一旦忘れたいと思う。以下の項目を実例をあげながら変更していきたい。

  • タイトル
    • Four Cropper
  • サブタイトル
    • 大きな写真を4分割しよう!
  • キーワード
    • 写真の分割, Twitter, 写真の加工, 4Cropper, 4分割
  • 概要
    • 長いのでここでは省略

さくさんが実施したこと

App Store上のテキストデータを書き換えていくといっても修正の方向性・指針が定まらないとどう変更してよいのかわからない。お手本を見つけてそれに沿って変更していきたいと考えググってみた。

具体的なASOの変更方法

ASOについて調べていくと「https://note.fourm.jp/n/n3fe63652dd43」の「ASO確認チェックシート」がとても参考になりそうだ。チェックシートを試してみたところ下記の結果となった。

Aが取れていないのでダメダメっぽい評価だった。書かれている文字数は実際に試すと分かるんだけど超長い!こんなに文字数限界まで使うものなのね……。ローカライズどうするんだろう。

このチェックシートには「x文字以上でなになにをすること」と書かれているので、それらに従いながら変更していくことにした。

タイトル

25〜30文字で、アピールしたい単語を入れると良いらしい。アピールしたい「写真」「四分割」「簡単」を入れることにした。

  • 旧:Four Cropper
  • 新:Four Cropper 写真を選ぶだけで簡単に四分割できる

タイトルにアプリ名以外を入れているとリジェクトされそうな気がするけれど大丈夫かな?

サブタイトル

25〜30文字で、タイトルと違う単語を入れると良いらしい。タイトルでは「写真」「四分割」を含むことにしたので、サブタイトルでは「画像」「4分割」を含めるようにした。

  • 旧:大きな写真を4分割しよう!
  • 新:大きな画像を4分割してTwitterでシェアしよう!

写真を四分割してTwitterに投稿する感を高めた。

キーワード

96文字以上で、タイトル/サブタイトルと違う単語を設定すると良いらしい。なお「写真の分割」などの2単語以上から構成されるキーワードは入れても無駄になるらしい。

  • 旧:写真の分割, Twitter, 写真の加工, 4Cropper, 4分割
  • 新:写真,画像,ツイッター,Twitter,加工,4Cropper,Split,4分割,四分割,分割,cropping,トリミング,シェア,切り取り,ツイート,WeChat,微信,Weibo,微博

思いつく限りの単語を入れてみた。キーワード欄ってこんなにいろんな単語入れるところだったんだ……。

同じ単語を繰り返しても効果が薄れてしまうらしいが単語を思いつかない。最後の方はとても厳しい。

概要(説明文)

2800文字以上で、狙った単語を5回以上繰り返し使用すると良いらしい。ただ今現在概要欄のテキストは検索対象から外れているようで、ここはApple(検索エンジン)向けというよりも、アプリを探しているユーザー向けに向けて変更するようだ。

旧 (280文字)

このアプリを使えば、1枚の写真を4分割してシェアできる!

とっておきの1枚をSNSにアップロードしても勝手に小さくリサイズされてしまう…… でも大丈夫!
写真をあらかじめ4分割しておくことで写真を大きく表示します。

Twitterでは16:9の比率で画像が表示されています。Four CropperでもTwitter上で綺麗にあなたの写真を表示できるように16:9の比率にクリッピングしてから写真を分割しています。

スマートフォンのカメラの発展はすさまじく、写真は毎年高解像度になっています。あなたが撮影した自慢の写真を4分割して、省略されてしまう細部までみんなに見てもらおう!

旧でも文字数を多く感じるけれど、実際にはこの10倍の文章量にしないといけないようだ。

新(470文字)

写真を選ぶだけで4分割して簡単にTwitterへ直接共有できる!わざわざ保存してからTwitterアプリで選択する必要はありません。

とっておきの1枚をSNSにアップロードしても勝手に小さくリサイズされてしまう…… でも大丈夫!
写真をあらかじめ4分割しておくことで写真を大きく表示します。

Four Cropperの原理はとても簡単でシンプルなものです。 TwitterのWebやアプリでは基本的に16:9の比率で画像を表示されています。画像を手動で計算しながら四分割することもできますが、Twitterプラットフォーム上であなたの写真が綺麗に表示できるように、Four Cropperでは写真を16:9の比率でクリッピング(トリミング)してから写真を分割します。

写真の分割数は4分割・3分割・2分割と任意の数を選択することができます。あなたの写真がもっとも映える分割数を選択してください。

スマートフォンのカメラの発展はすさまじく、写真は毎年高解像度になっています。あなたが撮影した自慢の写真を4分割して、省略されてしまう細部までみんなに見てもらおう!

頑張って文字数を増やしたけど、これ以上増やすのは厳しいので諦めた。また思いついたら変更したいと思う。

まとめ

以上で、さくさん初めてのASO対策は終わりだ。キーワード等が浸透するのを待って、検索結果がどうなったかの経過報告をしたいと思う。それではまた3ヶ月後くらいに。

(2022/06/18追記) ASO対策してから1年半が経過した

それではまた3ヶ月後くらいに。

まとめ部分には3ヶ月後に追記することにしていたが、ASO対策したのに満足してしまい、その後の結果を書くのを忘れていた。

「Four Cropper」はなぜか南米・北欧でのユーザー数が多い。ロシア人は「クソアプリなので星1つです」とレビューによく書くのでユーザー数が多いイメージだったが、日本人ユーザーとほぼ同数である。経済制裁が関係しているのかわからないが、ウクライナ侵攻後はロシア人のレビューがなくなった気がしている。原因はよくわからないが Google Play にアクセスできなくなったのかな?

日本・ロシアよりも多いのがブラジルを含む南米のユーザー数である。ブラジルには日本のほぼ3倍のユーザー数がいる。たま〜〜にエゴサーチしているのだが、容姿が綺麗で自撮り写真をアップロードしている層とBTSを初めとする韓国アイドルのファン層が、アプリ名をツイートしているのを見かける。最初の時点からスペイン語・ポルトガル語に対応したのがよかったのかもしれない。

まずはアクティブユーザー数である。1日の利用者数が1年半前と比較して 14人 から 219人 と約16倍増加していた。30日単位では、197人から 6009人と 約30倍増加している。

次に維持率コホートである。右上が濃い青色になれば長期間愛用してもらえていると判断できる。意外とこの FourCropper を愛用している方がいるようで青色が濃くなっていた。

最後にキーワード検索順位も調査した。いままで検索対象外だったキーワードでも該当するようになり、上位に表示されるようになった。これはユーザーの目につきやすくなったと言えるだろう。

  2021/01/19 2022/06/18
4分割 1 3
四分割 検索対象外 検索対象外
画像 4分割 検索対象外 検索対象外
写真 4分割 検索対象外 1
画像 四分割 検索対象外 15
写真 四分割 検索対象外 検索対象外
Twitter 分割 3 2
Twitter 写真 検索対象外 検索対象外
Twitter グリッド 検索対象外 3
Twitter 4分割 3 2
Twitter 四分割 検索対象外 1
split twitter 6 2
twitter split 6 2

参考記事

メディアハッカーの記事。ASOのチェックシートはとても役に立ちました。

https://note.fourm.jp/n/n3fe63652dd43note.fourm.jp

ASO対策を初めてその結果を記事にしている。ASOはこうしたらよい的な記事はたくさんあるが、実例と結果をあげてる記事は驚くほど少ない。これらの記事はその希少な記事のひとつ。

*1:Search Engine Optimization・検索エンジン最適化

AdMobの広告メディエーションを使って複数のアドネットワークを利用する

アプリ広告といえば AdMob ですね。Four Cropperでは画面のフッターに AdMob のバナー広告を表示させるシンプルな実装となっています。

Four Cropper

Four Cropper

  • KENJI WADA
  • ユーティリティ
  • 無料

「Four Cropper」は大きな写真をTwitterに掲載するのに適切なアスペクト比に切り抜いて四分割する超画期的なアプリなのですが、毎日使うアプリではないので1日10人が使うか使わないかみたいなレベルのユーザー数しかいません。理由は不明ですが一時期フィリピンで流行ってたみたいです。

ユーザー数の少なさと1機能といった簡単な作りのため変更がしやすく、UIKitからSwiftUIへの移植作業など個人的な実験するのに使っています。

年末年始には バナー広告の AdMob メディエーション対応をしました。

AdMob メディエーションとは

AdMob メディエーションについて、詳しく知りたい方は AdMobのヘルプページをご参照ください。

メディエーション グループは、広告ユニットで得られる収益の最適化を目的とするターゲット設定の組み合わせです。事前にメディエーション グループを作成しておけば、広告ユニットと広告ソースをいつでも追加できます。すべて一度に作成することも可能です。メディエーション グループに対してメディエーションを一度設定すれば、あとはそのグループに広告ユニットを追加するだけです。広告ユニットごとに繰り返しメディエーションを設定する必要はありません。

簡単に説明すると、アプリ開発者と取引している広告配信会社は AdMob だけではなくて、広告配信会社A・広告配信会社B など複数存在しています。これらを広告ソースと呼びます。

AdMob向けの広告実装さえしておけば、別の広告ソースの広告を表示させることができるのが「AdMobメディエーション」です。

アプリ側での実装

すでに下図のようにAdMobのバナー広告を表示できていると仮定します。

f:id:ch3cooh393:20210116142125p:plain

Nend(AppBank Network)、Facebook Audience Network、MoPub(Twitter) を広告ソースに追加します。CocoaPods経由で下記の関連ライブラリをインストールします。

  pod 'Firebase/AdMob'
  pod 'GoogleMobileAdsMediationNend'
  pod 'GoogleMobileAdsMediationFacebook'
  pod 'GoogleMobileAdsMediationMoPub'

AdMobのバナー広告はこのような実装になっているかと思います。

let banner = GADBannerView(adSize: kGADAdSizeBanner)
banner.adUnitID = "広告ユニットのID"
banner.rootViewController = self
banner.load(GADRequest())

広告ソースを追加します。

// for AdMob
import GoogleMobileAds

// for Nend
import NendAdapter

// for Facebook Audience Network (FAN)
import FBAudienceNetwork
import FacebookAdapter

// for MoPub (Twitter)
import MoPub
import MoPubAdapter

static func requestAds() -> GADRequest {
    let request = GADRequest()

    let extrasNend = GADMAdapterNendExtras()
    extrasNend.nativeType =  GADMAdapterNendNativeType.normal
    request.register(extrasNend)
    
    let extrasFacebook = GADFBNetworkExtras()
    extrasFacebook.nativeAdFormat = .nativeBanner
    request.register(extrasFacebook)
    
    let extrasTwitter = GADMoPubNetworkExtras()
    request.register(extrasTwitter)
    
    return request
}
--- banner.load(GADRequest()) // AdMobのみ
+++ banner.load(requestAds()) // AdMob + Nend + FAN + MoPub

アプリ側の対応はこれだけで完了です。

あとはAdMobの広告ユニットと広告ソースごとの広告ユニットIDと関連付けすれば完了です。広告ユニット同士を関連付けする方法は AdMob が用意するそれぞれの方法をご参照ください。

備考

Nendについて

アプリの審査はゆるい。

アプリの言語設定が日本語以外にすると広告が表示されません。広告の在庫がないのかな?

AdMobメディエーションには国別に広告ソースを切り替える機能が備わっているので 日本以外の国で nend を使わないとしていても、国=言語ではないのでアクセス元が日本であれば nend が割り振られてしまう。言語設定が日本語以外であれば広告リクエスト時に GADMAdapterNendExtras を追加しない方が良いかもしれません。

MoPubについて

アプリの審査はゆるい。

スマートバナーではない 320x50サイズのバナー広告の場合、なぜか view の左側に寄ってしまう問題がある。NendやAdMobの場合には中央揃えになるが、MoPubだけ変な表示になってしまう。

バナー広告の表示前にMoPub SDKの初期化を走らせておかないと、バナー広告表示時にアプリがクラッシュしてしまう罠がある。

Facebook Audience Networkについて

AdMobよりもeCPMが高い。

アプリごとに審査されるが審査基準は厳しめ。

CropViewController が v2.6.0 になって Deployment Target が iOS 12.0に設定されているようになった

iOSアプリで画像をクリッピングすると言えば、利用するライブラリはほぼ TOCropViewController 一択です。

github.com

TOCropViewControllerには Objective-C向けのTOCropViewController と Swift向けの CropViewController との両方のライブラリが混在しています。僕が使っているのは CropViewController の方です。Carthage経由で CropViewController をインストールしています。

5日前に TOCropViewController の v2.6.0 がリリースされました。v2.6.0での大きな変更点は Swift Package Manager (SPM)に対応したところです。その影響か CropViewController の Deployment Target が iOS 12.0 になってしまいました。

トップページには今でも iOS 8以上対応と書かれているので間違いだと思いたい……ちなみに該当のIssueは以下の通りです。

github.com

現在開発中のアプリの Deployment Targetは iOS 11.4 なので、当然ビルドエラーが発生するようになってしまいました。

/PATH/ViewController.swift:13:8: Compiling for iOS 11.4, but module 'CropViewController' has a minimum deployment target of iOS 12.0:
/PATH/Carthage/Build/iOS/CropViewController.framework/Modules/CropViewController.swiftmodule/arm64-apple-ios.swiftmodule

解決方法その1:アップデートしない

一番簡単な解決方法は一つ前のバージョンを使うことです。

...
github "yhirano/LicensePlistViewController"
github "TimOliver/TOCropViewController" == 2.5.5
github "dzenbot/DZNEmptyDataSet"

解決方法その2:TOCropViewControllerを使う

TOCropViewController の方を使う。CropViewController 向けの実装ができているのであれば、少しの変更で対応が可能です。

解決方法その3:アプリのDeployment Targetをあげる

日本のスマホユーザーの大半が使っている「LINE」は、2021年1月4日現在 iOS 12.0〜 がサポートされているバージョンになります。アプリの Deployment Targetを iOS 12以上にしても問題ないと判断できそうです。

Bitrise のXcode 12.3.xスタックでビルドしたバイナリが App Store へアップロードできない

12月15日に Xcode 12.3 Release版がリリースされました。当日中にXcodeをアップデートして「iOSシミュレータの画面が黄色くなる不具合」など踏んでStack Overflowに初めての書き込みをしたことなど今となっては良い思い出です。

Bitriseとは

今年に入ってからこのブログでも、Bitriseを使ってiOSアプリのビルド時間の短縮に挑戦する記事Firebase iOS SDKをCocoaPods経由でインストールしたときにどれだけビルド時間が伸びるかなど紹介してきました。

弊社ではプロジェクトごとにじわじわとJenkinsからBitriseへの移行がおこなわれていましたが、現時点で完全にBitriseへの移行が完了しています。関係者が多いプロジェクトだとProvisioning Profile(プロビジョニングプロファイル)の更新が頻繁に発生して、更新処理が大変だったので随分と負荷が下がりました。Jenkinsおじさんは引退です。

もしBitriseを使ってみたい方がいれば↓のリンク経由でアカウントを作ってみてください。

ビルド時間が5分伸びるみたいです!(宣伝)

Bitriseでビルドしたバイナリが AppStore へアップロードできない

さて、本題です。

手元で Xcode 12.3 を使ってビルドが通ることが確認できたので、Bitriseでのスタックを「Xcode 12.3.x, on macOS 10.15.6 (Catalina)」へ変更しました。「Bitriseのスタック」は「ビルド環境」と読み替えていただいても問題ないかと思います。

このXcode 12.3.xスタックでビルドしたバイナリをアップロードしたところ、App Storeからエラーが返ってきました。

2020-12-17 00:10:34.983 altool[4011:33028] *** Error: Error uploading '/Users/vagrant/deploy/MY_APPLECATION.ipa'.
2020-12-17 00:10:34.983 altool[4011:33028] *** Error: code -18000 (ERROR ITMS-90534: "Invalid Toolchain. Your app was built with an unsupported SDK or version of Xcode. If you plan to submit this build to the App Store, make sure you are using the versions listed in https://help.apple.com/xcode/mac/current/#/devf16aefe3b or later.")
Uploading IPA failed: exit status 1

エラー内容は以下の通りです。

「Invalid Toolchain. Your app was built with an unsupported SDK or version of Xcode. If you plan to submit this build to the App Store, make sure you are using the versions listed in https://help.apple.com/xcode/mac/current/#/devf16aefe3b or later.」とのこと。App StoreでサポートしていないバージョンのXcodeでビルドされたバイナリと指摘を受けています。

Bitriseのシステムレポートを確認する

Bitrise側で使っている Xcode 12.3のバージョンを確認します。Bitriseではスタックのシステムレポートが出力されています。システムレポートを読むことでどんなツールがインストール済みなのか?そのツールのバージョンはどうなっているのか?を知ることができます。

Xcode 12.3.xスタックのシステムレポートは以下の通りです。

Xcodeについての記載を抽出すると下記の通り。

* Xcode Version:
Xcode 12.3
Build version 12C33

うちのMacBook ProにインストールされているRelease版は下図の通り。

f:id:ch3cooh393:20201217094109p:plain

Xcode 12.3 (12C33) と、ビルドバージョンは同じようです。

ただ調べてみたところ、Xcode 12.3のRC版とRelease版のビルド番号は同じとのことです。ここを見るだけでは判断できなさそうです。

Swiftコマンドのバージョンが異なる

僕はCarthage用にビルドしたframeworkを複数のプロジェクトで共有する「Rome」を使っています。RomeはSwiftバージョンごとに生成したバイナリを管理できるため、以下のコマンドをよく利用します。

export SWIFT_VERION=`xcrun swift --version | head -1 | sed 's/.*\((.*)\).*/\1/' | tr -d "()" | tr " " "-"`
echo ${SWIFT_VERION}

f:id:ch3cooh393:20201217100051p:plain

Xcode 12.3.xスタックでは以下の通りでした。

swiftlang-1200.0.44.1-clang-1200.0.32.28

Xcode 12.3 Release版を使っているローカル環境では以下の通りでした。

swiftlang-1200.0.45-clang-1200.0.32.28

このことから BitriseのXcode 12.3.xスタックはまだ RC版を使っているのではないかと推測することができます。

おまけ

スタックの更新状況は、Bitriseの掲示板で stack-updatesタグのついているものを見れば XcodeのRC版なのかRelease版なのかを特定することができます。

discuss.bitrise.io

swiftコマンドのバージョンを調べるなどしなくても済むのでオススメです。Bitriseの掲示板を読みましょう!

僕は Xcode 12.2.x スタックに戻しました。