c-geru のすべての投稿

Swiftの確認事項もろもろ(2)

引き続きガリガリやってます。ブラウザのタブも一杯になってきたので、第2弾です。

・自分用のアプリでは、関数作ってたけどその時間もないので。備忘録的に。
RGBの16進数表記をUIColorのコードに変換してくれるサービス。 » ブロードヒューマンネットワーク社員ブログ

・カンタンお手軽。速度的にはどうなんすかね?
【Swift】配列内で重複する要素を除去する – Qiita

・ソートです。明示的にソートしないと、Controller間で要素順が変わってしまうと事象があったので。
【Swift】配列をソートする方法(昇順・降順・逆順) | Fussan Blog

・UITextViewでのHTML表示関連
UITextViewにHTMLを表示する(Swift3版) – Qiita
[Swift] URLエンコード URLデコード: スタジオプリズム㐧3ブログ
Decode HTML string in Swift 3 – Stack Overflow

・String操作いろいろ
[Swift3] Stringのプロパティ/メソッドのまとめ (文字列操作) – Qiita

・正規表現など
Swiftで正規表現を使って文字列を置換する – Qiita

・配列周りなど
Swiftの配列操作(map,reduce,filter) – Qiita

・extensionってなんすか?
エクステンション | Swift言語を学ぶ

・こんなことできるんですね。タブなんかを外側に表示するのに便利。
UITableViewCellの領域外にUIをはみ出させたい – Qiita

・for in もおさらい。
Swift さくっと確認したい基礎文法 [いろんなループ処理] – Qiita

・特殊文字の置換
Unescape HTML special characters of String in Swift

・TableCellの表示位置チェック
【iOS】特定のセルがUITableViewの表示領域内に収まっているかどうかを調べる

・Constraintは初めていじりました。
NSLayoutConstraintをプログラムでシンプルに書く – Qiita

Swiftの確認事項もろもろ(1)

急遽、Swiftを実戦投入(要は仕事)することになったので、諸々メモです。

・Calendarである日付を起点にいろいろ操作できるのは便利。
Swift3での日時に関する処理 – Qiita

・mapに出てくる「$0」の意味がわからなかった(笑)
特有の関数? mapとは?$0とは? – “まだ”の力 [Swift]基礎辞書

・演算子も改めて再確認。
【Swift】演算子の使い方。論理演算、比較演算、代入演算、ビット演算 | はじはじアプリ体験記

・私の場合も「nilなのは値じゃなくて変数だった」でした。
明らかにnilじゃないのに「unexpectedly found nil〜」が出る

・この辺もObjective-Cにはないので、混乱しがち。
[Swift] Optional 型についてのまとめ Ver2 – Qiita

・DateFormatterの使い方もろもろ
【Swift】Dateの王道 【日付】 – Qiita
日付操作の基本 – Swiftをはじめよう!

【CreateJS】AS3からHTML5 Canvasに書き出す場合のJavaScript(その3)2017年版

【MacOS Seria+Adobe Animate CC 2017.5リリース】
swfで作ったコンテンツをHTML5 Canvasに書き出すお勉強のその3です。

その2から随分間が空きましたが(笑)、ちょうど以前開発したAS3コンテンツをまとめてCanvas化する仕事があって、思い出しつつ調べつつ作業したので、2017年10月の時点でどのように変換したかをまとめておきます。

前提とするコンテンツは下記のようなものです。

・Flash IDE(いわゆる純正のFlashアプリケーション)で作成されたもの。
・スクリプトを外部ファイル化していない(関数などは1フレーム目にまとめて定義)。
・タイムラインとActionScriptを併用している(定義した関数や変数をフレームで呼び出すようなもの)

ちょっとしたインタラクションとアニメーションを絡めたようなコンテンツは、わざわざクラスファイルを外部化するのも面倒だし、こういう作り方してる人も多いんじゃないかと思います。FlashDevelopやFlash Bulderなんかでガリガリ作ったものは、その筋の人たちにお任せします(笑)。

0.FlaファイルをCanvas形式に変換する。

コマンド>他のドキュメント形式に変換 を選択します。

ドキュメントタイプコンバーターで「HTML5 Canvas」を選択してファイル名、保存場所を指定してOKを押します。

ドキュメントタイプコンバーターのポップアップ

※この時、全てのActionScriptはコメント化されます。「this.stop();」などそのまま使えるものもコメント化されるので要注意。

1.型は全て削除する(:uint や as MovieClipなど)。

JavaScriptにはないので当然ですね。アクションパネルなどで一括置換が漏れがなくてオススメです。

2.タイムラインのどこからでも使いたいものは、アクション>グローバル>スクリプトに書く(のグローバル化)

1フレーム目に関数(function)を書いて、別のフレーム(例えば10フレーム目)で呼び出すのは、ASではよくやりますが、これがそのままではJavascriptで動きません。その場合は、アクションパネルでアクション>グローバル>スクリプトを開いて、ココに書きます。ココに書かれた変数や関数はグローバル化されるので、this参照なしでどこからでも呼び出せます。

アクションパネルの左側から、グローバル>スクリプトを選択します。

・スクリプト例(定義:グローバル>スクリプト)

//---アニメフラグ
var flg_anime = true;

//ボタン:切替
function switchBtn(flg)
{
	if (flg)
	{
		//アニメーション開始;
		startAnimation();
	}
}

・スクリプト例(呼び出し;フレームスクリプト)

//ボタン:初期化
switchBtn(flg_anime);

