Web ページから iOS アプリを起動したい - Custom URL scheme
やりたいこと
Web ブラウジングをしていて、ボタンやリンクを押すとアプリが起動する機能があります。
それを iOS で実現したい!ということで、その方法を調べました。
なお、ただ起動するだけでなく、起動時の URL も取得したかったので、その方法も含みます。
実現する方法
調べた感じでは、2 つの方法があるようです。
後者の Universal Links は iOS 9 で導入された機能のようで、UX 的にはこちらのほうが良さそうです。
ただ試すのにいろいろ制約 (SSL 接続必須とか、独自ドメインとか) があったので、今回は Custom URL scheme をやってみることにしました。
事情により Swift/Objective-C の両方でやる必要があったので、両方のやり方を紹介します。
Custom URL scheme
Custom URL scheme とは
ディープリンクとは、アプリの特定の画面に遷移(遷移先のアプリ側で実装が必要です)させることのできるリンクのことです。 iOSでは、 cm-app:// のような、アプリ固有の Custom URL Scheme を実装することで、これを実現できます。
ウェブのリンクは通常 http:// とか https:// とかで始まります。
これをアプリ固有の myapp:// とか hogehoge:// とか (これを Custom URL scheme と呼ぶらしい) にすることで、この scheme を持ったアプリがインストールされていれば起動されます。
注意点としては、以下のようなことがあげられます。
後者に関してはいろいろ対策を考えられている方もいらっしゃるようです。
今回はここまで踏み込まず、アプリの起動と、そのときの URL の取得までを行います。
アプリに Custom URL scheme を設定する
これは非常に簡単です。
プロジェクトの Info -> URL Types を開き、+ ボタンを押します。
すると下記のようになるので、ここの URL Schemes に使いたい scheme 名を入力します。
ここでは sctest としました。
リンクを作成する
これもとっても簡単です。
html に以下のように記述すれば OK です。
<a href="sctest://">Launch!!</a>
iOS 側でこのリンクをクリックすると、以下のような画面になります。
これだけで、アプリの起動までは完了です。
起動時の URL を取得する
ただ今回はこれだけではなく、起動時の URL をアプリ側で取得したかったのです。
それによってアプリの動作を変えるってことも簡単にできるので。
たとえば html ファイルのリンクを以下のようにして、ユーザ名やパスワード等をアプリに渡すことを考えます。
<a href="sctest://user:password@192.168.100.100:5555/#fragment">Launch!!</a>
Swift/Objective-C それぞれやり方が微妙に違ったので、どちらも紹介します。
Swift
AppDelegate.swift ファイル内に AppDelegate クラスがあります。
そのクラスのどこかに以下のような記述を入れます。
func application(_ application: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool { print("scheme: \(url.scheme!)") print("user: \(url.user!)") print("password: \(url.password!)") print("host: \(url.host!)") print("port: \(url.port!)") print("fragment: \(url.fragment!)") return true }
こうすると Xcode の出力部に以下のように出力されます。
なお url から取れる情報は、Apple の開発者ページが参考になります。
NSURL
実装は下記のページを参考にしました。
とても詳しく書いてあるのでおすすめです。
ディープリンク(Custom URL Scheme)でアプリを起動する
Objective-C
AppDelegate.m ファイルのどこかに以下のような記述を入れます。
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options { NSLog(@"scheme: %@", url.scheme); NSLog(@"user: %@", url.user); NSLog(@"password: %@", url.password); NSLog(@"host: %@", url.host); NSLog(@"port: %@", url.port); NSLog(@"fragment: %@", url.fragment); return YES; }
こうすると、ターミナルのログに以下のように出力されます。
Swift と同じですが、url から取れる情報は、Apple の開発者ページが参考になります。
NSURL
ログの確認は以下のページを参考にしました。
iOS Simulatorのシステムログを確認する
さいごに
思ったよりも簡単にできてしまいました。
いや〜、iOS アプリすごいですね...
Xcode で Emacs キーバインドを使いたい - Hammerspoon を使う
Hello, Xcode
仕事で iOS アプリを作ることになりました。
当然ながら開発環境は Xcode になります。
今までで最も長い組み込み開発では、Emacs を使ってきました。
Python 開発は、Spyder を使っているものの、keyhac というソフトウェアを使うことで Emacs のキーバインドにしていました。
キーマップの変更手段
El Capitan 以前は Karabiner というソフトを使って、キーマップを入れ替えるのが定番だったようです。
しかし macOS Sierra 以降では使えないとのこと...!!
後継のKarabiner Elements というソフトウェアもリリースされていますが、残念ながら Modifier (Ctrl とか Shift とか) を使ったキーリマップができないようです。
Windows で愛用している keyhac を使ってみましたが、いろいろうまくいかず断念。。
そして候補にあがってきたのが Hammerspoon です。
Hammerspoon
Hammerspoon とは
This is a tool for powerful automation of OS X. At its core, Hammerspoon is just a bridge between the operating system and a Lua scripting engine. What gives Hammerspoon its power is a set of extensions that expose specific pieces of system functionality, to the user.
基本的には Mac の自動化ツールだそうです。
Lua スクリプトで動作を記述し、それを OS に渡すというブリッジの役目をします。
API Document を見るとかなりいろいろできるのですが、今回はキーリマップのためだけに使います。
Hammerspoon のインストール
公式ページ から zip ファイルをダウンロードします。
Hammerspoon.app をアプリケーションディレクトリにコピーすれば OK です。
なお初回起動のときに、Hammerspoon への制御許可を求められます。
システム環境設定 -> セキュリティとプライバシー -> アクセシビリティで Hammerspoon にチェックを入れれば OK です。
キーバインドの実現
こちら の記事をかなり参考にさせていただきました。
というかコア部分はほとんど上記のスクリプトで、一部追加しただけです。
キーバインド
このスクリプトでは Xcode を開いているときに以下のキーバインドを実現します。
- Ctrl + Space
- Mark set
- Ctrl + W
- Cut
- Alt + W
- Copy
- Ctrl + Y
- Paste
- Ctrl + X, Ctrl + F
- Open quickly
- Ctrl + X, Ctrl + S
- Save file
- Ctrl + X, N
- 次のタブへ
- Ctrl + X, P
- 前のタブへ
- Ctrl + X, K
- タブを閉じる
- Alt + Shift + ,
- ファイルの先頭へ移動
- Alt + Shift + .
- ファイルの末尾へ移動
- Ctrl + /
- Undo
- Ctrl + '
- Ctrl + S
- Search
- Ctrl + K
- Kill line
- Alt + V
- Page up
Kaggle 初挑戦: タイタニック号の生存予測その 3 - ニューラルネットワークによる予測 -
Kaggle: タイタニック号生存予測シリーズ
- その1: データの可視化
- その2: データの補完、加工
- その3: ニューラルネットワークによる予測 <-- この記事
ニューラルネットワークとは
ニューラルネットワークとは、脳神経系をモデルにした情報処理システムのことです。学習能力を持ち、必要とされる機能を、提示されるサンプルに基づき自動形成することができます。文字認識や、音声認識など、コンピュータが苦手とされている処理に対して有効です。
いわゆる人工知能の一つのアプローチです。
実は大学時代 (もう10年前か...)、人工知能の一分野である、強化学習というのを研究していました。
当時は実装が大変で、かなり挫折してました...
それが今は Python という言語に強力なライブラリがあるおかげで、とても簡単に実装できるようになっていました。
Python で作るニューラルネットワークの本「ゼロから作る Deep Learning」を買ったので、
せっかくなのでタイタニック号生存予測をニューラルネットワークでやることにしました。
ゼロから作る Deep Learning
ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装
- 作者: 斎藤康毅
- 出版社/メーカー: オライリージャパン
- 発売日: 2016/09/24
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (13件) を見る
Amazon でも大評判のこの本、読んでみました。
たしかに説明もわかりやすく、かつ Python の実装もちゃんと書かれているので、とても理解しやすいです。
また、素晴らしいのはコードが Github で公開されていることです。
MITライセンスで自由に使えるとのことで、ありがたいです。
今回はこのソースからニューラルネットワークを使わせてもらうことにしました。
データの加工
前回の記事のように、まずはデータを加工します。
前回の記事では試行錯誤しながらデータを加工しているので、これをまとめました。
# coding: utf-8 import pandas as pd """Titanic データの前処理をする 同じディレクトリに train.csv, test.csv を置いて使う。 Available functions: wrangle_data - データを加工する。返り値は train_df, test_df (pandas のデータフレーム) """ def wrangle_data (): # Preprocess training data train_df = pd.read_csv ('train.csv') test_df = pd.read_csv ('test.csv') train_df['Title'] = train_df.Name.str.extract(' ([A-Za-z]+)\.', expand=False) test_df['Title'] = test_df.Name.str.extract(' ([A-Za-z]+)\.', expand=False) # train_df title_list = train_df['Title'].sort_values (inplace=False).unique () pclass_list = train_df['Pclass'].sort_values (inplace=False).unique () for title in title_list: for pclass in pclass_list: guess_df = train_df[(train_df['Title'] == title) & (train_df['Pclass'] == pclass)]['Age'].dropna () train_df.loc[(train_df['Age'].isnull()) & (train_df['Title'] == title) & (train_df['Pclass'] == pclass), 'Age'] = guess_df.median () # test_df title_list_t = test_df['Title'].sort_values (inplace=False).unique () pclass_list_t = test_df['Pclass'].sort_values (inplace=False).unique () for title in title_list_t: for pclass in pclass_list_t: guess_df = test_df[(test_df['Title'] == title) & (test_df['Pclass'] == pclass)]['Age'].dropna () test_df.loc[(test_df['Age'].isnull()) & (test_df['Title'] == title) & (test_df['Pclass'] == pclass), 'Age'] = guess_df.median () # test_df 'Title' == 'Ms' の補完 guess_df = train_df[train_df['Title'] == 'Ms']['Age'].dropna () test_df.loc[test_df['Age'].isnull(), 'Age'] = guess_df.median () # train_df 'Embarked' の補完 freq_port = train_df.Embarked.dropna().mode()[0] train_df['Embarked'] = train_df['Embarked'].fillna (freq_port) # test_df 'Fare' の補完 guess_df = test_df[test_df['Pclass'] == 3].dropna() test_df.loc[test_df['Fare'].isnull(), 'Fare'] = guess_df['Fare'].median() df_list = [train_df, test_df] for df in df_list: df.loc[df['Age'] <= 8, 'Age'] = 0 df.loc[(df['Age'] > 8) & (df['Age'] <= 16), 'Age'] = 1 df.loc[(df['Age'] > 16) & (df['Age'] <= 32), 'Age'] = 2 df.loc[(df['Age'] > 32) & (df['Age'] <= 48), 'Age'] = 3 df.loc[(df['Age'] > 48) & (df['Age'] <= 64), 'Age'] = 4 df.loc[(df['Age'] > 64) & (df['Age'] <= 80), 'Age'] = 5 df['Fellow'] = df['SibSp'] + df['Parch'] df['IsAlone'] = 0 df.loc[df['Fellow'] == 0, 'IsAlone'] = 1 df['Sex'] = df['Sex'].map ({'female': 0, 'male': 1}).astype (int) df['Embarked'] = df['Embarked'].map ({'C': 0, 'Q': 1, 'S': 2}).astype (int) df.loc[df['Fare'] <= 7.91, 'Fare'] = 0 df.loc[(df['Fare'] > 7.91) & (df['Fare'] <= 14.454), 'Fare'] = 1 df.loc[(df['Fare'] > 14.454) & (df['Fare'] <= 31), 'Fare'] = 2 df.loc[df['Fare'] > 31, 'Fare'] = 3 df['Fare'] = df['Fare'].astype(int) train_df = train_df.drop (['PassengerId', 'Ticket', 'Cabin', 'Name', 'Title', 'SibSp', 'Parch', 'Fellow'], axis=1) test_df = test_df.drop (['Ticket', 'Cabin', 'Name', 'Title', 'SibSp', 'Parch', 'Fellow'], axis=1) return train_df, test_df if __name__ == '__main__': train_df, test_df = wrangle_data () print (train_df.describe()) print (test_df.describe())
ニューラルネットワークによる予測
いよいよ予測をします!
まずは使用するニューラルネットワークのライブラリを Github からダウンロードしてください。
common ディレクトリだけ使うので、それだけダウンロードしても大丈夫です。
そして、以下のようにスクリプトを配置してください。
dir │ data_wrangle.py │ test.csv │ train.csv │ train_neuralnet.py │ ├───common │ │ functions.py │ │ gradient.py │ │ layers.py │ │ multi_layer_net.py │ │ multi_layer_net_extend.py │ │ optimizer.py │ │ trainer.py │ │ util.py
train_neuralnet.py は以下のソースコードになってます。
自分の Github からもダウンロード可能です。
# coding: utf-8 """titanic の生存予測を、ニューラルネットワークで行う ニューラルネットワークのサイズは MultiLayerNet の引数で決まる。 """ import numpy as np from common.multi_layer_net import MultiLayerNet import pandas as pd import matplotlib.pyplot as plt from common.optimizer import * import data_wrangle def change_one_hot_label(X): """書籍を参考。正解データを one_hot_label 形式にする""" T = np.zeros((X.size, 2)) for idx, row in enumerate(T): row[X[idx]] = 1 return T # 前処理されたデータの取得 train_df, test_df = data_wrangle.wrangle_data () # データを学習物と答えに分ける x_train = train_df.drop (['Survived'], axis=1).values t_train = train_df['Survived'].values t_train = change_one_hot_label (t_train) # ニューラルネットワークの生成。 # 隠れ層は適当に 100, 100, 100 とした。 network = MultiLayerNet(input_size=6, hidden_size_list=[100, 100, 100], output_size=2, weight_decay_lambda=0.01) # Optimizer の選択。AdaGrad() は common.optimizer 以下にある。 optimizer = AdaGrad() iters_num = 1000 train_size = x_train.shape[0] batch_size = 99 train_loss_list = [] train_acc_list = [] iter_per_epoch = max(train_size / batch_size, 1) # 学習 for i in range(iters_num): batch_mask = np.random.choice(train_size, batch_size) x_batch = x_train[batch_mask] t_batch = t_train[batch_mask] # 勾配 #grad = network.numerical_gradient(x_batch, t_batch) grad = network.gradient(x_batch, t_batch) # 更新 optimizer.update(network.params, grad) loss = network.loss(x_batch, t_batch) train_loss_list.append(loss) if i % iter_per_epoch == 0: train_acc = network.accuracy(x_train, t_train) train_acc_list.append(train_acc) # train データでどのように学習されたか図示 plt.plot (train_acc_list) # テストデータの PassengerId を退避させる test_df_id = test_df['PassengerId'] test_df = test_df.drop ('PassengerId', axis=1) x_test = test_df.values # テストデータの生存予測 output = network.predict (x_test) output = np.argmax (output, axis=1) # 生存予測を Kaggle に投稿する形に修正する output_df = pd.DataFrame (output, columns=['Survived']) output_df['PassengerId'] = test_df_id output_df = output_df.ix[:, ['PassengerId', 'Survived']] output_df.to_csv('test_ans.csv', index=False, encoding='utf-8')
さあ、ついにこれで test_ans.csv を得ました!
これを Kaggle に投稿してみましょう!
結果は....
0.7799!!
いまいち!!(笑)
まだまだ全然修行がたりないようです。
でも、こうやってデータ処理を競うっておもしろいですね。
これからも頑張ります。