koogawa blog

iOS、Android、foursquareに関する話題

APIKit + Himotoki + foursquare API でベニューを取得する

これは何

[twitter:@ishkawa] さん作の APIKit と、 id:ikesyo さん作の Himotoki を組み合わせて、foursquare API でベニューを取得したときのメモです。

実行環境

  • Xcode 7.3.1
  • Swift 2.2
  • Carthage 0.11.0
  • APIKit 2.0.1
  • Himotoki 2.0.1

準備

Carthage

ライブラリをインストールするために Carthage を使います。未インストールの場合は Homebrew でインストールします。

$ brew install carthage

アクセストークン取得

foursquare API にアクセスするためのアクセストークを取得します。

一番早いのは API Explorer にアクセスして、そこに表示される oauth_token をコピーする方法です。(本番リリースするアプリには使っちゃダメよ)

https://developer.foursquare.com/docs/explore#req=venues/search%3Fll%3D40.7,-74

image

https://api.foursquare.com/v2/venues/search?ll=40.7,-74&oauth_token=(YOUR_ACCESS_TOKEN)&v=20160529

でベニュー一覧が取得できることを確認します。

実装

サンプルプロジェクト作成

今回は 4sqAPIKitHimotoki という名前にしました。

f:id:koogawa:20160529142148p:plain

APIKit + Himotoki インストール

.xcodeproj ファイルがあるディレクトリに Cartfile を作成します。

github "ishkawa/APIKit" == 2.0.1
github "ikesyo/Himotoki" == 2.0.1

carthage update を実行します。(ちょっと時間がかかる)

$ carthage update
*** Fetching Himotoki
*** Fetching APIKit
*** Fetching Result
*** Downloading Result.framework binary at "2.0.0: A Portable Result"
*** Checking out APIKit at "2.0.1"
*** Checking out Himotoki at "2.0.1"
...

このとき、一緒に Result.framework もダウンロードされます。

update が終わったら、プロジェクトに3つのライブラリを追加します。

f:id:koogawa:20160529142225p:plain

Carthage の使い方は [twitter:@yutat93] さんの記事がとてもわかりやすいです。

データモデル定義

今回使用する /venues/search API のレスポンスは次のようなJSONになっています。

f:id:koogawa:20160529142259p:plain

ベニューのリスト response > venues だけ欲しいので、次の2つのデータモデルを定義することにします。

  • Resp(Response にしたかったが、APIKit の Response クラスと名前が重複するのでこの名前にした)
  • Venue

JSON デコードには Himotoki を使っています。Decodable プロトタイプで必須となる decode メソッドで各プロパティの初期化を行っています。演算子 <| の意味などは Himotokiのドキュメントを参照してください。

Resp.swift

import Himotoki

struct Resp {
    var venues: [Venue]
}

extension Resp: Decodable {
    static func decode(e: Extractor) throws -> Resp {
        return try Resp (
            venues: e <|| "venues"
        )
    }
}

Venue.swift

import Himotoki

struct Venue {
    var id: String
    var name: String
}

extension Venue: Decodable {
    static func decode(e: Extractor) throws -> Venue {
        return try Venue (
            id: e <| "id",
            name: e <| "name"
        )
    }
}

APIリクエスト用の型作成

最初にRequestType プロトコルを継承したプロトコルを作成します。

FoursquareRequestType.swift

protocol FoursquareRequestType: RequestType {

}

RequestType プロトコルに準拠するためには次の5項目を実装する必要があります。

  • typealias Response
  • var baseURL: NSURL
  • var method: HTTPMethod
  • var path: String
  • func responseFromObject(object: AnyObject, URLResponse: NSHTTPURLResponse) throws -> Response

まず、ベース URL を返す実装をプロトコル拡張で持たせます。

extension FoursquareRequestType {
    var baseURL: NSURL {
        return NSURL(string: "https://api.foursquare.com")!
    }
}

v2/venues/search をリクエストするための型を作成します。

struct GetVenueList : FoursquareRequestType {
    typealias Response = Resp

    var method: HTTPMethod {
        return .GET
    }

    var path: String {
        return "/v2/venues/search"
    }

    var parameters: AnyObject? {
        return [
            "ll": "40.7,-74",
            "oauth_token": "(YOUR_OAUTH_TOKEN)",
            "v": "20160529",
        ]
    }