※ flg_anime, switchBtn()共にグローバルなので、this等の修飾なしで使用できる。

グローバル>スクリプトの関数で、ステージ上に配置されたMovieClipやボタン、ステージのタイムラインなどを使用する場合は、exportRootで参照します。

・スクリプト例(定義:グローバル>スクリプト)

//アニメーション開始
function startAnimation()
{
	exportRoot.gotoAndPlay("f_anime");
}

3.MovieClip内の関数

タイムラインが1フレームしかないもので、MovieClip内で完結する変数、関数は、そのままthis等の参照なしでフレームに書いてOK。
・そのMovieClip内に閉じたものは、function xxx(){} で定義したものをthis.xxx()で参照すればよい。

・スクリプト例(MovieClip;フレームスクリプト)

//何か例を入れる

但しMovieClipの外部から参照する変数、関数はプロパティにする必要があります。
・this.xxx = function(){} な書き方。

//何か例を入れる

4.プロパティパネルに書いたインスタンス名は参照出来ない。

ボタンやMovieClipでインスタンス名で参照したいときは、明示的にnameプロパティに文字列を設定する必要があります。

・スクリプト例(定義:グローバル>スクリプト)

//ボタン:初期化
function inithBtn()
{
	exportRoot.btn_up_year.name = "btn_up_year";
	exportRoot.btn_up_month.name = "btn_up_month";
	exportRoot.btn_up_day.name = "btn_up_day";
}

※MovieClipなら、MovieClip内のフレームで、this.name = “xxx” でも定義できる。

5.円形グラデーションやフィルターなどは使えない。

・HTML5 Canvasに書き出すのに制限があります。ライブラリ上でビットマップに変換しておくか、場合によってはビットマップで作り直す必要があります。

6.MouseEvent.MOUSE_UP >> pressup

・ここは “mouseup” じゃないので要注意。

処理 AS JavaScript
クリック MouseEvent.CLICK click
マウスオーバー MouseEvent.MOUSE_OVER mouseover
マウスアウト MouseEvent.MOUSE_OUT mouseout
マウスダウン MouseEvent.MOUSE_DOWN mousedown
マウスアップ MouseEvent.MOUSE_UP pressup
ロールアウト MouseEvent.ROLL_OUT rollout

7.Movieclip内の関数でのthis参照

関数内でのthisを参照すると、ページのルートであるWindowを指します(詳しくは、別記事の「4.thisの参照」を参照のこと)。
MovieClip内の関数で、相対的にパスを指定したいときは、関数外で変数にthisを入れておき、それを使って参照します。

・スクリプト例(定義:MovieClip内のフレームスクリプト)

//関数内での参照用にthisを変数化
var my_mc = this;

//初期化
this.init = function ()
{	
	my_mc.open_cap.visible = false;
	my_mc.close_cap.visible = false;

}

8.MovieClipボタンのカーソル対応

“buttonMode” は”cursor”に変更します。

処理 AS JavaScript
指カーソルにする MovieClip.buttonMode = true; MovieClip.cursor = “pointer“;
デフォルトカーソルにする MovieClip.buttonMode = false; MovieClip.cursor = “default“;

9.TFLは使えない。

・強制的に普通のテキストに変換されるので、注意が必要です。

10.Dateオブジェクトの扱いに注意

ActionScriptにもJanaScriptにも同じようにDateオブジェクトがありますが、年月日時分秒を取得する際に、ActionScriptはプロパティで、JavaScriptはfunctionのため、微妙に違うので要注意。

処理 AS JavaScript
Dateオブジェクト生成 var date:Date = new Date(); var date = new Date();
年(4桁) date.fullYear date.getFullYear()
date.month date.getMonth()
date.date date.getDate()
date.hours date.getHours()
date.minutes date.getMinutes()
date.seconds date.getSeconds()

11.MovieClipのwidth,height

MovieClip.nominalBoundsで取得可能です。

・スクリプト例(定義:MovieClip内のフレームスクリプト)

this.tft07.nominalBounds.width
this.tft07.nominalBounds.height

【参考】
・X-LABO: Toolkit for CreateJS & Haxe : シンボルの初期表示サイズ・位置情報取得 nominalBounds
・Animate CCで書きだしたCreateJSのグラフィックスの色をプログラム上から変更する – Qiita

createjs – How to change the width of a movieclip in flash html5 canvas? – Stack Overflow

12.MovieClipをリストアップする

多くのMovieClipを配置して制御する場合、ひとつずつに名前をつけたりするのも大変なので、MovieClipだけを配列化して使う場合があります。

・ActioScript例

function initBit():void
{
	array_bit = [];
	
	for (var i:uint=0; i<this.numChildren; i++)
	{
		var ds:DisplayObject = this.getChildAt(i);

		if (ds is MovieClip)
		{
			array_bit.push(ds);
			ds.visible = false;
		}
	}

}

この「is MovieClip」がjavaScriptにないので、どうしようかな?と考えて、MovieClipにはtimelineが存在するので、それを代替にしてMovieClipのみをリストアップすることにしました。まあ気づけばたいした話ではないんですが。

・JavaScript例

his.initBit = function ()
{
	array_bit = [];
	
	for (var i=0; i<this.numChildren; i++)
	{
		var ds = this.getChildAt(i);

		if (!!ds.timeline)
		{
			array_bit.push(ds);
			ds.visible = false;
		}
	}
}

だいたいこんなところだと思いますが、抜けがあったらまた更新します。

以前書いたブログもよければご参考に。

