20200621のiOSに関する記事は2件です。

iOS 13 以降ではウェブ上でセンサー値を扱う際にユーザからの許可が必要

はじめに

iOS 12 までは、Safari の設定からセンサー値を利用していいか権限を与えていました。
iOS 13 からは仕様が変わり、サイトごとにユーザーからセンサー値の権限を JavaScript で与える必要があります。
※ 永続化は不可能、セッション(?)ごとに許可を取る必要がある
IMG_0758.PNG
ご覧の通り項目がなくなっていますね…(iOS 13.3.1 にて)

DeviceMotionEvent.requestPermission();
DeviceOrientationEvent.requestPermission();

このコードを実行するとセンサー値の権限を制御できるウィンドウが出てくるのですが
どうやらタップやクリックなどの意図的なユーザの行動から有効化の流れを作る必要があるみたいです…
iOS(特に 13 以降)でのモーションセンサー有効化 - http://dotnsf.blog.jp/archives/1076737232.html
こちらサイト様に有効化の手段は書いてあったのですが、他にも方法がないか検証してみたので共有しておきます。

ユーザに確認を取ったが駄目 ? だったパターン

1. window.confirm

if (typeof DeviceMotionEvent.requestPermission === 'function' && sessionStorage.getItem('isPermission') === null) {
    const isPermission = confirm('このサイトでは、センサー値を扱います。');

    if (isPermission) {
        const isDeviceOrientationEvent = await DeviceOrientationEvent.requestPermission();
        const isDeviceMotionEvent = await DeviceMotionEvent.requestPermission();

        // 許可したあとはまた許可が必要になるまでポップアップが出ないようにする
        if (isDeviceOrientationEvent && isDeviceMotionEvent) {
            sessionStorage.setItem('isPermission', 'true');
        }
    }
}

これでは動きませんでした。
これを禁止にしている Apple さんの意図が正直よくわからない

2. DOM でボタンを生成し、 JS 内で仮想クリックを行う

if (typeof DeviceMotionEvent.requestPermission === 'function' && sessionStorage.getItem('isPermission') === null) {
    const confirmElement = document.createElement('div');
    confirmElement.style.display = 'none';

    confirmElement.onclick = () => {
        const isDeviceOrientationEvent = await DeviceOrientationEvent.requestPermission();
        const isDeviceMotionEvent = await DeviceMotionEvent.requestPermission();

        // 許可したあとはまた許可が必要になるまで生成しないようにする
        if (isDeviceOrientationEvent && isDeviceMotionEvent) {
            sessionStorage.setItem('isPermission', 'true');
        }

        document.body.removeChild(confirmElement);
    }

    document. body.appendChild(confirmElement);
    window.onload = confirmElement.click();
}

ボタンをクリックしたイベントを呼び出して動作させればイケるんじゃないか ? と思いましたが駄目でした。
ブラウザ側でタップやクリックの動作も監視しており、それも同時に実行されてないと呼び出しできない仕様になってるんですかねえ

3. 外部ライブラリのクリック時の動作メソッドを使う

if (typeof DeviceMotionEvent.requestPermission === 'function' && sessionStorage.getItem('isPermission') === null) {
    const tingleLinkElement = document.createElement('link');
    tingleLinkElement.rel = 'stylesheet';
    tingleLinkElement.href = 'https://cdnjs.cloudflare.com/ajax/libs/tingle/0.15.3/tingle.min.css';
    document.head.appendChild(tingleLinkElement);

    tingleLinkElement.onload = () => {
        const tingleScriptElement = document.createElement('script');
        tingleScriptElement.src = 'https://cdnjs.cloudflare.com/ajax/libs/tingle/0.15.3/tingle.min.js';
        document.body.appendChild(tingleScriptElement);

        tingleScriptElement.onload = () => {
            const modal = new tingle.modal({
                footer: true
            });

            modal.setContent('<p>このサイトでは、センサー値を扱います。</p>');
            modal.addFooterBtn('Cancel', 'tingle-btn tingle-btn--default tingle-btn--pull-right', () => modal.close());
            modal.addFooterBtn('OK', 'tingle-btn tingle-btn--primary tingle-btn--pull-right', () => {
                const isDeviceOrientationEvent = await DeviceOrientationEvent.requestPermission();
                const isDeviceMotionEvent = await DeviceMotionEvent.requestPermission();

                // 許可したあとはまた許可が必要になるまでポップアップが出ないようにする
                if (isDeviceOrientationEvent && isDeviceMotionEvent) {
                    sessionStorage.setItem('isPermission', 'true');
                }

                modal.close();
            });

            modal.open();
        };
    };
}

