あたも技術ブログ

セットジャパンコーポレーションの社員が運営しています。

PHPの関数集(日付関連)

 今回はPHPで日付を出力する際によく使うと思われる関数のサンプルを紹介していきたいと思います。

現在日時を取得する

 現在日時はdate関数を使用します。date関数にフォーマットを指定することで指定したフォーマットの日時を取得することができます。
 PHPdate関数で日時を取得してもミリ秒までは取得できません。ミリ秒まで出力したい場合はstrtotime関数を使用します。

  function getNowDateTime() {
    return date("Y-m-d H:i:s");
  }
  
  function getNowDateTimeMicro() {
    return date("Y-m-d H:i:s") . "." . substr(explode(".", (microtime(true) . ""))[1], 0, 3);
  }

指定した月の月初、月末日を取得する。

PHPの場合、strtotime関数の書式指定で簡単に取得することができます。
modify関数へ引数の日付が変数展開されるよう記述しておきます。

  function getFirstDate($targetDate) {
    $d = new DateTime($targetDate);
    $d->modify("first day of $targetDate");
    return $d->format('Y-m-d H:i:s');
  }

  function getLastDate($targetDate) {
    $d = new DateTime($targetDate);
    $d->modify("last day of $targetDate");
    return $d->format('Y-m-d H:i:s');
  }
次月の月初、月末を取得する。

次月を求めるためにmodify関数へ引数の日付を変数展開されるよう記述しておきます。

  function getFirstDateNextMonth($targetDate) {
    $d = new DateTime($targetDate);
    $d->modify("first day of $targetDate next month");
    return $d->format('Y-m-d H:i:s');
  }

  function getLastDateNextMonth($targetDate) {
    $d = new DateTime($targetDate);
    $d->modify("last day of $targetDate next month");
    return $d->format('Y-m-d H:i:s');
  }

参考
http://php.net/manual/ja/function.date.phpphp.netPHPリファレンス date関数
http://php.net/manual/ja/function.microtime.phpphp.netPHPリファレンス microtime関数
http://php.net/manual/ja/function.strtotime.phpphp.netPHPリファレンス strtotime関数

マルチウィンドウ

さてさて、先日「AndroidN」が正式リリースされました。
AndroidMからの大きな変更点としては

  • マルチウィンドウ
  • データセーバー
  • 通知

などでしょうか。

新機能・変更点などは色々なサイトがまとめてくれているので割愛します。
http://mobilelaby.com/blog-entry-android-n-new-feature.html
http://androidlover.net/android-os/android-m/android-m-surely-releases-2015.html

様々な変更がありますが、その中で今回はマルチウィンドウの対応方法について書いていきたいと思います。



マルチウィンドウとは

マルチウィンドウとは1画面上に複数のアプリを同時に表示すること

f:id:atamo_dev:20160920221356g:plain

では、AndroidNならどんなアプリでもマルチウィンドウにできるのか?
答えは×です。
簡単な手順ですが、対応しなければいけないことがあります。




アプリをマルチウィンドウに適用する

アプリでマルチウィンドウモードを可能にするためには、 <application>全体、または適用させたい個々の<activity>マニフェストファイルで、 新たな属性android:resizeableActivityをtrueに設定する必要があります。
(アプリのtargetSdkVersionがandroidNより以前の場合、属性はデフォルトでfalseになっています)


簡単ですね!!
マルチウィンドウに対応できたら、以下の属性でリサイズの設定をすることができます。

android:defaultWidth:デフォルトの幅を指定
android:defaultHeight:デフォルトの高さを指定
android:gravity:グラビティの初期値。アプリをどこで分割すればよいかを指定
android:minimumSize:最小サイズを指定

マニフェストを修正して、サイズを決めるだけで対応できる。
素晴らしいですね!








と思っていることでしょう。
実はこのマルチウィンドウ、一つ罠が仕掛けられています。
マルチウィンドウに対応しないのであれば、resizeableActivityをfalseにすれば、確かにマルチウィンドウにはならないです。

が、

startActivityForResultで別アプリから呼び出された場合は話は別です。
Activityのマルチウィンドウ設定をfalseにしていても、共有の場合は強制でマルチウィンドウモードになってしまいます。

【参考サイト】
Pro-tip 5: Activities started by other apps must always support multi-window
ということで、暗黙的インテントを作成する場合はマルチウィンドウに対応しておきましょう!