【CreateJS】AS3からHTML5 Canvasに書き出す場合のJavaScript(その1) | AS blind side
【CreateJS】AS3からHTML5 Canvasに書き出す場合のJavaScript(その2) | AS blind side

【追記】
早速、12.を追記しました(2017.10.11)。

【Xcode】各種ユーザーデータを初期処理する際の注意

【macOS Sierra 10.12.6+Xcode 8.3.3+iPhoneSE(iOS 10.3.2)】

とあるアプリで、カメラロールの写真とミュージックのアートワークを使ってたんですが、何故かインストール直後にどちらも0件になってしまう事象が発生。2回目以降に起動すると使えるのに・・・。

と思って調べたら、初期処理の実行タイミングに問題がありました。

※ここでいう「初期処理」とは、各種ライブラリをこのアプリ使うための実装処理を指します。

iOS10以降は各種ユーザーデータへアクセスに許可が必要になりました。で、以前書いたようにinfo.plistにメッセージ用テキストが必須になったわけですが、これで使用許可を求めるダイアログを表示してくれますが、該当処理はダイアログによる許可を待ってくれるわけではなく、許可されないと(ダイアログのボタンを押さないと)無視されてしまうようです。

で、viewDidLoad に書いてあった写真ライブラリとメディアライブラリの初期処理が、インストール直後でも正しく実行されるように、以下のように修正しました。

    //写真ライブラリ:初回許可(未設定の場合にダイアログの選択結果を受け取って、初期処理を行う)
    PHAuthorizationStatus status_photo = [PHPhotoLibrary authorizationStatus];
    if (status_photo == PHAuthorizationStatusNotDetermined){
        // このアプリに与える権限が未選択(アプリ初回起動時)
        NSLog(@"---PHPhotoLibrary:未選択");
        
        [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
            if (status == PHAuthorizationStatusAuthorized) {
                // Access has been granted.
                NSLog(@"-----写真ライブラリ:使用OK");
                [self initIconList_photo];
            }
            else {
                // Access has been denied.
            }
        }];
        
    } else if (status_photo == PHAuthorizationStatusAuthorized){
        NSLog(@"-----写真ライブラリ:使用OK済み");
        [self initIconList_photo];
        
    }
    
    //メディアライブラリ設定:初回許可(未設定の場合にダイアログの選択結果を受け取って、初期処理を行う)
    MPMediaLibraryAuthorizationStatus status_media = [MPMediaLibrary authorizationStatus];
    if (status_media == MPMediaLibraryAuthorizationStatusNotDetermined){
        // このアプリに与える権限が未選択(アプリ初回起動時)
        NSLog(@"---MPMediaLibrary:未選択");
        
        [MPMediaLibrary requestAuthorization:^(MPMediaLibraryAuthorizationStatus status) {
            if (status == MPMediaLibraryAuthorizationStatusAuthorized) {
                // Access has been granted.
                NSLog(@"-----メディアライブラリ:使用OK");
                [self initIconList_artwork];
            }
            else {
                // Access has been denied.
            }
        }];
        
    } else if (status_media == MPMediaLibraryAuthorizationStatusAuthorized){
        NSLog(@"-----メディアライブラリ:使用OK済み");
        [self initIconList_artwork];
    }

要は、requestAuthorization でそれぞれのライブラリの使用許可ダイアログの結果を受け取って、OK(写真なら PHAuthorizationStatusAuthorized)だった場合に、初期処理(写真の場合は、initIconList_photo )を実行するということです。

ちなみに、else if で書かれているのは、既に許可済みの場合(写真だと PHAuthorizationStatusAuthorized)の場合に、初期処理を実行するためです(これがないと、後から許可した場合に初期処理が実行されない)。

各種ライブラリへのアクセス許可のダイアログは、info.plistにメッセージ用テキストを必須条件として、プログラム中で初めて該当ライブラリにアクセスしたタイミングで表示されるので、写真ライブラリとメディアライブラリのチェック順を入れ替えれば、ダイアログを出す順番も制御できます。また、特に初期処理が要らないライブラリなら、ユーザーがボタンを押したタイミングで許可を求めるダイアログを表示することもできそうです。

【参考】
iOSでカメラと写真の利用許可の確認方法[AVCaptureDevice] – Qiita
【Xcode】iOS10以降は各種ユーザーデータへアクセスに許可が必要 | AS blind side
【Xcode】iOS10で各種ユーザーデータへのアクセス許可を参照 | AS blind side
requestAuthorization: – MPMediaLibrary | Apple Developer Documentation

【Xcode】効果音と多重再生とSFSpeechRecognizer(音声認識)

作成中のアプリで使おうと思って調べたメモです。

サウンドの再生方法


調べてみると、下記の2つの方法があって、それぞれ用途によって総称されているフレームワークが違っている。

・AudioToolBoxを使う方法(効果音等短い音)
・AVFoundationを使う方法(BGM等長い音やループ)

【参考】
Objective-C:音楽(BGM)や効果音(SE)等のサウンド再生方法 | siro:chro
アプリ開発ブログ(仮): 効果音を鳴らす
【Xcode】アプリ内のボタンに効果音を付ける! | iDEACLOUD/dev

BGMと効果音を併用する際の問題点


効果音はAudioToolBox、BGMはAVFoundationで鳴らせばいいのかと思って、効果音をAudioToolBoxで組み込んで、テスト中にミュージックアプリで音楽流しながら、効果音を鳴らしたら音楽がフェードアウトしてしまった。で、調べてみたら、どうもAudioToolBoxとAVFoundationの併用に問題があるらしいことが発覚。

