概要
あるアプリ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を利用して開くこともできるらしい。
@discardableResult
private 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
}
記事は以上。