    func responseFromObject(object: AnyObject, URLResponse: NSHTTPURLResponse) throws -> Response {
        return try decodeValue(object, rootKeyPath: "response")
    }
}

これで RequestType プロトコルに準拠できました。

リクエス

実際に foursquare API にアクセスしてみます。

let request = GetVenueList()

Session.sendRequest(request) { response in
    switch response {

    case .Success(let responses):
        self.venues = responses.venues

    case .Failure(let error):
        print(error)
    }
}

リクエストが成功すると response.Success になります。

f:id:koogawa:20160529142525p:plain

所感

最初 Himotoki 独特の演算子に少し戸惑いましたが、慣れてしまうとかなり便利に感じられるようになりました。

APIKit も活発にアップデートが続けられており、作者である ishkawaさんが APIKit の使い方を積極的に発信されているのが嬉しいです。

APIKit + Himotoki とても良さそうです😃

サンプルコード

github.com

リンク

2016/5/25 #potatotips #29 (iOS/Android開発Tips共有会) に参加してきたよ

f:id:koogawa:20160526013231p:plain

昨日は赤坂にて開催された potatotips #29 (iOS/Android開発Tips共有会) に参加してきました。主催は bitFlyer さんです。

potatotips.connpass.com

会場は水槽や植物などもあるオシャレなスペースでした。

f:id:koogawa:20160526013730p:plain

http://t-time156.com/

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

2016/5/25 #potatotips (iOS/Android開発Tips共有会) 第29回 - Togetter

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


WWDC初参加の方に送る
6つのおすすめな巡り方

mogmet さんによる発表です。WWDCに初めて参加する人向けに、おすすめの現地の巡り方を紹介されていました。

mogmet さんによると、WWDCの各セッションは1Fで視聴したほうがリアルタイムに英語の字幕を見ることができるため、セッションも聞き取りやすくなるそうです。これは参加した人にしかわからない情報なのでとても嬉しい情報ですね。他にもたくさんのTIPSを共有されていました!

  1. キーノート前日の朝までには到着しよう!
  2. キーノートに参加しよう!
  3. セッションは1Fで視聴しよう!
  4. ラボに張り付きましょう!
  5. 夜はパーティにでかけよう!
  6. 帰国後WWDC勉強会に出る

Bitriseを使っています

kurikazu さんによる発表。私も愛用している Bitrise についてのお話でした。

Bitrise は iOS, Android, Xamarin に対応したCIサービスです。無料枠もあります。GUIベースで「ワークフロー」と呼ばれる一連のステップ(git clone, pod install, Unit Test など)を追加していくのが特徴です。Slackなど色々なサービスにも対応しているそうです。

Apple の TestFlight にも対応していますが、Submit for Testflight オプションには罠があり、yes にするといきなりアップルにアプリがアップロードされるので注意が必要とのこと。

Segue をもっと使いやすくするTips

hirose_yudai さんによる発表です。Segueに関するお話でした。

Segueを使いやすくしてくれるライブラリとして ResourceKit というライブラリがあります。

これを使うことにより

  • 補完が効く
  • typoを防ぐことができる
  • Replaceし忘れがなくなる

などのメリットがありますが、さらに便利な SegueAddition というライブラリがあります。

これによって

  • 値渡しが楽になる
  • より狭いスコープで完結する
  • 循環参照しない

などのメリットが得られます。

さらに、ResourceKit と SegueAddition を併用することで更に便利になるそうです。詳しくはスライドをご覧ください。

docs.google.com

AppStoreで最新バージョン以外のアプリをダウンロードする話

hikarusato さんによる発表です。AppStore からアプリをダウンロードする際のリクエストを書き換えて、過去のバージョンをダウンロードするという素晴らしいTipsでした!

あるとき、旧バージョンをビルドしようとしたら、古いXcodeと互換性がないためビルドできない!ということがあったそうです(開発者あるある)。そこで、AppStore から過去のバージョンをダウンロードしようと思ったのがきっかけなんだそうです。

用意するものは次の3つ:

AppStore には初回リリースからすべてのバージョンが保存されている(おそらく)ことも知らなかったので、非常に勉強になりました!

Introducing Anglerfish

dealforest さんによる発表。私も愛用している Xcode プラグイン Anglerfish の紹介でした。

GitHub - dealforest/Anglerfish: sort the recently used simulators