以下のリンクによれば、AudioToolBoxで音を鳴らすと、AVFoundationの方はフェードアウトしてしまうらしい。
なので、効果音もAVFoundationで再生することにする。

【参考】
BGMを鳴らしながら効果音再生するときの落とし穴 – iOSアプリ開発まとめWiki – アットウィキ

多重再生するための設定


更に調べてみると、多重再生させるには、以下の2つが必要とのこと。

・音声ファイルを .caf形式にする
・AVAudioSessionのカテゴリを変更する

.cafとは、Core Audio File の拡張子で、iOSおよびOS Xのネイティブのオーディオファイルフォーマットらしいです。

ターミナルから以下のコマンドで変換できます(例:sound_ok.m4a を sound_ok.caf に変換)

afconvert -f caff -d 0 sound_ok.m4a sound_ok.caf

※変換したところ、ファイルサイズは【107KB】>>【54KB】になりました。

また、AVAudioSessionのカテゴリは、デフォルトではオーディオ再生中にアプリを起動するとオーディオが停止(AVAudioSessionCategorySoloAmbient)するようになっているので、どちらも鳴らせるように設定を変更します。Objective-Cだと、AppDelegate.m にこんな感じに書きます。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // バックグラウンドでの音の再生を許可
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
    return YES;
}

【参考】
Swift:AVAudioSession アプリのバックグラウンドで音楽を再生する | siro:chro
iPhoneアプリ開発のためのサウンドフォーマットまとめ | ぱーくん plus idea
Core Audioの概要【PDF】
【iOS】音楽を止めずに効果音を同時に再生するには|てくめも@ecoop.net

SFSpeechRecognizer(音声認識)で効果音が再生されない問題


SFSpeechRecognizer(音声認識)を使ったアプリに効果音を組み込んでみたら、SFSpeechRecognizerを使って音声認識した後に、効果音が鳴らなくなった。

結果的には、SFSpeechRecognizerを使って音声認識を実行した後に、もう一度AVAudioSessionのカテゴリを変更してやることで解決しました。Objective-Cだと、こんな感じ。

// バックグラウンドでの音の再生を許可(AppDelegateで設定しているが、speechRecognizer使用後に再度設定しないと、音が鳴らない)
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];

SFSpeechRecognizer自体はここでは触れないので、下記リンクを参照して下さい。要は、SFSpeechRecognizerを使う際にAVAudioSessionのカテゴリを録音用に変更(AVAudioSessionCategoryRecord)しているので、それを戻さないと音が鳴らないということです。

【参考】
[iOS 10] SFSpeechRecognizerで音声認識を試してみた | Developers.IO

【Gulp】GulpでPNGとJPEGを圧縮する

以前、鹿野さんが書いた下記の記事をベースに作ったものをPNG,JPEGの両方に対応するように作り直してみたメモ。

5分で導入! タスクランナーGulpでWeb制作を効率化しよう – ICS MEDIA

全体的な解説は鹿野さんの記事に詳しいので、ここでは変更点のみ記します。

・PNGの圧縮は、imagemin-pngquan を使う方がいいらしいので、こっちを追加。以下のコマンドで該当フォルダにインストール。

npm install --save-dev imagemin-pngquan

・JPEGの圧縮は、imagemin-mozjpeg を使うため追加。以下のコマンドで該当フォルダにインストール。

npm install --save-dev imagemin-mozjpeg

※ちなみに「–save-dev」はinstallしたlibraryの情報を自動でpackage.jsonに書いてくれるオプションです。
npmでnode.jsのpackageを管理する – Qiita

・複数の拡張子を指定するには、{} で囲めばいいらしい(gulpfile.js 11行目)。

・PNG,JPEG共に圧縮設定を追加(gulpfile.js 12-25行目)。

これらを書き足したコードが以下です。

・gulpfile.js(全文)

// gulpプラグインの読みこみ
var gulp = require('gulp');

// 画像を圧縮するプラグインの読み込み
var imagemin = require("gulp-imagemin");
var pngquant  = require('imagemin-pngquant');
var mozjpeg  = require('imagemin-mozjpeg');

// imagesフォルダのpng,jpg画像を圧縮して、minified_imageフォルダに保存する
gulp.task("imageMinTask", function() {  // 「imageMinTask」という名前のタスクを登録
    gulp.src("images/*.{png,jpg}")    // imagesフォルダ以下のpng,jpg画像を取得
    .pipe(imagemin([
       pngquant({
         quality: '65-80',
         speed: 1,
         floyd:0
       }),
       mozjpeg({
         quality:85,
         progressive: true
       }),
       imagemin.svgo(),
       imagemin.optipng(),
       imagemin.gifsicle()
     ]
        ))   // 画像の圧縮処理
        .pipe(gulp.dest("minified_image/"));    //保存
});

実際に圧縮してみた結果は、こんな感じでした。

・PNG
(圧縮前)2534 × 3490 ピクセル 2.9 MB(2,854,228 バイト)
(圧縮後)2534 × 3490 ピクセル 1.6 MB(1,555,755 バイト)> 約55%に圧縮

・JPEG
(圧縮前)3024 × 4032 ピクセル 6.3 MB(6,335,114 バイト)
(圧縮後)3024 × 4032 ピクセル 1 MB(1,032,033 バイト)> 約16%に圧縮

【参考】
Gulpでpngquantを使ってPNGの減色&軽量化 – Qiita
gulp-imageminのプラグインによる最適化が機能しない際の対処 – Qiita