【iOS 10】SiriKit触ってみた

さて、iOS 10から追加されたSiriKitを使うことで、Siriからアプリの機能を使うことができるようになります。 今回はSiriからの機能を使い、メッセージを送るためのサンプルを紹介します。

公式のサンプルや執筆時点でネット上に存在する情報を元に書いているので、動作に問題があるかもしれませんが、とりあえずSiriからの呼び出しが成功したので、手順を紹介します。

環境

Xcode Version 8.0 beta 6

iOS 10 beta 8

プロジェクト設定

まず、アプリに対して、Siriの使用を有効にします。 プロジェクトファイルを開き、[Target]からアプリ本体を選択します。 Capabilityタブを開き、SiriをONにして下さい。

f:id:atamo_dev:20160908032005p:plain

次に、Siriから呼ばれるハンドラを作成します。 [File]→[New]→[Target]を選択

f:id:atamo_dev:20160908031956p:plain

iOSタブから[Intents Extension]を選択します。 任意の名前をつけ、プロジェクトにターゲットを追加します。

f:id:atamo_dev:20160908032045p:plain

次に、追加したターゲットのinfo.pristファイルを開き、[NSExtension]→[NSExtensionAttributes]→[IntentsSupported]の中身をINSendMessageIntentのみにします。

f:id:atamo_dev:20160908032207p:plain

コード

Intents ExtensionのIntentHandler.swiftを編集します。

import Intents

class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessagesIntentHandling, INSetMessageAttributeIntentHandling {
    
    override func handler(for intent: INIntent) -> Any {
        return self
    }
 
    // MARK:- 解決フェーズ
    // 宛先の選択時にコールされる
    func resolveRecipients(forSendMessage intent: INSendMessageIntent, with completion: @escaping ([INPersonResolutionResult]) -> Void) {
        NSLog("resolveRecipients : %@", intent.recipients?.description ?? "nil")
        guard let recipients = intent.recipients , recipients.count > 0 else {
            completion([INPersonResolutionResult.needsValue()])
            return
        }
        
        let results:[INPersonResolutionResult] = recipients.flatMap{ INPersonResolutionResult.success(with: $0)}
        completion(results)
    }

    
    // 送信テキストの入力時にコールされる
    func resolveContent(forSendMessage intent: INSendMessageIntent, with completion: @escaping (INStringResolutionResult) -> Void) {
        NSLog("resolveContent : %@", intent.content ?? "nil")
        // メッセージ内容は必須なので、指定されていない場合は、needsValue()を設定
        let result = intent.content != nil ? INStringResolutionResult.success(with: intent.content!) :
            INStringResolutionResult.needsValue()
        completion(result)
    }
    
    // MARK:- 確認フェーズ
    // 送信の確認時にコールされる
    func confirm(sendMessage intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
        NSLog("confirm : %@", intent.description)
        let response = INSendMessageIntentResponse(code: .success, userActivity: nil)
        completion(response)
    }
    
    
    // 送信の実行時にコールされる
    func handle(sendMessage intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
        // Implement your application logic to send a message here.
        NSLog("sendMessage : %@", intent.content ?? "nil");
        let userActivity = NSUserActivity(activityType: NSStringFromClass(INSendMessageIntent.self))
        let response = INSendMessageIntentResponse(code: .success, userActivity: userActivity)
        completion(response)
    }
    
    
    // MARK:- Handling Intentフェーズ
    func handle(setMessageAttribute intent: INSetMessageAttributeIntent, completion: @escaping (INSetMessageAttributeIntentResponse) -> Void) {
        NSLog("handle %@", intent.description)
        // メッセージ送信処理を追加
        let response = INSetMessageAttributeIntentResponse(code: .success, userActivity: nil)
        completion(response)
    }
    
    
    func resolveGroupName(forSendMessage intent: INSendMessageIntent, with completion: @escaping (INStringResolutionResult) -> Swift.Void){
        NSLog("resolveGroupName %@", intent.groupName ?? "nil")
        // グループに対して送信する場合
        let notRequired = INStringResolutionResult.notRequired()
        completion(notRequired)
    }
    