iOS シミュレータが増えてくると、プルダウンから選択するときにわけが分からなくなりますよね。それを解決してくれるのが Anglerfish です!シミュレータを使った順にソートしてくれます。

ちなみに Anglerfish という名前は「アンコウ」からきているそうです。

Swift Package Manager (SwiftPM, SPM)

神戸から参加された JPMartha さんの発表!Swift Package Manager のお話でした。

Swift Package Manager は CocoaPods や Carthage のように、ライブラリを管理するためのツールです。Apple純正の管理ツールであり、将来的にはXcodeに組み込まれるそうです。今回は Swift Package Manager の入門的な話と、Swift Package Managerを駆使したツールの話が中心でした。

懇親会でも色々と質問させて頂きました。Twitterで補足をいただき、ありがとうございます 🙇🏻

ちなみに、JPMartha さんはニューヨークで開催されている Brooklyn swift developers に登壇されるそうです!

スタイルの一貫性を保つ

今回の主催である bitFlyer の hayashi331 さんによる発表です。

人間が行動するときにかかる負荷は「運動」「視覚」「認知」の3つだと言われているそうです。そして、運動よりも視覚よりも重い認知負荷を下げるのがもっとも重要なんだそうです。そのためにも「スタイルの一貫性を保つこと」が大事であり、今回は bitFlyer で作成している

  • NSAttributedString + Builder
  • UICatalog

について紹介されていました。

所感

今回は質疑応答ありのスタイル*1でしたが、時間通りに進んでいてタイムキーピングが素晴らしいと思いました。

ちなみに、bitFlyer さんは iOS エンジニアを大募集されているそうです!

*1:個人的には大好きなスタイルだけど時間のコントロールが難しくなる

RxSwiftを使ってfoursquareのベニューを取得するメモ

2016.10.23 追記:Swift 3.0 対応版を作成しました

blog.koogawa.com


RxSwift のメリットを理解するには実際に使ってみるのが一番!ということで、とりあえず foursquare のベニューを取得するサンプルを作ってみました。

f:id:koogawa:20160515223640p:plain

※RxSwift は絶賛勉強中なので間違ったことを書いている可能性が高いです。ツッコミ歓迎です!

動作環境

  • Swift 2.2
  • RxCocoa (2.4)
  • RxSwift (2.4)
  • SwiftyJSON (2.3.2)
  • Xcode 7

APIクライアント

APIにアクセスする部分を作ります。戻り値を Observable<[Venue]> にしているのがポイントです。

func send() -> Observable<[Venue]> {
    return Observable.create{ (observer) in
        let client = FoursquareAPIClient(accessToken: "YOUR_TOKEN")
        let parameter: [String: String] = [
            "ll": "40.7,-74",
        ];
        client.requestWithPath("venues/search", parameter: parameter) {
            [weak self] (data, error) in
            let json = JSON(data: data!)
            let venues = (self?.parseVenues(json["response"]["venues"])) ?? [Venue]()
            observer.on(.Next(venues))
            observer.on(.Completed)
        }
        return AnonymousDisposable {}
    }
}

func parseVenues(venuesJSON: JSON) -> [Venue] {
    var venues = [Venue]()
    for (key: _, venueJSON: JSON) in venuesJSON {
        venues.append(Venue(json: JSON))
    }
    return venues
}

通信ライブラリは拙作 FoursquareAPIClient を使用しました。

ViewModel

ViewModel から先ほどの send を呼び出す部分です。

public func fetch() {
    self.send()
        .subscribe { [weak self] (event) -> Void in
            switch event {
            case .Next(let value):
                self?.venues.value = value
            case .Error(_):
                ()
            case .Completed:
                ()
            }
        }
        .addDisposableTo(disposeBag)
}

戻り値が Observable<[Venue]> なので、これを subscribe して、返ってくる値(ベニューリストなど)を処理しています。

ViewController

ViewController は30行程と、かなりスッキリしました。rx_itemsWithDataSource でベニューリストとTableViewをBindしているのがポイントです。ViewModel のベニューリストに変化があるとTableViewがリロードされます。

import UIKit
import RxSwift
import RxCocoa

class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!
    
    var viewModel = ViewModel()
    var dataSource = DataSource()
    var delegate = Delegate()
    let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.tableView.delegate = self.delegate
        self.viewModel.fetch()
        self.viewModel.venues
            .asDriver()
            .drive (
                self.tableView.rx_itemsWithDataSource(self.dataSource)
            )
            .addDisposableTo(self.disposeBag)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