【node.js】node.js,npm,bower,gulpなどの諸々

最近この辺使うので、とりあえず個人的なリンクのまとめ。

・node.js,npm関連
npm自体のバージョンを上げる – Qiita
nodebrewでnodeとnpmのバージョン管理しよう(Mac編) | Webサプリ
Macにnode.jsを複数バージョン切り替えられるようにインストール – Qiita
nodebrewでnodeとnpmのバージョン管理しよう(Mac編) | Webサプリ
nodebrewで手軽にnode.jsバージョンアップ&バージョン切り替え – Qiita
Cordovaの環境構築 – Qiita

・bower関連
Bower(フロントエンド用パッケージマネージャー)の導入方法と使い方 | 株式会社LIG

・Gulp関連
5分で導入! タスクランナーGulpでWeb制作を効率化しよう – ICS MEDIA
ざっくりGulp.js入門(Mac向け) – Qiita
Gulpでpngquantを使ってPNGの減色&軽量化 – Qiita
gulp-imageminのプラグインによる最適化が機能しない際の対処 – Qiita

・ターミナル関連
Mac OS X ターミナルで別のボリュームへ移動するには?? – Qiita
必ず使える!Macのターミナルで使う基本UNIXコマンド15選 | NEZU.log

今更ながらPHPでFacebook API でログイン処理を書いてみた(v2.8)

【MacOS X 10.11.6 + Facebook API v2.8 + Facebook SDK for PHP v5.0】
必要があってFacebookのログイン処理をPHPで書いてみたので、そのメモです。

1.Facebookに開発者登録してサイトをアプリとして登録

Facebook APIを使うには、Facebookに開発者登録してサイトをアプリとして登録する必要があります。その辺はここでは割愛するので、ご存じない方は下記を参照のこと。
Facebook開発者登録の方法とアプリ作成方法の手順

※この辺の説明はたくさんありますが、サイトによってはキャプチャ画面が古いものもあるので、適宜読み替えが必要。

2.Facebook SDK for PHP v5.0 のインストール

以下から、手動インストール。
facebook for developers | スタートガイド – ウェブSDK
解凍後、サイトのルートに「php-graph-sdk-5.0.0/src/Facebook」をフォルダごとコピーする。

3.PHPでのログイン処理

下記のサイトを参考にしました。コールバックのURLもパス付きで書いてあって、わかりやすいです。
[Facebook SDK for PHP v5.0]を利用してログイン認証をおこなう[Oauth]

ざっと図解すると、こんな感じ。

FacebookAPIの画面遷移

以下、各phpの概要です。

・config.php

Facebook APIで使用する、アプリID,app secret,APIのバージョンを定義。実際にAPIを使用するphpに、require_onceで読み込ませるために共通化。各設定項目とダッシュボードとの対応は、以下の通り。

app_id:アプリID
app_secret:app secret
default_graph_version:APIバージョン

アプリのダッシュボード画面

・index.php

Facebook APIでアプリにログインさせる。実際にはAPI側で、Facebookのログイン及びアプリの認証の可否を確認する画面が表示されるため、ユーザーの意に反してログインされることはない。

・callback.php

Facebook APIからのコールバックを受け取るphp。ここでアクセストークンを取得し、ログインしたユーザー情報を取得する。ユーザーがログインしない/アプリを許可しない場合、取得できないのでindex.phpへのリンクを表示。取得できた場合は、member.phpへ。

・member.php

ログインしたユーザー情報を表示する。

・logout.php

アプリからログアウトする。クッキーを削除して、セッションを破棄することでログアウトになる。

4.コールバックURLをアプリの管理画面に登録

コールバックURLはアプリの管理画面に登録する。左メニューから「プロダクト」>「+製品を登録」>「Facebookログイン」選ぶ。「クライアントOAuth設定」の「有効なOAuthリダイレクトURI」にコールバックURLをフルパスで指定し、右下の「変更を保存」ボタンを押す。

Facebookログインの画面でコールバックURLを指定して、保存する。

4.アクセストークン取得にはコールバックURLの指定が必要

参考サイトのコードをそのまま使ったら、「URLを読み込めません: このURLのドメインはアプリのドメインに含まれていません。このURLを読み込むには、アプリ設定のアプリドメインにすべてのドメインとサブドメインを追加してください。」というエラーが出た。

以下のサイトを参考に、アクセストークン取得のところにコールバックのURIを指定してエラーを解消。

【PHP】 Facebook APIのOauthのエラー解決「URLを読み込めません」「Can’t Load URL」 | codechord

実際にはこんな感じに書き換える。

//アクセストークンを取得する
$accessToken = $helper->getAccessToken('https://c-geru.sakura.ne.jp/c-geru/fb_test/callback.php');

5.デモ

実際の動作デモは、こちらからどうぞ。

6.各ソース

上記4.で述べたように、アクセストークン取得(callback.php)とログイン(index.php)の2箇所でコールバックURLが必要になるため、このサンプルでは、config.phpにコールバックURLを定義して、各phpではセッションから値を取得するようにしました。

・config.php

<?php 
session_start(); 
require_once("Facebook/autoload.php"); 
//コールバックURLをセッションに保存 
$_SESSION['fb_callback_ulr'] = 'https://c-geru.sakura.ne.jp/c-geru/fb_test/callback.php'; 
$fb = new Facebook\Facebook([
  'app_id' => '○○○○○○○○○',
  'app_secret' => '△△△△△△△△△△△△△',
  'default_graph_version' => 'v2.8',
]);

