koogawa blog

iOS、Android、foursquareに関する話題

2016/4/20 #potatotips #28 (iOS/Android開発Tips共有会) に参加してきたよ

昨日は南青山にあるエウレカさんで開催された potatotips #28 (iOS/Android開発Tips共有会) に参加してきました。

potatotips.connpass.com

とても綺麗なオフィスでした。

f:id:koogawa:20160421004042p:plain

いつものようにツイートもまとめておきました。

2016/4/20 #potatotips (iOS/Android開発Tips共有会) 第28回 - Togetter

Androidの発表内容については、すでに id:konifar さんが素晴らしいまとめを公開されています。

konifar.hatenablog.com

以下はiOSに関する発表のメモになります。間違いなどあれば教えて下さい。


PlaygroundでカスタムUIのプロトタイピング

milkit さんによる発表。Xcode の Playground についてのお話です。

Playgroundは日々進化しており、なんとたった3行で TableViewController が表示できてしまう。そして、アニメーションや AutoLayout にも対応している。更に、Xcode 7.3からタップイベントも取れるようになったんだとか。

Debug Remote / Local Notification on watchOS

shingt さんによる発表。watchOS 上で扱う Local Notification のお話です。

watchOS における Remote Notification のテストは、Xcode に仕組みが用意されているため割と簡単。それに対し、Local Notification は仕組みがないのと、引数が UILocalNotification オブジェクトで渡ってくるので、テストがとても大変。そこで、Remote/Local Notification のラッパーを作ったという素敵なお話でした。

RxSwiftのドライバー

Motoki Narita さんによる発表。RxSwift の Driver についてのお話でした。

アカウント登録画面を例に、入力されたユーザ名がすでに使われているかどうかを Validate するサンプルを使って Driver の解説をされていました。

Swiftにわか勢な自分がMacアプリを作ってRxSwiftを使えるようになった話

y.imajo さんによる発表。Macアプリを作ってRxSwiftを使えるようになった話でした。

発表が始まった途端、スクリーンにハッシュタグ #potatotips のツイートが流れているな〜と思っていたら、実はそれ自体が今回発表するMacアプリだった!という驚きの内容でした。

今回のMacアプリのソースコードGitHubに公開されているそうです。

github.com

手を動かさないとRxSwiftは理解できない

これはその通りだなぁ、と思いました。

Repository pattern in Swift

naoty さんによる発表。リポジトリパターンのお話です。

リポジトリパターンを使うことで、オブジェクトをWebAPI経由で取得するのか?Realmから取得するのか?などを知る必要がなくなり、疎結合なコードを書くことが可能になります。今回は「レシピデータ」をリポジトリを介して取得する処理をSwiftで実装する実践的な内容でした。

github.com

tvOS tips

koutalou さんによる発表。tvOSのお話です。最近、マネーフォワードのtvOSアプリを作られたそうです!

個人的には tvOS にも TabBarController と NavigationBar の概念があったことや、WebViewが使いないことを知らなかったので色々と勉強になりました。

国際カンファレンスに登壇する - 応募編

shu223 さんこと堤さんによる発表。堤さんは国際カンファレンスである UIKonf / iOSCon に登壇されるそうです!🎉

発表の中では、応募する理由、参加することのメリット、カンファレンスの探し方など、なかなか聞くことのできない貴重なお話を聞くことができました。

スライドなどは堤さんのブログにまとめられているようです。

d.hatena.ne.jp

iOS Universal Linkについて

最近ボルダリングにハマっているという TachibanaKaoru さんによる発表。iOS 9 から使えるようになった Universal Link のお話でした。

Universal Link の仕組み自体は知っていたのですが、SFSafariViewController, WKWebView, UIWebView の中では無効になる事実や、自分でURLを入力した場合も無効になるというTipsが知れたのは良かったです。

また、Smart App Banner との違いについても触れられていました。Universal Link と Smart App Banner は目的が違うものになるため、別々に実装する必要があるとのことです。

所感

ポテチでもRxの発表が増えてきており、流行ってるんだなぁ〜というのを再認識しました。

また、発表中も程よいボリュームでBGMが流れていて良いな、と思いました。

現場からは以上です。*1

*1:こにふぁーさんをリスペクトしてみました。怒られたら消します

【Tips】iOSでマイクの音を検知する(Swift編)

数年前に書いた下記記事が古くなってきたので、Swift編 として書き直しました。

f:id:koogawa:20160417130548p:plain


Core Audioを使用して、マイクから音を検知する方法をメモしておきます。

