Google+ もご覧ください
ユーザーアイコン

実践!RubyMotion

第九回 RubyMotion で 位置情報や地図を使う方法教えます

Satococoa

第九回 RubyMotion で 位置情報や地図を使う方法教えます

今回は RubyMotion で地図を表示したり、現在位置やジオコーディングから位置情報を取得して地図上にピンを置いたりしてみたいと思います。

RubyMotion 独自のノウハウはそれほど多くありませんが、少々楽をするために以下の 2 つの gem を使ってみたいと思います。

地図を表示させる

まずはとにかく地図を表示させてみましょう。

プロジェクトの作成から gem のインストール、メインになる View Controller の作成まで行います。

$ motion create LocationSample
$ cd LocationSample
$ vim Gemfile
  # 以下を追加
  gem 'bubble-wrap', require: 'bubble-wrap/location'
  gem 'map-kit-wrapper'
$ bundle install

app/app_delegate.rb

class AppDelegate
  def application(application, didFinishLaunchingWithOptions:launchOptions)
    @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
    @window.rootViewController = MainController.new
    @window.makeKeyAndVisible
    true
  end
end

app/main_controller.rb

class MainController < UIViewController
  include MapKit

  # ステータスバーを非表示にします
  def prefersStatusBarHidden
    true
  end

  def viewDidLoad
    super
    view.backgroundColor = UIColor.whiteColor

    # デフォルトは東京駅の位置
    # 緯度: 35.681382 経度: 139.766084
    @map = MapView.new.tap do |m|
      m.frame = view.frame
      m.region = CoordinateRegion.new([35.681382, 139.766084], [3.1, 3.1])
    end
    view.addSubview(@map)
  end
end

rake コマンドで実行してみて下さい。

地図を表示
地図を表示

あっという間に地図の表示までできましたね。MapView や CoordinateRegion というクラスが MapKitWrapper の提供してくれているクラスです。それぞれ MKMapView と MKCoordinateRegion をラップしています。

現在位置を取得する

次は現在位置を取得してみます。現在位置の取得には BubbleWrap の提供している以下のメソッドが使えます。

BW::Location.get_once do |result|
  # 現在位置を取得し、ブロック内を実行します。
end

早速実装します。以下のように MainController を変更しましょう。

app/main_controller.rb

class MainController < UIViewController
  include MapKit

  # ステータスバーを非表示にします
  def prefersStatusBarHidden
    true
  end

  def viewDidLoad
    super
    view.backgroundColor = UIColor.whiteColor
    current = UIButton.buttonWithType(UIButtonTypeSystem).tap do |b|
      b.setTitle('現在位置', forState:UIControlStateNormal)
      b.addTarget(self, action:'get_location:', forControlEvents:UIControlEventTouchUpInside)
      b.frame = [[0, self.view.frame.size.height - 44], [80, 44]]
    end
    view.addSubview(current)

    # デフォルトは東京駅の位置
    # 緯度: 35.681382 経度: 139.766084
    @map = MapView.new.tap do |m|
      m.frame = [[0, 0], [self.view.frame.size.width, self.view.frame.size.height - 44]] # ボタンの縦幅分、地図を小さくしている
      m.delegate = self # 追加
      m.region = CoordinateRegion.new([35.681382, 139.766084], [3.1, 3.1])
    end
    view.addSubview(@map)
  end

  def get_location(sender)
    BW::Location.get_once do |location|
      add_pin(location, '現在位置') if location.respond_to?(:coordinate)
    end
  end

  def add_pin(location, title)
    # 既にピンが表示されていたら一旦全部取り除く
    @map.removeAnnotations(@map.annotations) if @map.annotations

    annotation = MKPointAnnotation.new.tap do |an|
      an.title = title
      an.coordinate = location.coordinate
    end
    @map.addAnnotation(annotation)

    # 地図の表示領域をピンが中心に来るように移動させる
    @map.region = CoordinateRegion.new({:center => location.coordinate, :span => {:latitude_delta => 0.5, :longitude_delta => 0.5}})
  end

  # MKMapView のデリゲートメソッド
  def mapView(mapView, viewForAnnotation:annotation)
    MKPinAnnotationView.alloc.initWithAnnotation(annotation, reuseIdentifier:'pin').tap do |pi|
      pi.animatesDrop = true
      pi.canShowCallout = true
    end
  end
end

正しく動作しましたでしょうか?

現在位置の取得
現在位置の取得

シミュレータのデバッグメニューから現在位置を変更することが出来ますので、実機にインストールできない方はそれを利用して試してみてください。

実機にインストールできる方は rake device コマンドで、ぜひ実機で動作させてみてください。

ジオコーディングを使う

続いてジオコーディングを使って「東京」や「札幌」という文字列から位置情報を取得し、地図上にピンを置いてみましょう。

app/main_controller.rb