・index.php

<?php 

session_start(); 
header("Content-type: text/html; charset=utf-8"); 

//設定ファイルを読み込み 
require_once("config.php"); 

//コールバックURLの取得 
$callcak_url = $_SESSION['fb_callback_ulr']; 
$helper = $fb->getRedirectLoginHelper();

//オプションによって認証画面の文言が変わる
//$permissions = ['email', 'user_likes','user_posts']; //あなたの公開プロフィール、メールアドレス、タイムライン投稿、いいね!。
//$permissions = ['email', 'user_likes']; //あなたの公開プロフィール、メールアドレス、いいね!。
//$permissions = ['email', 'user_posts'];//あなたのタイムライン投稿。
//$permissions = ['email','user_friends'];//あなたの公開プロフィール、友達リスト、メールアドレス。
//$permissions = ['email'];//あなたの公開プロフィール、メールアドレス。
$permissions = [];//あなたの公開プロフィール。
$loginUrl = $helper->getLoginUrl($callcak_url, $permissions);

echo '<a href="' . $loginUrl . '">ログインする</a>';

・callback.php

<?php 

session_start(); 

header("Content-type: text/html; charset=utf-8"); 

//設定ファイル 
require_once("config.php"); 

//コールバックURLの取得 
$callcak_url = $_SESSION['fb_callback_ulr']; 

//タイムゾーンの設定 
date_default_timezone_set('asia/tokyo'); 

$helper = $fb->getRedirectLoginHelper();

try {
	if (isset($_SESSION['facebook_access_token'])) {
		$accessToken = $_SESSION['facebook_access_token'];
	} else {
		//アクセストークンを取得する
		$accessToken = $helper->getAccessToken($callcak_url);
	}
} catch(Facebook\Exceptions\FacebookResponseException $e) {
	// When Graph returns an error
	echo 'Graph returned an error: ' . $e->getMessage();
	exit;
} catch(Facebook\Exceptions\FacebookSDKException $e) {
	// When validation fails or other local issues
	echo 'Facebook SDK returned an error: ' . $e->getMessage();
	exit;
}

if (isset($accessToken)) {
	//アクセストークンをセッションに保存
	$_SESSION['facebook_access_token'] = (string) $accessToken;
	
	header('Location: member.php');
	exit();
}else{
	echo "<a href='index.php'>はじめのページへ</a>";
}

・member.php

<?php 

session_start(); 
header("Content-type: text/html; charset=utf-8"); 
//設定ファイル 
require_once("config.php"); 

if (isset($_SESSION['facebook_access_token'])) { 
$accessToken = $_SESSION['facebook_access_token']; 
$fb->setDefaultAccessToken($accessToken);
	
	try {
		//取得するユーザ情報の指定
		$response = $fb->get('/me?fields=id,name,first_name,last_name,email,gender');
		$profile = $response->getGraphUser();
		
		//ユーザ画像取得
		$UserPicture = $fb->get('/me/picture?redirect=false&height=200');
		$picture = $UserPicture->getGraphUser();
		
	} catch(Facebook\Exceptions\FacebookResponseException $e) {
		// When Graph returns an error
		echo 'Graph returned an error: ' . $e->getMessage();
		exit;
	} catch(Facebook\Exceptions\FacebookSDKException $e) {
		// When validation fails or other local issues
		echo 'Facebook SDK returned an error: ' . $e->getMessage();
		exit;
	}
	
	$id=$profile['id'];
	$name=$profile['name'];
	$first_name=(isset($profile['first_name'])) ? $profile['first_name'] : '';
	$last_name=(isset($profile['last_name'])) ? $profile['last_name'] : '';
	$email=$profile['email'];
	$gender=(isset($profile['gender'])) ? $profile['gender'] : '';
	$picture_url = $picture['url'];
    
   
	echo "アクセストークン:".$accessToken."";
	echo "ID:".$id."";
	echo "名前:".$name."";
	echo "性別:".$gender."";
	echo "ファーストネーム:".$first_name."";
	echo "ラストネーム:".$last_name."";
	echo "メール:".$email."";//ユーザが未公開・未設定の場合は表示されない
	echo "<img src=".$picture_url.">";
	echo "<a href='logout.php'>ログアウト</a>";

}else{
	header('Location: index.php');
	exit();
}
?>

<!--#_=_を排除する-->
<script type="text/javascript">
if (window.location.hash && window.location.hash == '#_=_') {
  if (window.history && history.pushState) {
      window.history.pushState("", document.title, window.location.pathname);
  } else {
    // Prevent scrolling by storing the page's current scroll offset
    var scroll = {
        top: document.body.scrollTop,
      left: document.body.scrollLeft
    };
    window.location.hash = '';
    // Restore the scroll offset, should be flicker free
    document.body.scrollTop = scroll.top;
    document.body.scrollLeft = scroll.left;
  }
}
</script>

・logout.php

<?php
session_start();
 
header("Content-type: text/html; charset=utf-8");

//セッション変数を全て解除
$_SESSION = array();
 
//セッションクッキーの削除
if (isset($_COOKIE["PHPSESSID"])) {
    setcookie("PHPSESSID", '', time() - 1800, '/');
}
 
//セッションを破棄する
session_destroy();
 
echo "ログアウトしました。"; 
echo "<a href='index.php'>はじめのページへ</a>";

【Xcode】iOS10で各種ユーザーデータへのアクセス許可を参照