実行環境

  • Xcode 7.3
  • Swift 2.2
  • Deployment Target iOS 8

実装方法

ヘッダをインポートします。

import AudioToolbox

音声入力用のキューと監視タイマーを準備しておきます。

var queue: AudioQueueRef!
var timer: NSTimer!

記録するデータフォーマットを決めます。

var dataFormat = AudioStreamBasicDescription(
    mSampleRate: 44100.0,
    mFormatID: kAudioFormatLinearPCM,
    mFormatFlags: AudioFormatFlags(kLinearPCMFormatFlagIsBigEndian |
        kLinearPCMFormatFlagIsSignedInteger |
        kLinearPCMFormatFlagIsPacked),
    mBytesPerPacket: 2,
    mFramesPerPacket: 1,
    mBytesPerFrame: 2,
    mChannelsPerFrame: 1,
    mBitsPerChannel: 16,
    mReserved: 0)

▼各項目の意味

データ型 メンバ名 意味
Float64 mSampleRate サンプリング周波数(1秒間のフレーム数)
UInt32 mFormatID フォーマットID(リニアPCM、MP3、AACなど)
UInt32 mFormatFlags フォーマットフラグ(エンディアン、整数or浮動小数点数
UInt32 mBytesPerPacket 1パケット(データを読み書きする単位)のバイト数
UInt32 mFramesPerPacket 1パケットのフレーム数
UInt32 mBytesPerFrame 1フレームのバイト数
UInt32 mChannelsPerFrame 1フレームのチャンネル数
UInt32 mBitsPerChannel 1チャンネルのビット数
UInt32 mReserved

音の監視を開始します。

var audioQueue: AudioQueueRef = nil
var error = noErr
error = AudioQueueNewInput(
    &dataFormat,
    AudioQueueInputCallback,
    UnsafeMutablePointer(unsafeAddressOf(self)),
    .None,
    .None,
    0,
    &audioQueue)
if error == noErr {
    self.queue = audioQueue
}
AudioQueueStart(self.queue, nil)

今回は音を検知するだけで、録音の必要はないので AudioInputCallback は空にしておきます。

private func AudioQueueInputCallback(
    inUserData: UnsafeMutablePointer<Void>,
    inAQ: AudioQueueRef,
    inBuffer: AudioQueueBufferRef,
    inStartTime: UnsafePointer<AudioTimeStamp>,
    inNumberPacketDescriptions: UInt32,
    inPacketDescs: UnsafePointer<AudioStreamPacketDescription>)
{
    // Do nothing, because not recoding.
}

マイクのレベルを取得できるように、レベルメータを有効化しておきます。

var enabledLevelMeter: UInt32 = 1
AudioQueueSetProperty(self.queue,
                      kAudioQueueProperty_EnableLevelMetering,
                      &enabledLevelMeter,
                      UInt32(sizeof(UInt32)))

一定時間ごとにマイクレベルを監視します。ここでは NSTimer を使用しました。

self.timer =
    NSTimer.scheduledTimerWithTimeInterval(0.5,
                                           target: self,
                                           selector: #selector(AudioViewController.detectVolume(_:)),
                                           userInfo: nil,
                                           repeats: true)
self.timer.fire()

一定時間ごとにレベルを取得して、画面に表示します。

func detectVolume(timer: NSTimer)
{
    // Get level
    var levelMeter = AudioQueueLevelMeterState()
    var propertySize = UInt32(sizeof(AudioQueueLevelMeterState))

    AudioQueueGetProperty(
        self.queue,
        kAudioQueueProperty_CurrentLevelMeterDB,
        &levelMeter,
        &propertySize)

    // Show the audio channel's peak and average RMS power.
    self.peakTextField.text =
        "".stringByAppendingFormat("%.2f", levelMeter.mPeakPower)
    self.averageTextField.text =
        "".stringByAppendingFormat("%.2f", levelMeter.mAveragePower)

    // Show "LOUD!!" if mPeakPower is larger than -1.0
    self.loudLabel.hidden = (levelMeter.mPeakPower >= -1.0) ? false : true
}

通常、mPeakPowermAveragePower には負の値がセットされており、音量に比例して値も大きくなっていきます。最大値は 0 になります。

最後に忘れずにキューを空にして停止します。

AudioQueueFlush(self.queue)
AudioQueueStop(self.queue, false)
AudioQueueDispose(self.queue, true)

サンプルコード

注意点

  • ユーザがマイクの使用を許可していない場合、音の検知はできません

応用できそうなアプリ

  • 声や拍手などに反応するゲームアプリなど
  • iPhoneに息を吹きかけると何かがめくれるアプリ(実際にありましたねw)

参考にさせて頂いたリンク

LINE BOT API で bot を作ったときにハマったことメモ

LINEBOT APIが公開されたので、簡単な検索botを作ってみました。 botの作り方についてはすでにたくさんの良記事が公開されているので、ここでは主にハマりやすいポイントについてメモしておきます。

開発環境

アカウント登録

下記サイトからBOT API Trial Accountを登録します。

https://business.line.me/services/products/4/introduction

アカウントは1人あたり1つまで申し込めます。先着10,000名様まで利用可能だそうです。(4/9 17:00時点ではまだ申し込めるようです)

注意点

  • 友だち数上限は50人までです
  • 料金は無料です

実装方法

素晴らしい解説記事がすでに公開されているので、ここではリンクを貼るだけにしておきます。先人たちに感謝🙇🏻

ハマったポイント

Callback URLはHTTPS必須

公式ドキュメント

The callback URL must use HTTPS.

とあるように、Callback URLは HTTPS を利用する必要があります。HTTPで登録しようとするとエラーになります。

ポート番号を443にする必要がある

公式ドキュメントではあまり強調されていないのですが、Callback URLにはポート番号を指定する必要があります。

例:https://example.com:443/callback

Callback先をホワイトリストに登録しておく必要がある

Callback先のサーバーを予め「Server IP Whitelist」に登録しておかないと、サーバーからBOT APIをたたくことができません(StatusCode:427が返ってくる)。ホワイトリストはLINE developersの画面から登録することができます。

f:id:koogawa:20160409172820p:plain

Callback URLにリクエストがこない問題

サーバ側で使用しているSSL証明書によってはCallback URLにリクエストが通らない例があるようです。Twitterを見ると Let's Encrypt の証明書と相性が悪いようです。

成果物

キーワードをつぶやくと、それに関するノウハウ記事を教えてくれるbotを作りました。

f:id:koogawa:20160409173934p:plain

こちらから友達登録できます。(予告なく停止する可能性があります)

f:id:koogawa:20160409174015p:plain

リンク

【Tips】iOSで電子コンパスを使う(Swift編)

数年前に書いたこちらの記事が古くなってきたので、Swift編 として書き直しました。

blog.koogawa.com


f:id:koogawa:20160402095838p:plain

磁力センサーを利用し、iPhoneの向きを計測する方法を解説します。

「磁北」と「真北」について

単純に「北」と言っても、「磁北(じほく)」と「真北(しんぽく)」の二種類が存在します(2つの違いについては「真北とは? | 用語集とGISの使い方 | 株式会社パスコ」で丁寧に解説されています)。

ここでは磁北を取得する方法について解説します。

実行環境

実装方法

まずは「CoreLocation.framework」を追加します。

f:id:koogawa:20131122152458p:plain

次に Info.plist の NSLocationWhenInUseUsageDescription位置情報を使用する目的 を記載します。この内容はユーザに位置情報の使用を許可する際に表示されます。

f:id:koogawa:20160326145627p:plain

CoreLocation をインポートしましょう。

import CoreLocation

CLLocationManagerDelegate プロトコルを宣言します。

class ViewController: UIViewController, CLLocationManagerDelegate {

ユーザが位置情報の使用を許可しているか確認します。許可状況は didChangeAuthorizationStatus デリゲートで確認できます。初回は NotDetermined ステータスが返るので、requestWhenInUseAuthorization() メソッドでユーザに許可を求めます。

func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
    switch status {
    case .NotDetermined:
        locationManager.requestWhenInUseAuthorization()
    case .Restricted, .Denied:
        break
    case .Authorized, .AuthorizedWhenInUse:
        break
    }
}

これで準備が整いました。さっそく、ヘディングイベントの取得を開始してみましょう。

if CLLocationManager.locationServicesEnabled() {
    locationManager = CLLocationManager()
    locationManager.delegate = self
    
    // 何度動いたら更新するか(デフォルトは1度)
    locationManager.headingFilter = kCLHeadingFilterNone
    
    // デバイスのどの向きを北とするか(デフォルトは画面上部)
    locationManager.headingOrientation = .Portrait

    locationManager.startUpdatingHeading()
}

この状態でiPhoneの向きが変わると、次のメソッドが呼ばれます。

func locationManager(manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
    self.textField.text = "".stringByAppendingFormat("%.2f", newHeading.magneticHeading)
}

magneticHeading には 0.0 から 359.9 の値が入ります。北の方向がちょうど 0.0 になり、それから東、南、西の順に値が増えていき、北に戻ると再び 0.0 に戻ります。

方角
0.0
90.0
180.0
西 270.0

ヘディングイベントの取得を停止するには次のメソッドを実行します。

if CLLocationManager.locationServicesEnabled() {
    locationManager.stopUpdatingHeading()
}

イベントの取得が不要になったら、忘れずに停止しておきましょう。

サンプルコード

注意点

応用できそうなアプリ

参考リンク

なぜ技術ブログを書くのか

昨日、こちらの記事がバズっていました。

www.2dgod.com

ブコメ欄を見ると厳しい意見が並んでいますが、個人的には自分が考えていることや悩みをアウトプットするのは大事だなぁ、と思ったのでした。

自分も普段からブログやQiitaにいろんなことをアウトプットしているので、この機会に なぜブログを書くのか? 自分の考えを整理してみたいと思います。

恩返しをしたかった

プログラミングを始めた頃、先人たちが残してくれた技術記事にいつもお世話になっていました。そして、いつしか自分も「読む側」から「書く側」に回りたいと思うようになりました。

最初は自分の記事なんか人の役に立つのか?そもそも読んでもらえるのか?などと不安でしたが、書いていくうちに「あなたの記事が参考になった」など嬉しい反応を頂けるようになりました。

なんとなく書いたTipsが意外にも多くの反応を頂けたりすることもあるので(困っていることはみんな一緒だったりする)、どんな些細な事でもアウトプットしていくことが大事なんだなぁ、と思いました。

文章力向上のため

自分は元々作文がとても苦手でした(今も得意ではないですが)。ブログを書き始めた頃は、頭の中で考えていることをなかなか文章化できず苦しんだのを覚えています。

しかし慣れというのは不思議なもので、書いているうちに便利な言い回しや、文と文をつなげるテクニック的なものが自然と身に付いてきます。今も決して上手な文章を書けるとは思っていませんが、昔よりは読める文章になってきたと思います。

知識を整理するため

勉強会で発表するときも同じ事が言えますが、いい加減なことをアウトプットすることはできないので、準備に多くの時間を割くことになります。この過程が知識を整理するためのとても良い時間になると思っています。

万が一、内容に間違いがあった場合は、読者の方からご指摘を頂けるのも嬉しいことです*1。こういった助け合いの文化は良いものですね。

自分の存在を知ってもらうため

こちらの記事にとても良いことが書いてあります。

同じようなキャリアを歩んでいる先輩が、他にいなかった 【Kaizen Platform 伊藤直也さん】|転職エージェントのパソナキャリア

いい商品を作っても、マーケティングや営業をしないと売れないケースって多々ありますよね。それと同じで、どんなに高いスキルを持っていてもそれをアピールしなければ、その自分の技術力に気づいてもらう機会はそうそうない。技術系のイベントに登壇したり、ブログで情報発信をしたり、ソースコードを公開してみたりといった活動はキャリア形成において有利に働きます。もちろん内容が伴っていれば、ですよ。僕は自分のブログがたくさん読まれるようになって、それが転職のきっかけの一つになったようなところがありましたから。

私自身、転職する際に自分のブログがとても重要なキーとなったので、この内容にはとても共感できました。自分の名前が外でも通用する名前になるためにも、どんどん情報発信はしていったほうが良いと思います。

はてブ数、ストック数を稼ぐため?

もちろん自分の書いた記事がたくさんブックマークされたりすると嬉しいものです。ただ、それが目的になってしまうのは違うと思っています。

例えば「絶対使うべきライブラリ集」とか「初心者が今すぐ〇〇を使うべき10の理由」といった引きの強いタイトルを付ければ、記事がバズる確率も上がることでしょう。しかし、そういった記事ばかりを書くようにはなりたくないものです。

今後も 瞬間的にバズる記事より、必要なときに参照してもらえる記事 を書いていきたいと思っています。

まとめ

今回書いたこの記事自身も「みんなどういう理由でブログを書いているんだろう?」と疑問に思った方がググった結果として辿り着いてもらえたら嬉しいです。

*1:文句を言うだけで、指摘までしてくれない方もいますが…

【Tips】iOSで標高を取得する方法(Swift 3.0対応)

数年前に書いたこちらの記事が古くなってきたので、Swift編 として書き直しました。

blog.koogawa.com


CoreLocationを使って、位置情報から標高を取得する方法をメモしておきます。

実行環境

実装方法

準備

まずは「CoreLocation.framework」を追加します。

f:id:koogawa:20131122152458p:plain

次に Info.plist の NSLocationWhenInUseUsageDescription位置情報を使用する目的 を記載します。この内容はユーザに位置情報の使用を許可する際に表示されます。

f:id:koogawa:20160326145627p:plain

実装

CoreLocation をインポートします。

import CoreLocation

CLLocationManagerDelegate プロトコルを宣言します。

class ViewController: UIViewController, CLLocationManagerDelegate {

ユーザが位置情報の使用を許可しているか確認します。許可状況は didChangeAuthorizationStatus デリゲートで確認できます。初回は NotDetermined ステータスが返るので、requestWhenInUseAuthorization() メソッドでユーザに許可を求めます。

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
    switch status {
    case .notDetermined:
        locationManager.requestWhenInUseAuthorization()
    case .restricted, .denied:
        break
    case .authorizedAlways, .authorizedWhenInUse:
        break
    }
}

現在地の取得を開始します。

if CLLocationManager.locationServicesEnabled() {
    locationManager = CLLocationManager()
    locationManager.delegate = self
    locationManager.startUpdatingLocation()
}

この状態で iPhone の位置情報が更新されると、次のデリゲートが呼ばれます。

// 位置情報が更新されるたびに呼ばれる
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    guard let newLocation = locations.last else {
        return
    }
    self.textField.text = "".appendingFormat("%.2f m", newLocation.altitude)
}

