Logo
Overview

アプリがインストールされているかをチェックする方法

April 30, 2024
1 min read

概要

あるアプリ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スキーマであるということです。

canOpenURL

Nintendo Switch Online

例えばNintendo Switch Onlineが既にインストールされているかを調べたいとしましょう。

このとき、もしもNintendo Switch OnlineがURLスキーマを持っていなければチェックすることはできません。

URLスキーマはURL TypesとしてInfo.plistに書き込まれているので、アプリのバンドルを復号してInfo.plistの中身を見ればどのようなスキーマが定義されているかチェックすることができます。

するとnpf71b963c1b7b6d119com.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を利用する必要があります。

まとめ

XYcanOpenURLopenURL
なし未インストールfalse動作しない
なしインストール済みfalseYが開く
URL Types未インストールtrueXが開く
URL Typesインストール済みtrueXが開く
LSApplicationQueriesSchemes未インストールfalse動作しない
LSApplicationQueriesSchemesインストール済みtrueYが開く

これを利用すれば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
}

記事は以上。