【OSX 10.11.6+Xcode 8.0 (8A218a)+iPhoneSE(iOS 10.0.2)】

前回の記事でアプリの初回起動時以外に、各種ユーザーデータへのアクセス許可をどのように参照すればいいのかを前回対象とした「写真」「マイク」「音声認識」「メディアライブラリ」についてまとめました。

※以下、コードは Objective-C です。

写真


従来の写真アクセスである「ALAssetsLibrary」でコードを書いていると・・・

'ALAssetsLibrary' is deprecated: first deprecated in iOS 9.0 - Use PHPhotoLibrary from the Photos framework instead

というワーニングが出ます。

従来の「ALAssetsLibrary」はiOS9以降非推奨になり、Photos frameworkの「PHPhotoLibrary」を使いなさいということなので、「PHPhotoLibrary」ベースでのチェック方法です(もちろん写真のアクセス自体も「PHPhotoLibrary」ベースである前提です)。

//写真設定:参照(アプリの設定で許可されているかどうか)
-(BOOL)isPermitPhotoLibrary
{
    PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
    switch (status) {
        case PHAuthorizationStatusNotDetermined:
            // このアプリに与える権限が未選択(アプリ初回起動時)
            return NO;
            break;
            
        case PHAuthorizationStatusRestricted:
            // 設定による使用制限で、アプリのアクセスの許可を変更できない
            return NO;
            break;
            
        case PHAuthorizationStatusDenied:
            // このアプリに与える権限を拒否(アプリ初回起動時「許可しない」を選択)
            return NO;
            break;
            
        default:
        case PHAuthorizationStatusAuthorized:
            // このアプリに与える権限を許可(アプリ初回起動時「OK」を選択)
            return YES;
            break;
    }
}

マイク


いくつか方法があるようですが、ここでは「AVAudioSessionRecordPermission」を使ってます。

//---マイク使用許可チェック
-(BOOL)isPermitMic{
    AVAudioSessionRecordPermission status = [AVAudioSession sharedInstance].recordPermission;
    
    switch (status) {
        case AVAudioSessionRecordPermissionUndetermined:
            // このアプリに与える権限が未選択(アプリ初回起動時)
            NSLog(@"---AVAudioSession:未選択");
            return NO;
            break;
            
        case AVAudioSessionRecordPermissionDenied:
            // このアプリに与える権限を拒否(アプリ初回起動時「許可しない」を選択)
            NSLog(@"---AVAudioSession:拒否");
            return NO;
            break;
            
        default:
        case AVAudioSessionRecordPermissionGranted:
            // このアプリに与える権限を許可(アプリ初回起動時「OK」を選択)
            NSLog(@"---AVAudioSession:許可");
            return YES;
            break;
    }
}

【参考】
マイクのアクセス許可 – iOS9とXcode7
authorizationStatusForMediaType: – AVCaptureDevice | Apple Developer Documentation
AVAudioSessionRecordPermission – AVFoundation | Apple Developer Documentation
Swift: アルバム・カメラ・マイク・プッシュ通知のアクセス許可判定一覧 | siro:chro

メディアライブラリ


MPMediaLibraryの「authorizationStatus」を使います。Appleのリファレンスにもあんまり詳しいことは書いてないです。何でだ?

//メディアライブラリ設定:参照(アプリの設定で許可されているかどうか)
-(BOOL)isPermitMediaLibrary
{
    MPMediaLibraryAuthorizationStatus status = [MPMediaLibrary authorizationStatus];
    switch (status) {
        case MPMediaLibraryAuthorizationStatusNotDetermined:
            // このアプリに与える権限が未選択(アプリ初回起動時)
            NSLog(@"---MPMediaLibrary:未選択");
            return NO;
            break;
            
        case MPMediaLibraryAuthorizationStatusRestricted:
            // 設定による使用制限で、アプリのアクセスの許可を変更できない
            NSLog(@"---MPMediaLibrary:許可を変更できない");
            return NO;
            break;
            
        case MPMediaLibraryAuthorizationStatusDenied:
            // このアプリに与える権限を拒否(アプリ初回起動時「許可しない」を選択)
             NSLog(@"---MPMediaLibrary:拒否");
            return NO;
            break;
            
        default:
        case MPMediaLibraryAuthorizationStatusAuthorized:
            // このアプリに与える権限を許可(アプリ初回起動時「OK」を選択)
            NSLog(@"---MPMediaLibrary:許可");
            return YES;
            break;
    }
}

【参考】
authorizationStatus – MPMediaLibrary | Apple Developer Documentation
MPMediaLibraryAuthorizationStatus – MediaPlayer | Apple Developer Documentation

音声認識(Speech.Framework)


SFSpeechRecognizerの「authorizationStatus」を使います。

    [SFSpeechRecognizer requestAuthorization:^(SFSpeechRecognizerAuthorizationStatus status) {
        dispatch_async(dispatch_get_main_queue(), ^{
            switch (status) {
                case SFSpeechRecognizerAuthorizationStatusAuthorized: {
                    NSLog(@"--許可あり");
                    break;
                }
                case SFSpeechRecognizerAuthorizationStatusDenied: {
                    NSLog(@"--拒否");
                    break;
                }
                case SFSpeechRecognizerAuthorizationStatusRestricted: {
                    NSLog(@"--設定により使えない");
                    break;
                }
                case SFSpeechRecognizerAuthorizationStatusNotDetermined: {
                    NSLog(@"--未認証");
                    break;
                }
            }
        });
        
    }];