newLocationaltitude プロパティで標高が取得できます。単位は メートル です。

現在地情報の取得を停止するには次のメソッドを実行します。

if CLLocationManager.locationServicesEnabled() {
    locationManager.stopUpdatingLocation()
}

サンプルコード

注意点

  • 標高は、現在の高度からではなく地図データから測定されます
  • つまり、東京タワーの展望台にいても地上にいても、取得できる標高は一緒になります

応用できそうなアプリ

  • 現在地の標高測定アプリ(すでにいろんなアプリがリリースされている)
  • 標高を競い合ったり、高さによって景品が出るゲーム
  • 気圧計と組み合わせて正確な高度を計測するアプリ

【Tips】iOSで位置情報を取得する方法(Swift 3.0対応)

※数年前に書いたこちらの記事が古くなってきたので、Swift編 として書き直しました。

blog.koogawa.com


CoreLocationを使って、緯度・経度を取得する方法をメモしておきます。

実行環境

実装方法

準備

まずは「CoreLocation.framework」を追加します。

f:id:koogawa:20131122152458p:plain

次に Info.plist の NSLocationWhenInUseUsageDescription位置情報を使用する目的 を記載します。この内容はユーザに位置情報の使用を許可する際に表示されます。

f:id:koogawa:20160326145627p:plain