    func resolveSenders(forSearchForMessages intent: INSearchForMessagesIntent, with completion: @escaping ([INPersonResolutionResult]) -> Swift.Void){
        NSLog("resolveSender %@", intent.senders?[0].displayName ?? "nil")
        let person = INPerson(personHandle: INPersonHandle(value: "Value",
                                                           type: .emailAddress),
                              nameComponents: PersonNameComponents(),
                              displayName: "displayName",
                              image: nil,
                              contactIdentifier: nil,
                              customIdentifier: nil)
        
        let sender = intent.senders == nil ?
            INPersonResolutionResult.success(with: person) : INPersonResolutionResult.success(with: intent.senders![0])
        completion([sender])
    }
    
    
    
    func resolveServiceName(forSendMessage intent: INSendMessageIntent, with completion: (INStringResolutionResult) -> Void) {
        NSLog("resolveServiceName %@", intent.description)
        let notRequired = INStringResolutionResult.notRequired()
        completion(notRequired)
    }

    
    
    // Implement handlers for each intent you wish to handle.  As an example for messages, you may wish to also handle searchForMessages and setMessageAttributes.
    
    // MARK: - INSearchForMessagesIntentHandling
    
    func handle(searchForMessages intent: INSearchForMessagesIntent, completion: @escaping (INSearchForMessagesIntentResponse) -> Void) {
        // Implement your application logic to find a message that matches the information in the intent.
        
        let userActivity = NSUserActivity(activityType: NSStringFromClass(INSearchForMessagesIntent.self))
        let response = INSearchForMessagesIntentResponse(code: .success, userActivity: userActivity)
        // Initialize with found message's attributes
        response.messages = [INMessage(
            identifier: "identifier",
            content: "I am so excited about SiriKit!",
            dateSent: Date(),
            sender: INPerson(personHandle: INPersonHandle(value: "sarah@example.com", type: .emailAddress), nameComponents: nil, displayName: "Sarah", image: nil,  contactIdentifier: nil, customIdentifier: nil),
            recipients: [INPerson(personHandle: INPersonHandle(value: "+1-415-555-5555", type: .phoneNumber), nameComponents: nil, displayName: "John", image: nil,  contactIdentifier: nil, customIdentifier: nil)]
            )]
        completion(response)
    }
    

}

実行

実際にSiriから呼び出してみましょう。

「アプリ名 + メッセージを送って」と言うと呼び出してくれます。

はじめは Siri Test というアプリ名を設定していたのですが、 Siri自身へのメッセージと勘違いをされ、「はい、なんでしょう?」のように応答されたので、混同しにくい適当な名前にしましょう。

今回は「はなこ」というアプリ名にしました。

f:id:atamo_dev:20160908032459j:plain

f:id:atamo_dev:20160908032506j:plain

Zend Frameworkでプロジェクトの雛形作成

 個人でPHPを利用してWebサイトを作りたい、他にはあまり無いですが仕事でPHPを利用してサイトを構築しなければならない、という事もあるかもしれません。今回はPHPの代表的なFrameworkのZend FrameworkCLIツールを使用してサイトの雛形を作成する手順を説明します。
framework.zend.comCLIツールの使用 - Zend_Tool_Framework


 サイトの雛形作成にはZend FrameworkのZendToolを使って作成します。使用できるコマンドは以下を参考にしてください。

ZFコマンド一覧
コマンド 説明
zf show version Zend Frameworkのバージョンを表示します。
zf show phpinfo phpinfoを表示します。
zf show manifest manifestを表示します。
zf show profile profileを表示します。
zf create project [作成するパス] Zendのプロジェクトを作成します。パスを指定しない場合は作成するパスの問い合わせが表示されます。
zf show project プロジェクトを表示します。
zf create controller [コントローラ名] Zend Frameworkのcontrollerを作成します。コントローラ名を指定しない場合は作成するコントローラ名の問い合わせが表示されます。
zf create action [アクション名] Zend Frameworkのactionを作成します。アクション名を指定しない場合は作成するアクション名の問い合わせが表示されます。
zf create view [コントローラ名] [アクション名] Zend Frameworkのviewを作成します。コントローラ名とアクション名を指定しない場合は作成するコントローラ名とアクション名の問い合わせが表示されます。
zf create module [モジュール名] Zend Frameworkのmoduleを作成します。モジュール名を指定しない場合は作成するモジュール名の問い合わせが表示されます。


 では実際にサイトの雛形を作成してみましょう。ZFコマンドはcreate projectを使用します。プロジェクト名は「hoge」で作成を行います。