完全なソースコードはこちらです。

GitHub - koogawa/RxSwiftSample: RxSwiftSample

参考にさせて頂いた記事など

地図から位置情報を選択できる「LocationPickerController」を公開しました

地図から位置情報を選択する処理を毎回書くのが面倒くさいので、ライブラリとして公開しました。 位置情報は CLLocationCoordinate2D で取得できます。

github.com

使い方

  1. プロジェクトに CoreLocation.frameworkMapKit.framework をリンクします
  2. 位置情報を使う目的を info.plist の NSLocationWhenInUseUsageDescription に記載します
  3. CoreLocationLocationPickerController をインポートします
  4. LocationPickerController を初期化します
  5. LocationPickerController を表示すると地図を表示します
  6. Doneをタップすると位置情報がクロージャで取得できます
import CoreLocation
import LocationPickerController
let viewController = LocationPickerController(success: {
    [weak self] (coordinate: CLLocationCoordinate2D) -> Void in
    self?.locationLabel.text = "".stringByAppendingFormat("%.4f, %.4f",
        coordinate.latitude, coordinate.longitude)
    },
                                              failure: nil)
let navigationController = UINavigationController(rootViewController: viewController)
self.presentViewController(navigationController, animated: true, completion: nil)

インストール方法

LocationPickerControllerCocoaPods に対応しています。Podfile に下記一行を追加して pod install してください。

pod "LocationPickerController"

動作環境

iOS 8.0 以上で動作します。

プルリク歓迎です!

まだまだ Swift 勉強中であり、なかなか Swifty な書き方ではないことを自覚しております。もっと Swift らしい書き方ができるよ!という方はぜひプルリクエストをお送りください🙇🏻

すでに [twitter:@yimajo] 様より Swiftyなプルリク を頂きました!ありがとうございます。

iOSで検知できるセンサー12項目をまとめた「iSensor」のSwift版を公開しました

輝度センサーやモーションセンサーなど、iOSで検知できる様々な項目をまとめたサンプル集 iSensorSwift Githubで公開しました。

f:id:koogawa:20160429213238p:plain

github.com

▼機能の1つである「輝度センサー」のスクリーン

f:id:koogawa:20131117122515p:plain

iSensorSwift は昔書いたこちらのサンプル集を Swift で書き直したものになります。

興味のある方は、ぜひ使ってみてください!😀

検知できる項目

  • 光・音声系
  • 位置情報系
  • 移動・動作系
    • 加速度センサー
    • 歩数・進行状況
    • 移動速度
  • その他
    • 顔検出
    • バッテリー残量

実装方法を解説した記事

【Tips】iOSの顔検出機能を使ってみる(Swift編)

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

【Tips】iOSの顔検出機能を使ってみる - koogawa blog

f:id:koogawa:20160429092322p:plain


iOS 5から追加された CIDetector を使って、顔検出機能を使う方法をメモしておきます。

実装方法

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

f:id:koogawa:20131122152458p:plain

インスタンスを生成します。

let detector = CIDetector(ofType: CIDetectorTypeFace,
                          context: nil,
                          options: [CIDetectorAccuracy: CIDetectorAccuracyHigh])

ofType には検出種類を指定します。今回は顔検出なので、CIDetectorTypeFace を指定します。

options には検出精度 CIDetectorAccuracy 等を指定できます。

  • CIDetectorAccuracyLow - 精度は低いが、パフォーマンスは良い
  • CIDetectorAccuracyHigh - 精度は高いが、パフォーマンスは悪い

次に、顔検出を実行します。

// UIImage から CGImage を作る
let cgImage = image.CGImage
// CGImage から CIImage を作る
let ciImage = CIImage(CGImage: cgImage)
// 顔検出実行
let features = detector.featuresInImage(ciImage,
                           options: [CIDetectorSmile : true])

featuresInImage の引数には、顔検出したい画像を CIImage クラスで指定します。

今回は検出された顔が笑っているかどうかも検出したいので optionsCIDetectorSmile をセットしました。

