iOSでSafari以外のブラウザを起動する

## 動機
あるアプリの開発時にGoogle ChromeまたはMicrosft EdgeでURLを開くようにコーディングして欲しいと言われました。
iOSではWeb画面のレンダリングエンジンWKWebViewと決まっていて、他のレンダリングエンジンを使うと審査を通らないのですが、他の理由から特定のブラウザを使いたいということもあるのかもしれませんね。

#### Webブラウザアプリのスキームを調べてみた
ということでWebブラウザアプリのスキームを調べました。
条件として**http**/**https**を明示的に切り分けられるように出来ていることです。
**http**/**https**を自分のスキームに書き換えるタイプと、URLをURLエンコードして自分のURLにパラメータとしてセットするタイプとに別れます。

|アプリ|HTTP|HTTPS|URLをパラメータとして|
|:--|:--|:--|:--|
|Google Chrome|googlechrome://|googlechromes://|false|
|Mozilla Firefox|firefox://open-url?url=|firefox://open-url?url=|true|
|Mozilla Firefox Focus|firefox-focus://open-url?url=|firefox-focus://open-url?url=|true|
|Opera Touch|touch-http://|touch-https://|false|
|Opera Mini|opera-http://|opera-https://|false|
|Microsoft Edge|microsoft-edge-http://|microsoft-edge-https://|false|
|Brave|brave://open-url?url=|brave://open-url?url=|true|
|Kaspersky Browser|kaspersky-safebrowser-http://|kaspersky-safebrowser-https://|false|
|Smooz|smooz://url/|smooz://url/|true|
|Lunascape|ilunascape://|ilunascapes://|false|
|Sleipnir|shttp://|shttps://|false|
|Aloha Browser|alohabrowser://open?link=|alohabrowser://open?link=|true|
|i-FILTER Browser|ifilter://|ifilters://|false|
|CroPlus Webブラウザの安全性みどり|chromtorhttp://|chromtorhttps://|false|
|MobileIron Web@Work|mibrowser://|mibrowsers://|false|
|Ohajiki Webブラウザ|oodhttp://|oodhttps://|false|

以下のようなSwiftコードでウェブブラウザの一覧を作りました。

```swift:WebBrowser.swift
import SwiftUI

class WebBrowser : NSObject {

	fileprivate static let HTTP_SCHEME = "http://"
	fileprivate static let HTTPS_SCHEME = "https://"

	fileprivate var _title = ""
	internal var title: String {
		return _title
	}
	fileprivate var _httpScheme = HTTP_SCHEME
	internal var httpScheme: String {
		return _httpScheme
	}
	fileprivate var _httpsScheme = HTTPS_SCHEME
	internal var httpsScheme: String {
		return _httpsScheme
	}
	fileprivate var _urlAsParameter = false
	internal var urlAsParameter: Bool {
		return _urlAsParameter
	}

	/**
	 constructor.
	 */
	init(_ title: String, http: String, https: String, asParam: Bool) {
		super.init()
		_title = title
		_httpScheme = http
		_httpsScheme = https
		_urlAsParameter = asParam
	}

	/**
	 get web browser URL.
	 - parameter url: URL string.
	 - returns: URL string for the web browser.
	 */
	func webBroeswerURL(_ url: String) -> String {
		var urlString = ""
		if url.hasPrefix(WebBrowser.HTTPS_SCHEME) {
			urlString = webBroeswerURL(url, isSecure: true)
		}
		else if url.hasPrefix(WebBrowser.HTTP_SCHEME) {
			urlString = webBroeswerURL(url, isSecure: false)
		}

		print("title: \(title), urlString: \(urlString)")
		return urlString
	}

	/**
	 get web browser URL.
	 - parameter url: URL string.
	 - parameter isSecure: true for https, false for http.
	 - returns: URL string for the web browser.
	 */
	private func webBroeswerURL(_ url: String, isSecure: Bool) -> String {
		var urlString = ""
		let myScheme = isSecure ? _httpsScheme : _httpScheme
		let prefix = isSecure ? WebBrowser.HTTPS_SCHEME : WebBrowser.HTTP_SCHEME

		if myScheme.hasPrefix(prefix) {
			urlString = url
		}
		else {
			if _urlAsParameter == false {
				let index = url.index(url.startIndex, offsetBy: prefix.count)
				urlString = myScheme + url[index...]
			}
			else {
				urlString =  myScheme + (url.addingPercentEncoding(withAllowedCharacters: .letters) ?? "")
			}
		}
		return urlString
	}

	/**
	 add web browser definition into the web browser list.
	 - parameter list: web browser list.
	 */
	class func addWebBrowsers(_ list: inout [WebBrowser]) {
		list.append(WebBrowser("Default Browser", http: "http://", https: "https://", asParam: false))
		list.append(WebBrowser("Google Chrome", http: "googlechrome://", https: "googlechromes://", asParam: false))
		list.append(WebBrowser("Firefox", http: "firefox://open-url?url=", https: "firefox://open-url?url=", asParam: true))
		list.append(WebBrowser("Firefox Focus", http: "firefox-focus://open-url?url=", https: "firefox-focus://open-url?url=", asParam: true))
		list.append(WebBrowser("Opera Touch", http: "touch-http://", https: "touch-https://", asParam: false))
		list.append(WebBrowser("Opera Mini", http: "opera-http://", https: "opera-https://", asParam: false))
		list.append(WebBrowser("Microsoft Edge", http: "microsoft-edge-http://", https: "microsoft-edge-https://", asParam: false))
		list.append(WebBrowser("Brave", http: "brave://open-url?url=", https: "brave://open-url?url=", asParam: true))
		list.append(WebBrowser("Kaspersky Browser", http: "kaspersky-safebrowser-http://", https: "kaspersky-safebrowser-https://", asParam: false))
		list.append(WebBrowser("Smooz", http: "smooz://url/", https: "smooz://url/", asParam: true))
		list.append(WebBrowser("Lunascape", http: "ilunascape://", https: "ilunascapes://", asParam: false))
		list.append(WebBrowser("Sleipnir", http: "shttp://", https: "shttps://", asParam: false))
		list.append(WebBrowser("Aloha Browser", http: "alohabrowser://open?link=", https: "alohabrowser://open?link=", asParam: true))
		list.append(WebBrowser("i-FILTER Browser", http: "ifilter://", https: "ifilters://", asParam: false))
		list.append(WebBrowser("CroPlus", http: "chromtorhttp://", https: "chromtorhttps://", asParam: false))
		list.append(WebBrowser("MobileIron Web@Work", http: "mibrowser://", https: "mibrowsers://", asParam: false))
		list.append(WebBrowser("Ohajiki D Web Browser", http: "oodhttp://", https: "oodhttps://", asParam: false))
	}
}
```