$ zf create project hoge


 作成されたプロジェクトの構成は以下を参考にしてください。これで雛形が作成されました。雛形ではデフォルトコントローラのIndexコントローラのみ作成されていますので、ControllerとViewを追加して実装することになります。

hoge
|-- application
|   |-- Bootstrap.php
|   |-- configs
|   |   `-- application.ini
|   |-- controllers
|   |   |-- ErrorController.php
|   |   `-- IndexController.php
|   |-- models
|   `-- views
|       |-- helpers
|       `-- scripts
|           |-- error
|           |   `-- error.phtml
|           `-- index
|               `-- index.phtml
|-- library
|-- public
|   |-- .htaccess
|   `-- index.php
`-- tests
   |-- application
   |   `-- bootstrap.php
   |-- library
   |   `-- bootstrap.php
   `-- phpunit.xml


 index.phtmlにスタートページのタグを書き込み、配置すれば簡単なWebサイトの完成です。業務アプリであればIndexController.phpで認証処理を行い認証がNGの場合、ログイン処理を行ってからコントローラへのリダイレクトを行うなどの実装が必要になると思います。
framework.zend.comプロジェクトを作成 - Zend Framework Quick Start


 これでプロジェクトの雛形作成の解説は終わりです。Zend Frameworkはリファレンスドキュメントが充実しており公式サイトで事細かに解説がなされています。PHP初心者でも作成はしやすいと思いますので、チャレンジしてみてはいかがでしょうか。

Androidでタップの制御

アプリ開発をしているとほぼ必ず行うタップの制御。
今回はこのタップ制御を使いやすくしてみようと思います。

とりあえず普通に作っていくと、以下のようになると思います。

private boolean isTap = false;

Button button = new Button(getApplication());
button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if(isTap) return;
        isTap = true;
        [何かしらの処理]
        isTap = false;
    }
});

これでも特に問題はないのですが、ボタンが複数ある場合にはそれぞれに処理を書かないといけないですし、フラグの管理を間違えたらボタンが反応しなくなってしまうなどのバグが出てしまいます。

そんな問題を少しでも解消するために少し一工夫をしてみましょう。

まずはベースとなるActivity、もしくはFragmentを作成します。
そして、自動解除用のHandlerとRunnable、タップ管理用のMapを用意します。

public class BaseFragment extends Fragment{
    // 自動解除用Handler
    private Handler handler = new Handler();
    // 遅延実行のためのRunnable
    private Runnable runnable = null;
    // タップ管理用Map
    private Map<String, Boolean> map = new HashMap();
}

Activity、FragmentのonPauseでフラグの解除を行っておきます。

@Override
public void onPause() {
    super.onPause();
    map.clear();
    if (null != runnable) {
        handler.removeCallbacks(runnable);
    }
}

次にタップ管理の処理を書いていきます。

// タップ制御開始
protected void executeEvent(String key) {
    map.put(key, true);
}

// タップ制御開始(自動解除)
protected void executeEvent(String key, long delay) {
    map.put(key, true);
    runnable = new Runnable() {
        @Override
        public void run() {
            finishEvent(key);
        }
    };
    handler.postDelayed(runnable, delay);
}

// タップ制御中かどうか
protected boolean isEvent(String key) {
    return map.containkey(key);
}

// タップの制御解除
protected void finishEvent(String key) {
    map.remove(key);
}

ベースクラスを継承したActivity, Fragmentであれば以下のように使用することができます。

// 手動解除
Button button = new Button(context);
button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
      if(isEvent("blockKey")) return;
      executeEvent("blockKey");
      [何かしらの処理]
      finishEvent("blockKey");
    }
});

// 自動解除
Button button = new Button(context);
button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if(isEvent("blockKey")) return;
        executeEvent("blockKey", 1000); // 1秒後に自動解除
        [何かしらの処理]
    }
});

単純なタップ制御なら、自動解除が結構使いやすくて便利だと思います。
タップの管理はMapで行っているので、複数ボタン別々のイベントも簡単に実装できます!

以上、タップ制御でした!

【iOS 10】Speech Frameworkで音声認識

先月行われたWWDCiOS 10が発表され、DeveloperにはすでにiOS 10とXcode 8のベータ版が配布されています。