【参考】
SFSpeechRecognizer – Speech | Apple Developer Documentation
authorizationStatus – SFSpeechRecognizer | Apple Developer Documentation
SFSpeechRecognizerAuthorizationStatus – Speech | Apple Developer Documentation

おまけ:自分のアプリの設定を呼び出す


各種ユーザーデータへのアクセス許可を参照して、許可がないものは改めて許可してもらいましょう。ということで、設定アプリの中にある自分の設定を呼び出す方法です。

いわゆるURLスキームというやつらしいですが、iOS10で使い方(openURLのメソッド)が変わっているようです。また下記の参考リンクに「URLスキームの内容をinfo.plistに記載する」とありますが、単純に自分のアプリ設定を呼び出すだけなら不要なようです(実機で動作確認済)。

//このアプリの設定を呼び出し
- (void)prefsButtonPushed {
    NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
    if ([[UIApplication sharedApplication] canOpenURL:url]) {
        [[UIApplication sharedApplication] openURL:url
                                           options:@{}
                                 completionHandler:nil];
        //以下は、iOS9までの書き方
        //[[UIApplication sharedApplication] openURL:url];
    }
}

これを実行すると、自分のアプリの設定が表示されます。

自分のアプリの設定が表示されます。
自分のアプリの設定が表示されます。

【参考】
application:openURL:options: – UIApplicationDelegate | Apple Developer Documentation
UIApplicationOpenSettingsURLString – UIKit | Apple Developer Documentation
[iOS 10] UIApplication の openURL: が Deprecated になりました | Developers.IO
iOS9でカスタムURLスキームの遷移に失敗するときの注意点 – Qiita
【iOS9】特定のURLスキームのみを呼び出し可能にする→.canOpenURL(url)について | MUSHIKAGO APPS MEMO
投稿の編集 ‹ AS blind side — WordPress
標準アプリのURLスキームについて – もう一人のY君

最後に


今回出てきた「Photo.Framework」「Speech.Framwork」については、別途まとめたいですが、とりあえず気になる人は下記を参考に。

・Photo.Framework
Photos vs Assets Library – いまさら始めるPhotos.framework
[iOS 8] PhotoKit 1 – Photos frameworkの概要 | Developers.IO
Photos Framework を使って写真.appと画像をやりとりする – Qiita
Photos frameworkを使ってiPhoneアルバム内の写真を取得・削除する+α – Think Big Act Local
Photos.framework でカメラロールを取得する – ObjecTips
投稿の編集 ‹ AS blind side — WordPress

・Speech.Framwork
[iOS 10] SFSpeechRecognizerで音声認識を試してみた | Developers.IO
[iOS 10] SFSpeechRecognizerの音声認識処理の仕組みを見てみる | Developers.IO
【iOS 10】Speechフレームワークで音声認識 – 対応言語リスト付き – Over&Out その後
新規投稿を追加 ‹ AS blind side — WordPress
【iOS】Speech Frameworkの実装 – Qiita
【iOS 10】Speech Frameworkで音声認識 – あたも開発ブログ
【iOS10】Speech Recognition API(音声認識API)の制約まとめ – Qiita

【Xcode】iOS10以降は各種ユーザーデータへアクセスに許可が必要

【OSX 10.11.6+Xcode 8.0 (8A218a)+iPhoneSE(iOS 10.0.2)】

iPhoneの音楽ライブラリからアートワークを使おうと思って、前に書いた古いコードから必要な部分を移植して動かしてみたらエラーが出た。

[access] This app has crashed because it attempted to access privacy-sensitive data without a usage description.  The app's Info.plist must contain an NSAppleMusicUsageDescription key with a string value explaining to the user how the app uses this data.

iOS10以降は、ユーザーデータにアクセするには、Info.plistに該当アクセスに対応したキーと使用目的を示すテキストを記述する必要があるようです。詳しくは下記リンクを。

[iOS 10] 各種ユーザーデータへアクセスする目的を記述することが必須になるようです | Developers.IO

具体的には、TARGETSからinfoを選んで、下記のように追加するだけ。

TARGETS > info で必要なアクセス先に対する許可を追加します。
TARGETS > info で必要なアクセス先に対する許可を追加します。ここではメディアライブラリ、マイク、音声解析、写真ライブラリの4つを指定。

Keyの選択肢は、全て「Privacy – 」で始まっているので、そこから必要なものを追加すると、アプリの初回起動時にそれぞれ許可を求めるダイアログが表示されます(keyに指定したString部分が、ダイアログの説明分として表示されます)。

info.plistで設定したアクセスに対して、許可を求めるダイアログが表示される。
info.plistで設定したアクセスに対して、許可を求めるダイアログが表示される。

ダイアログでの選択(許可するかしないか)は、iPhoneの 設定>アプリ名の中に保存され、ユーザーが切り替え可能です。逆に言うと、初回起動時以降は、ユーザーに設定で許可を変更するよう促す必要がある、ということですね。

アクセス許可は、iPhoneの 設定>アプリ名の中に保存される。
アクセス許可は、iPhoneの 設定>アプリ名の中に保存される。

で、冒頭の音楽ライブラリのアートワークを使うには、メディアライブラリへのアクセス許可を求めればいいようです(今回は音楽データそのものは使っていません)。

【参考】
iOS10ではカメラアクセスなどの目的を明示しないと強制終了する – Qiita
[iOS 10] ユーザーのプライバシー情報にアクセスするときは理由を書こう | Developers.IO
iOS 10 からユーザの許可を求めるアラートへの説明文追加が必須に – 強火で進め