class MainController < UIViewController
  include MapKit

  CURRENT = 1
  SAPPORO = 2
  TOKYO   = 3
  FUKUOKA = 4

  # ステータスバーを非表示にします
  def prefersStatusBarHidden
    true
  end

  def viewDidLoad
    super
    view.backgroundColor = UIColor.whiteColor
    current = UIButton.buttonWithType(UIButtonTypeSystem).tap do |b|
      b.tag = CURRENT
      b.setTitle('現在位置', forState:UIControlStateNormal)
      b.addTarget(self, action:'get_location:', forControlEvents:UIControlEventTouchUpInside)
      b.frame = [[0, self.view.frame.size.height - 44], [80, 44]]
    end
    view.addSubview(current)

    sapporo = UIButton.buttonWithType(UIButtonTypeSystem).tap do |b|
      b.tag = SAPPORO
      b.setTitle('札幌', forState:UIControlStateNormal)
      b.addTarget(self, action:'get_location:', forControlEvents:UIControlEventTouchUpInside)
      b.frame = [[80, self.view.frame.size.height - 44], [80, 44]]
    end
    view.addSubview(sapporo)

    tokyo = UIButton.buttonWithType(UIButtonTypeSystem).tap do |b|
      b.tag = TOKYO
      b.setTitle('東京', forState:UIControlStateNormal)
      b.addTarget(self, action:'get_location:', forControlEvents:UIControlEventTouchUpInside)
      b.frame = [[160, self.view.frame.size.height - 44], [80, 44]]
    end
    view.addSubview(tokyo)

    fukuoka = UIButton.buttonWithType(UIButtonTypeSystem).tap do |b|
      b.tag = FUKUOKA
      b.setTitle('福岡', forState:UIControlStateNormal)
      b.addTarget(self, action:'get_location:', forControlEvents:UIControlEventTouchUpInside)
      b.frame = [[240, self.view.frame.size.height - 44], [80, 44]]
    end
    view.addSubview(fukuoka)

    # デフォルトは東京駅の位置
    # 緯度: 35.681382 経度: 139.766084
    @map = MapView.new.tap do |m|
      m.frame = [[0, 0], [self.view.frame.size.width, self.view.frame.size.height - 44]]
      m.delegate = self
      m.region = CoordinateRegion.new([35.681382, 139.766084], [3.1, 3.1])
    end
    view.addSubview(@map)
  end

  def get_location(sender)
    @geocoder ||= CLGeocoder.new

    case sender.tag
    when CURRENT
      BW::Location.get_once do |location|
        add_pin(location, '現在位置') if location.respond_to?(:coordinate)
      end
    when SAPPORO, TOKYO, FUKUOKA
      title = sender.titleForState(UIControlStateNormal)
      @geocoder.geocodeAddressString(title,
        completionHandler:lambda {|place, error|
          add_pin(place[0].location, title) if place.count > 0
        }
      )
    end
  end

  def add_pin(location, title)
    @map.removeAnnotations(@map.annotations) if @map.annotations

    annotation = MKPointAnnotation.new.tap do |an|
      an.title = title
      an.coordinate = location.coordinate
    end
    @map.addAnnotation(annotation)

    @map.region = CoordinateRegion.new({:center => location.coordinate, :span => {:latitude_delta => 0.5, :longitude_delta => 0.5}})
  end

  def mapView(mapView, viewForAnnotation:annotation)
    MKPinAnnotationView.alloc.initWithAnnotation(annotation, reuseIdentifier:'pin').tap do |pi|
      pi.animatesDrop = true
      pi.canShowCallout = true
    end
  end
end

ジオコーディングを実装
ジオコーディングを実装

地図の下部に置かれた 4 つのボタンはいずれも get_location: メソッドを呼んでいます。どのボタンが押されたのかの判断には tag を使っています。

またジオコーディングについては特にラッパーを使わずに直接 CLGeocoder を使っています。

BubbleWrap や MapKitWrapper のように Cocoa Touch フレームワークをラップしている gem を使うときのコツは、Cocoa Touch のどのクラス (今回なら MKMapView や CLLocationManager) をラップしているのかを把握することです。

そうすることで Apple のクラスリファレンスを参照したりサンプルを参考にすることもできますし、ラッパーがラップしきれていない部分も自分でラップするなり直接 API を叩くなりしてスムーズに実装することが出来ます。

一見遠回りのようですが、無理にラッパーを使うのではなく楽を出来るところだけ取捨選択して上手に楽をするというのが最近の RubyMotion のトレンドです。

まとめ

今回は地図を表示し、端末から取得した現在位置やジオコーディングから取得した位置情報を使って地図上にピンを表示するコードを書いてみました。

位置情報や地図はマッシュアップの題材としてもよく利用されるものです。ぜひこのサンプルコードを出発点にして様々な情報を表示して遊んでみてはいかがでしょうか?

もちろん前回解説したように Storyboard を利用して実装してみても良いと思います。

ラッパをうまく使いこなして効率的にアプリ作りが出来るよう、ぜひチャレンジしてみてください。

今回のサンプルは 9-location.zip です。

Rubymotion

記事をリクエストする

関連記事

コメント