今回は、iOS 10で実装されたSpeech Frameworkを紹介します。

Speech Frameworkとは

iOS 10から対応するAppleが純正のAPIとして公開した音声認識Frameworkです。

今までiOS音声認識を使う場合、サードパーティ音声認識ライブラリを使う必要がありましたが、今後はSpeech Frameworkを利用することで、サードパーティに頼る必要がなくなります。

公式のサンプルコード

Using Speech Recognition with AVAudioEngineというサンプルコードがAppleから公開されています。

プロジェクトファイルを開くためにはXcode 8が必要になります。

developer.apple.com

使い方

音声認識を行うためには"SFSpeechRecognizer"というクラスを使うようです。

初期化の引数としてLocaleクラスを渡します。

 // 識別言語を日本語とする
private let speechRecognizer = SFSpeechRecognizer(locale: Locale(localeIdentifier: "ja_JP"))!

続いて、Delegateを設定します。

speechRecognizer.delegate = self

Delegateメソッドは以下のものが存在し、Recognizerのステータスが変化した場合に呼び出されるようです。

availableにRecognizerが使用可能か否かの値が入っています。

optional public func speechRecognizer(_ speechRecognizer: SFSpeechRecognizer, availabilityDidChange available: Bool)

音声認識を使用する場合は権限の許可を得なければならないので、

SFSpeechRecognizerクラスのrequestAuthorizationメソッドで、権限を求めるアラートを表示します。

サンプルでは、許可されている場合に認識を開始するボタンを有効にする処理になっていました。

SFSpeechRecognizer.requestAuthorization { authStatus in
            // 音声アクセス許可を求める
            OperationQueue.main().addOperation {
                switch authStatus {
                    case .authorized:
                    //  許可
                    case .denied:
                    // 拒否
                    case .restricted:
                    //  設定などにより制限されている
                    case .notDetermined:
                    //  未認証
                }
            }
        }

音声入力を使う場合

認識リクエスト
// プロパティで宣言する
private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?

// 初期化
recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
guard let recognitionRequest = recognitionRequest else { fatalError("Unable to created a SFSpeechAudioBufferRecognitionRequest object") }
// 確定前の結果を取得する
recognitionRequest.shouldReportPartialResults = true
音声ファイルを使う場合
// プロパティなどで宣言
private var recognitionRequest: SFSpeechURLRecognitionRequest?

// 初期化
// URL
recognitionRequest = SFSpeechURLRecognitionRequest(url: URL(string: "http://xxxxxxxxxxxxxxxx")!)
// ローカルファイル
recognitionRequest = SFSpeechURLRecognitionRequest(url: URL(fileURLWithPath: localPath))

タスクの設定

タスクを設定し、値を受け取った場合の処理をBlockで渡します。

// プロパティなどで宣言
private var recognitionTask: SFSpeechRecognitionTask?
// タスクを設定
recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { result, error in    
    if let result = result {
        //  結果を取得できた場合の処理
    }
    
    if error != nil {
        // 取得エラー
    }
}

resultはSFSpeechRecognitionResultオブジェクトになっています。

// 認識したテキストの取得
result.bestTranscription.formattedString
// 終了検知
result.isFinal

音声ファイルを使用する場合は以上で終了です。

認識の開始

AVAudioEngineによりマイクからの音声入力を取得します。

// プロパティなどで宣言
private let audioEngine = AVAudioEngine()

// 初期化
audioEngine = AVAudioEngine()
guard let inputNode = audioEngine.inputNode else { fatalError("Audio engine has no input node") }
// 音声取得形式を設定
 let recordingFormat = inputNode.outputFormat(forBus: 0)
// 音声入力を受け取った際に、SFAudioBufferRecognitionRequestに受け取ったバッファを追加します。
inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
    self.recognitionRequest?.append(buffer)
}
// 音声の取得を開始します
audioEngine.prepare()
try audioEngine.start()

注意点

上記のコードで簡単に実装できるSpeech Frameworkですが、いくつか問題点があるようです。 調べて分かった問題点を幾つか紹介します。

・インターネット環境必須

・回数制限あり

・連続使用は1分まで

使ってみて

実際にiPhoneに向かって喋ってみましたが、かなり高い精度で認識してくれているようです。 50文字程度の文章を音読してみましたが、誤字などは無く、識別率100%となりました。

