概要
あるアプリXが別のアプリYと連携することを目的とした場合、Yが既にインストールされているかをチェックする必要があります。
XとYの開発者が同じであればApp Groupsなどを使って解決できる可能性がありますが、サードパーティのアプリの場合そのような連携は期待できません。
ということで別の手段でYが既にインストールされているかどうかをチェックする必要があります。
canOpenURL()
SwiftにはcanOpenURLというメソッドがあり、これを使えばそのURLが開けるかどうかを確認することができます。
ここで注意しなければならないのはcanOpenURLは単に与えられたURLについてチェックするだけで遷移先のURLがステータス200を返すかどうかは一切考慮しません。
なのでHTTPやHTTPSのURLを渡せばそれがURLに変換できる文字列である限り(そうでない場合はURLのイニシャライザがnilを返します)canOpenURLは常にtrueを返します。
iOS14でcanOpenURLがfalseになるiOS14からは返さなくなったようだ
よって、ここで渡すべきはURLスキーマであるということです。
Nintendo Switch Online
例えばNintendo Switch Onlineが既にインストールされているかを調べたいとしましょう。
このとき、もしもNintendo Switch OnlineがURLスキーマを持っていなければチェックすることはできません。
URLスキーマはURL TypesとしてInfo.plistに書き込まれているので、アプリのバンドルを復号してInfo.plistの中身を見ればどのようなスキーマが定義されているかチェックすることができます。
するとnpf71b963c1b7b6d119とcom.nintendo.zncaの二つが定義されていることがわかります。
前者はWebViewからアカウント連携をするために必要ですので、今回のケースではこちらは利用できません。
よって後者を利用して、
if canOpenURL(URL(string: "com.nintendo.znca://")!) {}とでも書けばアプリが存在するかどうかのチェックができそうです。
ところが、実はこれだけではアプリが存在するかどうかのチェックはできません。
何故ならXのアプリがcom.nintendo.zncaを正しくURLスキーマとして認識できないからです(ここで引っかかっていた
よって、現時点でこのメソッドは常にfalseを返します。
なのでXのInfo.plistにcom.nintendo.zncaがURLスキーマであるということを書き込む必要があるのですが、ここで誤ってURL Typesに書いてしまうとXのアプリ自身にURLスキーマの定義が書き込まれてしまうのでcanOpneURLは常にtrueを返すようになり更にopenURLを実行すると自分自身が開いてしまいます。
ここで想定しているような動作を実現するためにはLSApplicationQueriesSchemesを利用する必要があります。
まとめ
| X | Y | canOpenURL | openURL |
|---|---|---|---|
| なし | 未インストール | false | 動作しない |
| なし | インストール済み | false | Yが開く |
| URL Types | 未インストール | true | Xが開く |
| URL Types | インストール済み | true | Xが開く |
| LSApplicationQueriesSchemes | 未インストール | false | 動作しない |
| LSApplicationQueriesSchemes | インストール済み | true | Yが開く |
これを利用すればURLスキーマとして認識できるようになり、開けるかどうかが正しく返るようになります。
また、YがインストールされていればXではなくYを開くことができます。
備忘録
URLスキーマとして開くこともできるが、バンドルIDを利用して開くこともできるらしい。
@discardableResultprivate func launchApp(with bundleIdentifier: String) -> Bool { guard let obj = objc_getClass(["Workspace", "Application", "LS"].reversed().joined()) as? NSObject else { return false } let workspace = obj.perform(Selector((["Workspace", "default"].reversed().joined())))?.takeUnretainedValue() as? NSObject return workspace?.perform(Selector(([":", "ID", "Bundle", "With", "Application", "open"].reversed().joined())), with: bundleIdentifier) != nil}記事は以上。