今回、Tingle.js というモーダルプラグインを使用した際にハマった点です。
クリックイベントも取ってるだろうし、これなら大丈夫っしょ!!って思って書きましたが駄目でした。
外部ライブラリを使いたい際は気をつけたほうが良いかもしれません。
毎回毎回、自作のコンポーネントを用意できるわけではないのでこの仕様は少し困ってしまいますね…

結局うまく行ったパターン

ライブラリのメソッドは使わず addEventListener でクリックやタップ動作に対応した

if (typeof DeviceMotionEvent.requestPermission === 'function' && sessionStorage.getItem('isPermission') === null) {
    const tingleLinkElement = document.createElement('link');
    tingleLinkElement.rel = 'stylesheet';
    tingleLinkElement.href = 'https://cdnjs.cloudflare.com/ajax/libs/tingle/0.15.3/tingle.min.css';
    document.head.appendChild(tingleLinkElement);

    tingleLinkElement.onload = () => {
        const tingleScriptElement = document.createElement('script');
        tingleScriptElement.src = 'https://cdnjs.cloudflare.com/ajax/libs/tingle/0.15.3/tingle.min.js';
        document.body.appendChild(tingleScriptElement);

        tingleScriptElement.onload = () => {
            const modal = new tingle.modal({
                footer: true
            });

            modal.setContent('<p>このサイトでは、センサー値を扱います。</p>');
            modal.addFooterBtn('Cancel', 'tingle-btn tingle-btn--default tingle-btn--pull-right', () => modal.close());
            modal.addFooterBtn('OK', 'tingle-btn tingle-btn--primary tingle-btn--pull-right', () => {
                modal.close();
            });

            document.querySelector('.tingle-btn.tingle-btn--primary.tingle-btn--pull-right').addEventListener('click', async () => {
                const isDeviceOrientationEvent = await DeviceOrientationEvent.requestPermission();
                const isDeviceMotionEvent = await DeviceMotionEvent.requestPermission();

                // 許可したあとはまた許可が必要になるまでポップアップが出ないようにする
                if (isDeviceOrientationEvent && isDeviceMotionEvent) {
                    sessionStorage.setItem('isPermission', 'true');
                }
            });

            modal.open();
        };
    };
}

動くことには動きましたが、なんか微妙に納得のいかない書き方に…
IMG_BF5F9E0E6ABB-1.jpeg IMG_0760.PNG

結論

ユーザからの動作であれば何でも良いわけではなく onclick か addEventListener を使って実装しなくてはいけない
セキュリティの観点からこういう風にサイトごとに許可を取るスタイルはしょうがないとは思うんだけど
メソッドを呼んでくれる基準がよくわからないから使う側としてはすごく困るなあと…
iOS のブラウザ(というか Safari )はこんな感じの謎独自機能と草案の機能の実装スピードをもう少し早くしてくれればなあ… と最近思うことが多いです。
正直、ブラウザに関しては Android ブラウザのほうが圧勝だなあと思いますね ?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

実機テストで「The maximum number of apps for free development profiles has been reached.」のエラーが出て困った話。

カメラアプリの開発中、実機にてビルドを行うと「Unable to install "product name"」と出てきて、詳細を確認すると「The maximum number of apps for free development profiles has been reached.」のエラーが出て、実機にアプリのインストールができず困った話です。

実行環境
xcode:version 11.5
iOSバージョン:13.5.1
実機:iphone7 

お金を払って開発者アカウントにしない限り、xcodeで作成したアプリを実機に三つまでしかインストールできないとのことだったのですが、私は初めての実機テストだったためインストールしているアプリが0の状態で、エラーが発生していました。

上記のような状況で、私が解決できた方法について以下に記載いたします。

1.Window->Devices and Simulators->実機を選択してOpen Consoleボタンをクリックしてログを表示
2.アプリを実行して実機へのインストールを試みる。
3.右上の検索にMIFreeProfileValidatedAppTrackerを入力してログを絞り込む。

コンソールログのコピー.png

ログメッセージを確認すると、AppStoreから以前ダウンロードしたであろうアプリが表示されます。ここに表示されているアプリは、以下の写真のように実機ホーム画面の雲マークがついたアプリになるかと思います。

iphoneホーム画面.PNG

雲マークがついたアプリをインストールもしくは削除することで、今回のエラーは解消されました。

「非使用のAppを取り除く」という設定をONにしていると、長期間使用されていないアプリが削除され、雲マークがつくようです。

xcodeのアプリが問題ではなく、AppStoreのアプリ原因だったのは盲点で、かつ実機のログまで確認しないとエラーの原因にたどり着かないので、教えていただいた方には本当に感謝しております。

似たような状況の方の解決につながれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む