PHPのよくある質問集

WEB構築用の言語としてはやや下火になってきた感がありますが、PHPのよくある質問をまとめてみました。

質問:if文の条件に「==」と「===」って使えるけどこれ何なの…。

回答:「==」は型までは比較しない比較演算子です。「===」の場合は型まで厳密にチェックします。「==」は内部で型の変換が行われるので遅くなります。速度面で速くなる「===」を使いましょう。

例:
$a = 1;
if ($a == ‘1’) {
  // 一致
}

if ($a === ‘1’) {
  // 一致しない
}


質問:文字列を定義するときに「’」と「”」の2通りあるけどこれ何なの…。

回答:「’」で定義したときは変数の展開はされません。「”」で定義したときは変数が展開されます。変数を展開したいときは「”」を、それ以外のときは「’」を使いましょう。変数展開するときに英字と混ざってわかりにくくなるときがあります。その場合「{}」で囲って記述します。

変数展開の例:
$name = ‘田中’;
echo “わたしは @{name} です”;

出力結果
わたしは 田中 です


質問:for文遅くね?これ何なの…。

回答:PHPのfor文は結構遅いです。あまりよろしくないコードだとcount関数でループの度に値を取得したりする人がいます。このようなダメコードを排除するためにもforeachを使いましょう。

悪い例:
$arr = array(1, 2, 3);
for ($i = 0; $i < count($arr); i++) {
  // 処理
}

良い例:
$arr = array(1, 2, 3);
foreach ($arr as $key => $value) {
  // 処理
}


質問:foreachだと最初と最後の要素わからなくね?それでもforeach使うの…。

回答:配列ポインタの先頭と最後に移動するreset関数とend関数があります。これを使いましょう。

例:
$arr = array(1, 2, 3);
// 先頭の要素を取り出す
echo reset($arr);

// 先頭の要素を取り出す
echo end ($arr);


質問:require, require_once, include, include_onceを書きすぎると遅くなってない?気のせいかな…。

回答:気のせいではありません。まずパスの指定が参照パスになっていないか確認しましょう。参照パスで指定したときは、そのファイルが見つかるまでinclude_pathを探しにいきます。その結果無駄なコストが発生しているのです。 require, require_once, include, include_onceは絶対パスで記述をしましょう。

例:
悪い例:
require_once ‘PEAR.php’

良い例:
require_once ‘/usr/share/pear/PEAR.php’


質問:PHPってメソッドのオーバーロード書けなくね?これ何なの…。

回答:PHPではオーバーロードはサポートされていません。その代わりに引数を省略可能とする引数のデフォルト値を定義することができますので、こちらを使用しましょう。またPHP5.6以降を使用するのでしたら可変長引数リストを使用することもできます。

引数のデフォルト値の例:
function hoge($arg = null) {
  if (!is_null(@arg)) {
    // 処理
  }
}

可変長引数リストの例:
function hoge(...$args) {
  foreach ($args as $arg) {
    // 処理
  }
}


質問:PHPでも春分秋分の日って日付型で計算できるの?

回答:PHPでも一般に公開されている計算式で算出できます。以下が春分秋分の日付を計算するサンプルとなります。

/**
 * 春分日を算出します。
 * @param  int      $year 算出対象となる年
 * @return DateTime 春分日
 */
public function CalcVernalEquinoxDay(@year) {
  // 2000年の太陽の春分点通過日
  @baseDate = 20.69115;

  // 春分点通過日の移動量 = (西暦年 - 2000年) * 0.242194
  @movement = (@year - 2000) * 0.242194;

  // 閏年によるリセット量 = (int)((西暦年 - 2000年) / 4)
  @leapYearOffset = (int)((@year - 2000) / 4.0);

  // 春分日 = (int)((1)+ (2) - (3))
  @vernalEquinoxDay = (int)(@baseDate + @movement - @leapYearOffset);

  $date = new DateTime();
  return $date->setDate(@year, 3, @vernalEquinoxDay);
}

/**
 * 秋分日を算出します。
 * @param  int      $year 算出対象となる年
 * @return DateTime 秋分日
 */