正常に顔が検出されると、検出結果が CIFaceFeature オブジェクトとして返ってきます。このオブジェクトには、検出された顔の範囲や、目と口の位置などが含まれます。

  • bounds - 顔の範囲
  • hasLeftEyePosition - 左目の位置を検出できたか
  • hasMouthPosition - 口の位置を検出できたか
  • hasRightEyePosition - 右目の位置を検出できたか
  • leftEyePosition - 左目の位置
  • mouthPosition - 口の位置
  • rightEyePosition - 右目の位置
  • hasSmile - 顔が笑っているか
  • leftEyeClosed - 左目が閉じているか
  • rightEyeClosed - 右目が閉じているか

ここで注意したいのは、CoreImage は、左下の座標が (0,0) となる点です。よって、検出元画像の上に部品を載せたい場合は、座標をUIKitの座標系に変換する必要があります。

var transform = CGAffineTransformMakeScale(1, -1);
transform = CGAffineTransformTranslate(transform,
                                       0,
                                       -self.imageView.bounds.size.height);
// UIKit座標系に変換
let faceRect = CGRectApplyAffineTransform(feature.bounds,
                                          transform)

これで顔検出ができました。

顔検出というと難しそうなイメージが有りますが、座標系のところ以外は、意外と簡単に使用できますね。

サンプル

https://github.com/koogawa/iSensorSwift/blob/master/iSensorSwift/Controller/FaceDetectionViewController.swift

f:id:koogawa:20131130175727p:plain

補足

【Tips】iOSで歩数をカウントする(Swift編)

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

【Tips】iOSで歩数をカウントする - koogawa blog

f:id:koogawa:20160423122813p:plain


CoreMotionを使って、歩数カウントを取得する方法をメモしておきます。

動作環境

Xcode 8.0 + Swift 2.2

実装方法

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

f:id:koogawa:20131122152458p:plain

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

import CoreMotion

歩数をカウントする

歩数をカウントするためには CMPedometer を使います。

iOS 7で登場した CMStepCounteriOS 8 で早くも deprecated になりました。早かったですね…

まずはインスタンスを生成します。メンバ変数にしないとうまくいかないので注意です。

let pedometer = CMPedometer()

startPedometerUpdatesFromDate メソッドで歩数のカウントを開始します。

if CMPedometer.isStepCountingAvailable() {
    self.pedometer.startPedometerUpdatesFromDate(NSDate(), withHandler: {
        [weak self] (data: CMPedometerData?, error: NSError?) -> Void in
        // 歩数が更新されるたびに呼ばれる
        dispatch_async(dispatch_get_main_queue(), {
            if data != nil && error == nil {
                self?.stepLabel.text = "step: \(data!.numberOfSteps)"
            }
        })
    })
}
  • 第1引数:どの時点からのデータを呼び出すか指定(今回は現在日時を指定)
  • 第2引数:歩数更新時に呼び出す handler

第2引数で指定する handler には、計測を始めてからの累計歩数、移動距離など様々な情報が返ってきます。詳細はアップルの公式ドキュメントを参照してください。

歩数カウントが不要になったら、忘れずにカウントを停止しておきます。

self.pedometer.stopPedometerUpdates()

アクティビティの変化を取得する

また、歩数以外にもユーザのアクティビティ情報(歩いているのか、走っているか等)も取得できます。

まずはインスタンスを生成します。メンバ変数にしないとうまくいかないので注意です。

let activityManager = CMMotionActivityManager()

取得を開始します。

if CMMotionActivityManager.isActivityAvailable() {
    self.activityManager.startActivityUpdatesToQueue(NSOperationQueue.mainQueue(),
        withHandler: {
        [weak self] (data: CMMotionActivity?) -> Void in
        dispatch_async(dispatch_get_main_queue(), {
            // アクティビティが変化するたびに呼ばれる
        })
    })
}

アクティビティは CMMotionActivity というクラスで返ってきます。このクラスには次のプロパティが含まれます。

プロパティ 意味
NSDate startDate アクションが発生した時間
CMMotionActivityConfidence confidence データの精度(Low/Medium/High のいずれか)
BOOL stationary 静止状態
BOOL walking 徒歩中
BOOL running ランニング中
BOOL automotive 自動車や電車等の乗り物に乗っている状態
BOOL unknown 不明なアクティビティ

どれかひとつの動作が true になるというわけではなく、例えばautomotivestationary が同時に true になる状況もありえるようです。

最後は忘れずに停止しておきます。

self.activityManager.stopActivityUpdates()

サンプル

注意点

リンク