実装

CoreLocation をインポートします。

import CoreLocation

CLLocationManagerDelegate プロトコルを宣言します。

class ViewController: UIViewController, CLLocationManagerDelegate {

ユーザが位置情報の使用を許可しているか確認します。許可状況は didChangeAuthorizationStatus デリゲートで確認できます。初回は NotDetermined ステータスが返るので、requestWhenInUseAuthorization() メソッドでユーザに許可を求めます。

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
    switch status {
    case .notDetermined:
        locationManager.requestWhenInUseAuthorization()
    case .restricted, .denied:
        break
    case .authorizedAlways, .authorizedWhenInUse:
        break
    }
}

現在地の取得を開始します。

if CLLocationManager.locationServicesEnabled() {
    locationManager = CLLocationManager()
    locationManager.delegate = self
    locationManager.startUpdatingLocation()
}

この状態で iPhone の位置情報が更新されると、次のデリゲートが呼ばれます。

// 位置情報が更新されるたびに呼ばれる
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    guard let newLocation = locations.last else {
        return
    }

    self.latTextField.text = "".appendingFormat("%.4f", newLocation.coordinate.latitude)
    self.lngTextField.text = "".appendingFormat("%.4f", newLocation.coordinate.longitude)
}

現在地情報の取得を停止するには次のメソッドを実行します。

if CLLocationManager.locationServicesEnabled() {
    locationManager.stopUpdatingLocation()
}

サンプルコード

注意点

  • シミュレータでテストする場合は、メニュー > Debug > Location から位置情報をカスタマイズできます
  • 位置情報のアクセス権限について詳しく知りたい場合は下記の記事を参照してください

blog.koogawa.com

参考リンク