public function CalcAutumnalEquinoxDay(@year) {
  // 2000年の太陽の秋分点通過日
  @baseDate = 23.09; 

  // 秋分点通過日の移動量 = (西暦年 - 2000年) * 0.242194
  @movement = (@year - 2000) * 0.242194;

  // 閏年によるリセット量 = (int)((西暦年 - 2000年) / 4)
  @leapYearOffset = (int)((@year - 2000) / 4.0);

  // 秋分日 = (int)((1)+ (2) - (3))
  @autumnalEquinoxDay = (int)(@baseDate + @movement - @leapYearOffset);

  $date = new DateTime();
  return $date->setDate(@year, 9, @autumnalEquinoxDay);
}

Androidでも音声認識

先日のWWDCでSiriのAPIを公開するという発表がありました。
これでサードパーティのアプリに組み込むことができるようになったので、今後音声を利用したアプリが増えてきそうですね。

そこで、Androidでも音声を使ってみようと思います!
AndroidMから音声認識APIが使えるようになっているので、今回はそれを利用したサンプルアプリのご紹介です。

Voice Interaction API
https://developers.google.com/voice-actions/interaction/voice-interactions



まずは普通にプロジェクトを作成。

次にAndroidManifet.xmlに以下のようにintent-filterを作成します。

<intent-filter>
    <action android:name="android.media.action.STILL_IMAGE_CAMERA" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.VOICE" />
</intent-filter>



音声コマンドで立ち上がった場合は、isVoiceInteraction()にtrueが返却されるので、以下のように判定します。

@Override
protected void onResume() {
    super.onResume();
    if (isVoiceInteraction()) {
        startVoiceTrigger();
    }
}



getVoiceInteraction().submitRequestで音声確認を要求します。
ここが実際に音声のやりとりを行う所になります。

PickOptionRequest.Optionで発声する文字のリストを作成します。
(リストに含まれている文字が発呼されたらonPickOptionResultにコールバックされます)
今回は「select pet」という要求が表示され、「dog」もしくは「cat」といった場合のみトーストが表示されます。

PickOptionRequest.Option[] options = new PickOptionRequest.Option[] {
    new PickOptionRequest.Option("dog", 0),
    new PickOptionRequest.Option("cat", 1)
};

getVoiceInteractor().submitRequest(
    new VoiceInteractor.PickOptionRequest(newVoiceInteractor.Prompt("select pet"), options, new Bundle()) {
        @Override
        public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) {
            if (finished) {
                Toast.makeText(getApplicationContext(), "Ok", Toast.LENGTH_SHORT).show();
            }
        }
    }
);



ここまで作成したらアプリを実行。

ホーム画面で「ok google」。
f:id:atamo_dev:20160615094128p:plain:h300

そして、「take a selfie」でアプリを起動することができます。
f:id:atamo_dev:20160615094826p:plain:h300 f:id:atamo_dev:20160615094832p:plain:h300

アプリが立ち上がると、「select pet」と聞かれるので、
f:id:atamo_dev:20160615095449p:plain:h300


今回は「cat」と発声!(「dog」でも可)
f:id:atamo_dev:20160615095803p:plain:h300

すると、onPickOptionResultが呼ばれてトーストが表示されました!
f:id:atamo_dev:20160615095807p:plain:h300


以上、簡単ですがAndroidでの音声操作の利用方法でした。
音声を使用すれば、手を使わなくてもスマホを操作できるようになるので、手が使えないときの画面のスクロールや切り替えなどがしやすくなるかも?(料理中とか?)

下記URL先を見れば、手順を踏みながら作成することもできるので、興味ある方はぜひ触ってみてください。
http://io2015codelabs.appspot.com/codelabs/voice-interaction#1

ではでは~~。

【Swift】KVCを用いたJSONパーサー

REST-APIやWebSocketで通信するアプリを作っているとどうしても通らないといけないのがJSONのパース。

普通に実装するとこのようになると思います

class Object: NSObject {
    dynamic var str0: String = "a"
    dynamic var str1: String = "b"
    dynamic var str2: String = "c"
    dynamic var num0: Int = 0
    dynamic var num1: Int = 1
    dynamic var num2: Int = 2
    
    init(dictionary: [String : AnyObject]) {
        super.init()
        str0 = dictionary["str0"] as! String
        str1 = dictionary["str1"] as! String
        str2 = dictionary["str2"] as! String
        num0 = dictionary["num0"] as! Int
        num1 = dictionary["num1"] as! Int
        num2 = dictionary["num2"] as! Int
    }
}