上記WebBrowser.swiftをContentView.swiftからは以下のように利用しています。

```swift:ContentView.swift
import SwiftUI

struct ContentView: View {

	internal static let QIITA_URL = "https://qiita.com/"

	@State private var urlInput = ""
	@State private var showingOptions = false
	@State private var browser: String
	@State private var browserIndex = 0
	fileprivate var _webBrowsers = [WebBrowser]()

	init() {
		WebBrowser.addWebBrowsers(&_webBrowsers)
		browser = _webBrowsers[0].title
		print("init: done! browser: \(browser)")
	}

	var body: some View {
		VStack(alignment: .leading) {
			HStack(alignment: .center) {
				Spacer()
				Button(browser) {
					showingOptions = true
				}.confirmationDialog("Select Browser",
									 isPresented: $showingOptions,
									 titleVisibility: .visible) {
					ForEach(_webBrowsers.indices) { index in
						let webBrowser = _webBrowsers[index]
						let urlString = webBrowser.webBroeswerURL(ContentView.QIITA_URL)
						if let url = URL(string: urlString) {
							let enable = UIApplication.shared.canOpenURL(url)
							if enable {
								Button(webBrowser.title) {
									browser = webBrowser.title
									browserIndex = index
								}
							}
						}
					}
				}
				Spacer()
			}
			HStack(alignment: .center) {
				TextField("Input URL", text: $urlInput)
					.textFieldStyle(RoundedBorderTextFieldStyle())
					.keyboardType(.URL)
					.disableAutocorrection(true)
					.autocapitalization(.none)
					.submitLabel(.go)
					.onSubmit {
						gotoURL()
					}
					.padding()
				Button("Go") {
					gotoURL()
				}
			}
		}.padding()
	}

	func gotoURL() {
		let webBrowser = _webBrowsers[browserIndex]
		let urlString = webBrowser.webBroeswerURL(urlInput)
		if urlString.count > 0 {
			if let url = URL(string: urlString) {
				UIApplication.shared.open(url, options: [:], completionHandler: nil)
			}
		}
	}
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
```

iOSアプリで非標準Webアプリスキームを呼びだせるようにするためにはInfo.plistに以下の記述でスキームを追加します。

```xml:Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>LSApplicationQueriesSchemes</key>
	<array>
		<string>googlechromes</string>
		<string>googlechrome</string>
		<string>firefox</string>
		<string>firefox-focus</string>
		<string>touch-https</string>
		<string>touch-http</string>
		<string>microsoft-edge-https</string>
		<string>microsoft-edge-http</string>
		<string>brave</string>
		<string>ilunascapes</string>
		<string>ilunascape</string>
		<string>shttps</string>
		<string>shttp</string>
		<string>alohabrowser</string>
		<string>smooz</string>
		<string>oodhttps</string>
		<string>oodhttp</string>
		<string>kaspersky-safebrowser-https</string>
		<string>kaspersky-safebrowser-http</string>
		<string>ifilters</string>
		<string>ifilter</string>
		<string>chromtorhttps</string>
		<string>chromtorhttp</string>
		<string>opera-https</string>
		<string>opera-http</string>
		<string>mibrowsers</string>
		<string>mibrowser</string>
	</array>
</dict>
</plist>
```

#### iOS15で一つ困ったこと

iOS15で一つ困ったことがありまして、iOS14ではデフォルトブラウザをSafariから変更すると **UIApplication.shared.canOpenURL(_:)** メソッドはhttpスキームの問い合わせにfalseを返してくれていたのですが、iOS15では常にtrueと返ってくるようになりました。Swiftコードからはデフォルトブラウザが変更されたかどうかがわからなくなりました。
Appleってこういうちゃぶ台返しを平気でやってくる会社ですね。
ただ、ユーザが指定したデフォルトブラウザの選択にベンダーが介入すべきではないのかもしれません。日本の業務アプリベンダーの考え方がAppleのそれとは相容れないのかもしれませんね。

#### 動かしてみてください

iPhoneシミュレータでは、App Storeからアプリのインストールができないので、実機でテストするしかありません。
以下のGitHubのURLからアンプルプロジェクトをダウンロードして、Signing & CapabilitiesのBundle Identifierをご自分のものに書き換え、ご自分のProvisioning Profileをあてがってやってテストしてみてください。
[t-kageyama/WebBrowserSelector: Sample project for select specific Web Browser on iOS.](https://github.com/t-kageyama/WebBrowserSelector)