しかし、APIが増えるごとにそれぞれのモデルごとにinitを書いているのは正直疲れます。 今回はKVC(Key Value Coding)を用いて、initを書かずにJSON⇔モデルクラスを実現する方法をSwiftで紹介します。

KVCとは?

キー値コーディングとは、オブジェクトのプロパティに間接的にアクセスするための仕組みです。ア クセサメソッドを呼び出してアクセスしたり、インスタンス変数としてアクセスするのではなく、プ ロパティの識別に文字列を使用してアクセスします。 引用元: https://developer.apple.com/jp/documentation/KeyValueCoding.pdf

要は、プロパティに対して.str1のようにアクセサメソッドやインスタンス変数でアクセスするのではなく、 "str1"といった文字列でプロパティに対してアクセスする手法です。 Objective-CではこのKVCを使用してプライベートなAPIを無理やり叩くこともできました。(Swiftではやったことないけど...)

プロパティ名の取得

オブジェクトからJSONマッピングするためにはKeyとなる物が必要なので、 JSONのKeyに対応するプロパティ名をすべて取得します。

class KVCModel: NSObject {

    class func allKeys() -> [String] {
        
        var names: [String] = []
        var count: UInt32 = 0
        self.classForCoder()
        let properties:UnsafeMutablePointer<objc_property_t> = class_copyPropertyList(self.classForCoder(), &count)
        
        for i: UInt32 in 0..<count {
            let property = properties[Int(i)];
            let cname = property_getName(property)
            let name = String.fromCString(cname)
            
            names.append(name!)
            
        }
        free(properties)
        return names
    }
}
self.classForCoder()

では自身のクラスを取得します。

Objective-Cでいうところの

+ (Class)class;

に対応しています。

イニシャライザでマッピングする JSON->モデル

イニシャライザにJSONから取り出したDictionaryを渡すことで自動的にプロパティに値が設定されるようにします。

class KVCModel: NSObject {
    init(dictionary: [String : AnyObject]) {
        super.init()
        if dictionary.count == 0 {
            return
        }
        
        let allPropertyKeys = self.dynamicType.allKeys()        
        for key: String in allPropertyKeys {
            let val = dictionary[key]
            if val != nil {
                self.setValue(val, forKey: key)
            }
        }
    }

}
self.dynamicType

でも自身のクラスを取得していますが、 こちらはObjective-Cでは

- (Class)class;

に対応しています。

Dictionaryで取り出す モデル->JSON

最後に以下のメソッドを追加し、モデルのプロパティをDictionaryとして取り出せるようにします。

    func dictionaryObject() -> [String : AnyObject] {
        let allKeys = self.dynamicType.allKeys()
        return self.dictionaryWithValuesForKeys(allKeys)
    }

動作

以上までで作成したクラスを継承し、モデルクラスを作成しましょう。

class Object: KVCModel {
    dynamic var str0: String = ""
    dynamic var str1: String = ""
    dynamic var str2: String = ""
    dynamic var num0: Int = 0
    dynamic var num1: Int = 0
    dynamic var num2: Int = 0
}

テストコード

let dic = ["str1" : "str", "str2" : "strstr", "num1": 1, "num2" : 2]
var jsonData: NSData = NSData()
var jsonDic: [String : AnyObject] = [:]

// Dictionary => JSON
do {
    jsonData = try NSJSONSerialization.dataWithJSONObject(dic, options: .PrettyPrinted)
} catch {
    // なんかやる
}

// JSON => Dictionary
do {
    jsonDic = try NSJSONSerialization.JSONObjectWithData(jsonData, options: .AllowFragments) as! Dictionary
} catch {
    // なんかやる
}


// Dictionary => Model
let model: Model = Model(dictionary: jsonDic)

print("str1 : \(model.str1)")
print("str2 : \(model.str2)")
print("num1 : \(model.num1)")
print("num2 : \(model.num2)")
print("dic : \(model.dictionaryObject())")

ログ

str1 : str
str2 : strstr
num1 : 1
num2 : 2
dic : ["num2": 2, "str2": strstr, "num1": 1, "str1": str]

開設しました。

はじめまして!

このブログは、セットジャパンコーポレーションの社員が、 最新の技術情報やTipsなど、役立つ情報を公開する場です。

スローペースで更新していくので、生暖かい目で御覧ください。