目次


サッカーのスコアを通知するモバイル・アプリを IBM Bluemix 上で作成する

Comments

私は熱烈なサッカー・ファンですが、数多くある試合を見る時間がありません。そこで、最新のスコアを取り込み続ける簡単な手段を求めていましたが、世間に出回っているこの類のアプリのほとんどは、私が望んでいない数々の機能で肥大化している感があるため、私は独自にアプリを作成することにしました。

このチュートリアルでは、私がこの「Occer」アプリをどのように作成して IBM の Bluemix プラットフォームにデプロイしたかを説明します。バックエンドの API 層は、Ruby、Sinatra、MongoDB を使って作成しました。最新のスコアは、無料で利用できる football-data.co.uk のデータ・フィードから取得します。アプリそのものは Swift で作成した iOS アプリですが、IBM Bluemix のMobile SDK を利用してプッシュ通知に対処します。

このチュートリアルでは、イングランド・プレミア・リーグの最新スコアを取得する iOS アプリを作成する方法を紹介します。このアプリは新しいスコアが入手可能になると、プッシュ通知を送信します。

アプリを作成するために必要となるもの

以降の説明で記載するコードは、IBM DevOps Services の「moccer」プロジェクトから入手することができます。

ステップ 1. バックエンドの API を作成する

このアプリを作成する最初のステップは、以下の処理を行うバックエンドを作成することです。

  • football-data.co.uk から最新のフィードを取得して構文解析する
  • 新しい結果がある場合、その結果で MondoDB データベースを更新する
  • 更新を調べるための API エンドポイントと、日付を指定してスコアを取得するための API エンドポイントを提供する

新規 Sinatra アプリを作成する

まずは、基本的な Sinatra アプリを作成するところから始めましょう。

  1. プロジェクトのディレクトリーを作成して、Gemfile という名前の新規ファイルをそのディレクトリーに追加します。新規ファイルに以下のコードを追加します。
        source 'https://rubygems.org'
        gem 'mongo'
        gem 'bson'
        gem 'bson_ext'
        gem 'sinatra'
        gem 'thin'
  2. コマンド・プロンプトで、カレント・ディレクトリーをプロジェクトのディレクトリーへと変更し、以下のコマンドを実行して依存関係をインストールします。
        $ bundle install
  3. app.rb という名前の新規ファイルを作成し、そのファイルに以下のコードを追加します。
        require 'sinatra'
    
        get '/' do
          'Hello, world!'
        end

    上記のコードで定義しているのは、かなり基本的な「Hello, World」Sinatra アプリで、ユーザーがアプリのルートにアクセスするとメッセージを表示するというものです。

  4. このアプリを実行する前に、別のファイルを新規に作成します。このファイルには config.run という名前を付けて、以下のコードを追加します。
        require './app'
        run Sinatra::Application
  5. アプリを起動するには、プロンプトで以下のコマンドを入力します。
        $ rackup -p 4567

任意の Web ブラウザーを開いて http://localhost:4567 にアクセスすると、お馴染みの「Hello, World!」メッセージが表示されるはずです。

スコア・データを取得して MongoDB に保管する

これから app.rb ファイルの中身をまるごと入れ替えます。このファイルには代わりに、football-data.co.uk サービスからデータを取得し、そのデータを MongoDB データベースに保管するコードを含めます。ローカルの MongoDB データベースが稼働していることを確認してください。そうでないと、アプリは起動されません。これを確認するには、コマンド・ラインで mongo を実行します。「connect failed (接続に失敗しました)」というようなメッセージが表示されたら、MongoDB はローカルで稼働していないことになります。

  1. 最初に必要な作業は、一連の Ruby gem をロードすることです。これらの Ruby gem によって、データを取得し、その CSV コンテンツを構文解析し、データベースに保管できるようになります。app.rb ファイルからすべてのコードを完全に削除してから、以下のコードで置き換えてください。
        require 'net/http'
        require 'uri'
        require 'csv'
        require 'mongo'
        require 'sinatra'
        require 'json'
    
        include Mongo
  2. 次に、2 つの構成ブロックを追加します。1 つ目のブロックは、開発環境内でのみ実行されます (本番環境では、Bluemix でホストされている MongoDB データベースに接続します)。2 つ目のブロックは、どの環境でも動作します。
        configure :development do
          host = 'localhost'
          port = MongoClient::DEFAULT_PORT
          client = MongoClient.new(host, port)
    
          db = client.db('occer-data')
          set :coll, db.collection('scores') || db.create_collection('scores')
        end
    
        configure do
          set :csvUrl, "http://www.football-data.co.uk/mmz4281/1415/E0.csv"
    
          CSV::Converters[:date_to_iso] = lambda do |s|
            begin
              Date.strptime(s, '%d/%m/%y').strftime('%Y-%m-%d')
            rescue
              s
            end
          end
        end

    1 つ目のブロックでは、MongoDB の接続パラメーター、データベース名、コレクションを定義しています (既存のコレクションがない場合は作成します)。2 つ目のブロックでは、サッカーの結果を調べるための URL を定義しています。この URL に含まれる「1415」は、サッカーのシーズンとして 2014/2015 を参照することに注意してください。別のシーズンのデータを取得するには、「1415」をそのシーズンに該当する値で置き換えます。このブロックでは CSV コンバーターも定義しています。このコンバーターは日付を受け取って、それを ISO の標準日付形式 (YYYY-MM-DD) にします。

  3. 次に、基本的な API エンドポイントを定義します。この API エンドポイントは、CSV ファイルの最新バージョンを取得し、そのファイルで新しいレコードの有無をチェックして、新しいレコードがあれば、MongoDB データベース内のデータを更新します。上記のリストのコードの直後に、以下のコードを追加します。
        get '/update' do
          count = settings.coll.find.count
    
          uri = URI.parse(settings.csvUrl)
          response = Net::HTTP.get_response uri
          csv = CSV.new(response.body, { :headers => :first_row, :header_converters => :symbol,
                :converters => [:all, :date_to_iso] })
    
          keep = [ :date, :hometeam, :awayteam, :fthg, :ftag, :ftr, :hthg, :htag, :htr, :referee ]
          data = csv.to_a.map { |row| row.to_hash.select { |k, v| keep.include?(k) } }  
          data.delete_if { |v| v[:ftr] == nil }
    
          records = data.length - count
    
          if records > 0
            # Push notification!
    
            settings.coll.remove
            settings.coll.insert(data)
          end      
    
          { :success => true, :records => records }.to_json
        end

    このコードは、データベース内のスコアの数を取得した後、最新のデータを取得し、そのデータの長さをスコアの数と比較して新しいスコアがあるかどうかを確認します。プッシュ通知を送信するために組み込んだプレースホルダーには、後で戻ります。ここでは簡単のため、データベースからすべてのレコードを削除して CSV ファイルの最新データで置き換えていますが、実際には差分更新を使用することをお勧めします。

  4. この時点で、アプリを起動して、この API エンドポイントにアクセスしたときの動作を確認することができます。それにはまず、Ctrl + C を押して、以下のコマンドをもう一度実行することによって、Rack サーバーを再起動します。
        $ rackup -p 4567
  5. ブラウザーで http://localhost:4567/update にアクセスします。すると、以下のようなレスポンスが表示されるはずです。
        {"success":true,"records":110}

    検出されるレコードの数は、このチュートリアルを読む時期によって異なるため、レコード数が 110 件になることはあり得ません。

試合の日付のリストを取得する

iOS アプリの最初のビューには、試合の日付のリストを表示して、ユーザーが試合の日付をドリルダウンすると、その日に行われた試合のスコアを確認できるようにします。この仕組みを円滑に機能させるには、試合の日付のリストを返す API を提供する必要があります。

  1. app.rb ファイルに以下のコードを追加します。
        get '/dates' do
          dates = settings.coll.distinct('date').to_a.sort! { |x, y| y <=> x }
          dates.to_json
        end

    このコードは、Mongo コレクションから個別の日付のリストを取得し、それを配列に格納してから、配列を逆順にソートして JSON として返します。

  2. Rack サーバーをリロードして http://localhost:4567/dates にアクセスすると、以下のような出力が表示されるはずです。
        ["2014-11-09","2014-11-08","2014-11-03","2014-11-02","2014-11-01","2014-10-27","2014-10-26","2014-10-25","2014-10-20","2014-10-19","2014-10-18","2014-10-05","2014-10-04","2014-09-29","2014-09-28","2014-09-27","2014-09-21","2014-09-20","2014-09-15","2014-09-14","2014-09-13","2014-08-31","2014-08-30","2014-08-25","2014-08-24","2014-08-23","2014-08-18","2014-08-17","2014-08-16"]

    ここでも同じく、皆さんがこのチュートリアルを読む頃までにはもっと多くの試合が行われているはずなので、表示される日付は、この出力より多くなっているはずです。

指定した日のスコアを取得する

最後の API エンドポイントは、指定した日のスコアからなる配列を取得します。この API エンドポイントは、調べる日付を API に指示するクエリー・パラメーターを 1 つ受け取ります。パラメーターが指定されなければ、試合の日付として最も大きい値を持つ日付を求め、それを使用します。

  1. app.rb ファイルに以下のコードを追加してから、このファイルを保存します。
        get '/' do
          date_str = params[:date]
          date = Date.strptime(date_str, '%Y-%m-%d') rescue nil
          
          if date.nil?    
            maxdate = settings.coll.find({}, :fields => ["date"], :limit => 1, :sort => ['date','desc']).to_a
            date_str = maxdate.length > 0 ? maxdate[0]["date"] : ''
            date = Date.strptime(date_str, '%Y-%m-%d') rescue nil    
          end
    
          unless date.nil?
            # Get scores that match the given date
            scores = settings.coll.find({"date" => date_str}).to_a
            scores.to_json
          else
            [].to_json
          end
        end

    この API は、URI に含まれる date クエリー・パラメーターを探します。見つからない場合は、limit ディレクティブと sort ディレクティブを使用して、直近の試合の日付をデータベースから取得します。次に、指定された日付と一致する日付の全試合のスコアを Mongo コレクションで検索し、それらのスコアを JSON として返します。

  2. 前と同じくサーバー・プロセスを再起動しますが、今回のアクセス先は http://localhost:4567/ です。以下のような出力が表示されるはずです。
        [{"_id":{"$oid": "5464efb0f10cf604dd0000d9"},"date":"2014-11-09","hometeam":"Sunderland","awayteam":"Everton","fthg":1,"ftag":1,"ftr":"D","hthg":0,"htag":0,"htr":"D","referee":"L Mason"},{"_id":{"$oid": "5464efb0f10cf604dd0000da"},"date":"2014-11-09","hometeam":"Swansea","awayteam":"Arsenal","fthg":2,"ftag":1,"ftr":"H","hthg":0,"htag":0,"htr":"D","referee":"P Dowd"},{"_id":{"$oid": "5464efb0f10cf604dd0000db"},"date":"2014-11-09","hometeam":"Tottenham","awayteam":"Stoke","fthg":1,"ftag":2,"ftr":"A","hthg":0,"htag":2,"htr":"A","referee":"M Jones"},{"_id":{"$oid": "5464efb0f10cf604dd0000dc"},"date":"2014-11-09","hometeam":"West Brom","awayteam":"Newcastle","fthg":0,"ftag":2,"ftr":"A","hthg":0,"htag":1,"htr":"A","referee":"C Pawson"}]

    皆さんに表示されるデータは異なりますが、キーは同様のはずです。

これで、バックエンドは完成しました。次は、バックエンドを Bluemix にデプロイします。

ステップ 2. バックエンドを Bluemix にデプロイする

アプリを Bluemix にプッシュするには、最初に manifest.yml ファイルをプロジェクト・ディレクトリーに追加する必要があります。この manifest.yml によって、デプロイするアプリに関する簡単な情報を Bluemix に提供します。

  1. manifest.yml ファイルを作成して、以下のコードを追加します。
        applications:
        - name: occer
          host: occer
          disk: 1024M
          path: .
          domain: mybluemix.net
          mem: 128M
          instances: 1

    注: name と host の値は、皆さんのアプリに固有となるように変更してください。

  2. manifest が作成されたので、アプリを Bluemix にプッシュする準備が整いました。コマンド・プロンプトで、以下のコマンドを実行します。これにより、cf ユーティリティーを使用して Bluemix にログインし、コードをクラウドにプッシュすることができます。cf ユーティリティーがまだインストールされていない場合は、このリンク先からダウンロードしてください。cf login コマンドを実行すると、Bluemix アカウントの e-メール・アドレスとパスワードを入力するよう求められます。
        $ cf api https://api.ng.bluemix.net
        $ cf login
        $ cf push
  3. 初めてアプリをプッシュする段階ではエラーが出るため、実際にアプリが実行されるようにはなりません。エラーの原因は、MongoDB の本番構成が存在しないことにあります。このエラーの原因を修正するには、新しい MongoDB サービス・インスタンスをプロビジョニングして、そのインスタンスをアプリにバインドする必要があります。この場合も、名前が固有になるように変更しなければ、インスタンスは機能しません。occer を参照する際に使用しているのは、manifest.yml ファイルの中で作成したアプリ名です。
        $ cf create-service mongodb 100 occer-db
        $ cf bind-service occer occer-db
  4. app.rb ファイルを開いて、前に作成した development 構成ブロックの上に、以下の構成ブロックを追加します。
        configure :production do
          # Get the credentials for the MongoDB Bluemix service
          env = JSON.parse(ENV["VCAP_SERVICES"])["mongodb-2.4"].first["credentials"]
    
          # Connect to MongoDB and authenticate using environment variables
          conn = MongoClient.new(env["hostname"], env["port"])
          db = conn.db(env["db"])
          db.authenticate(env["username"], env["password"])
    
          # Store the DB connection in a setting for easy retrieval later
          set :coll, db.collection('scores') || db.create_collection('scores')
        end
  5. これで、アプリを Bluemix にプッシュする準備が整ったので、以下のコマンドを実行します。
        $ cf push
  6. アプリの URL にアクセスして、アプリが稼働中であることを確認します (アプリには、http://occer.mybluemix.net のような URL が割り当てられます)。アプリが最新の結果を取得したことを確認するには、http://occer.mybluemix.net/update にある update API エンドポイントにアクセスします。すると、以下のような結果が表示されるはずです。
        {"success":true,"records":110}

レコード数は、このチュートリアルを読んでいる時期によって異なるため、110 にはならないでしょう。データベースにすでにデータを入力済みの場合は、レコード数がゼロになることに注意してください。これは、新しいレコードが検出されなかったからです。

次は、これらのスコアを表示する、フロントエンドの iOS アプリを作成します。

ステップ 3. iOS アプリを作成する

Xcode を起動して、master-detail iOS アプリ・プロジェクトを作成します。このプロジェクトには「Occer」という名前を付けて、組織名と Bundler ID を「Demo」に設定します。言語は「Swift」に、端末は「iPhone」に設定してください。「Use Core Data (コア・データを使用)」チェックボックスは選択されていない状態のままにして、「Next (次へ)」をクリックします。プロジェクトの保存先を選択すると、プロジェクトがそこに作成されます。

ユーザー・インターフェースを作成する

  1. プロジェクト・ナビゲーター・パネルで、Main.storyboard ファイルをクリックします。
  2. デザイン・ビューが開き、3 つの画面が表示されます。これらの画面は、ナビゲーション・コントローラー、マスター・ビュー、詳細ビューの 3 つです。マスター・ビューのタイトル・バーをダブルクリックして編集し、「Occer 2014/15」というタイトルに変更します。
  3. マスター・ビューには試合の日付のリストを表示し、日付をタップするとその日に行われた試合のスコアのリストが表示されるようにします。これを実現するには、マスター・ビューと詳細ビューの両方に表ビューが必要になります。「Detail View content goes here (詳細ビューのコンテンツをここに配置)」をクリックし、Backspace キーを押してメッセージを削除します。
  4. 詳細ビューの広くて白い領域をクリックして選択し、Backspace キーを押してその白い領域を削除します。領域が削除されると、グレーアウトされた「Detail View Controller」というテキストが表示されます。
  5. オブジェクト・ライブラリー (通常は、右下にある 3 番目のアイコンをクリックすると表示されます) から「Table View (表ビュー)」オブジェクト (「Table View Controller (表ビュー・コントローラー)」ではありません) を見つけて、このオブジェクトを詳細ビューにドラッグします。
  6. この表ビューに「Table View Cell (表ビューのセル)」オブジェクトをドラッグします。これで、詳細ビューは、マスター・ビューと同じような表示になります。
  7. 追加した「Table View Cell (表ビューのセル)」オブジェクトを選択します。属性インスペクターで、「Style (スタイル)」の値を「Basic (基本)」に変更します。
  8. 「identifier (識別子)」フィールドに、「Cell」と入力します。
  9. 表ビューのセルにラベルが表示されます。そのラベルをクリックして選択します。属性インスペクターで、中央のボタンをクリックして、テキストを中央揃えにします。

以上の操作が完了した時点で、ストーリーボードは以下のようになっています。

Main.storyboard ― UI デザイン
Main.storyboard ― UI デザイン

これで UI の作業は完了したので、アプリをバックエンドに接続する作業に移れます。

バックエンド API を操作するクラスを作成する

  1. Ruby API に接続する 2 つのクラスを新規に作成します。プロジェクト・ナビゲーターで、Occer フォルダーを右クリックして「New File (新規ファイル)」を選択します。
  2. 表示されたメニューから「Swift File (Swift ファイル)」を選択し、「Next (次へ)」をクリックします。
  3. 新規に作成するファイルに「DateAPIController」という名前を付けます。
  4. Xcode エディターに空の Swift ファイルが開きます。ファイルの中身を以下のコードで置き換えます。
    	import Foundation
    
    	protocol DateAPIControllerProtocol {
    	    func didReceiveDates(dates: NSArray)
    	}
    
    	class DateAPIController {
    	    var delegate: DateAPIControllerProtocol?
    	    
    	    init() {
    	        
    	    }
    	    
    	    func getDates() {
    	        let urlPath = "http://occer.mybluemix.net/dates"
    	        let url = NSURL(string: urlPath)
    	        let session = NSURLSession.sharedSession()
    	        let task = session.dataTaskWithURL(url!, completionHandler: {data, response,
                            error -> Void in
    	            if(error != nil) {
    	                println(error.localizedDescription)
    	            }
    	            
    	            var err: NSError?
    	            var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: nil,
                           error: &err) as NSArray
    	            self.delegate?.didReceiveDates(jsonResult)
    	            
    	            if(error != nil) {
    	                println("JSON Error \(err!.localizedDescription)")
    	            }
    	        })
    	        
    	        task.resume()
    	    }
    	}

    上記のコードで定義しているプロトコルにより、ビュー固有の関数をクラスから切り離し、代わりにデリゲートを使用して、サーバーからのレスポンス受信時に UI を更新することが可能になります。getDates() メソッドは Bluemix 上の API にアクセスし、データを JSON として取得してから、このプロトコルのデリゲート関数 didReceiveDates() を呼び出します。

    : urlPath 定数は必ず、Bluemix でホストされている Ruby アプリの URL に変更してください。

  5. 同じようにして別の Swift ファイルを新規に作成します。ただし今回は、ファイル名を「ScoreAPIController」にします。このファイルのコードは DateAPIController とほとんど同じですが、スコア API にアクセスするという点が異なります。ファイルの中身を以下のコードで置き換えます。
    	import Foundation
    
    	protocol ScoreAPIControllerProtocol {
    	    func didReceiveScores(scores: NSArray)
    	}
    
    	class ScoreAPIController {
    	    var delegate: ScoreAPIControllerProtocol?
    	    
    	    init() {
    	        
    	    }
    	    
    	    func getScores(date: NSString) {
    	        let urlPath = "http://occer.mybluemix.net/?date=\(date)"
    	        let url = NSURL(string: urlPath)
    	        let session = NSURLSession.sharedSession()
    	        let task = session.dataTaskWithURL(url!, completionHandler: {data, response,
                            error -> Void in
    	            if(error != nil) {
    	                println(error.localizedDescription)
    	            }
    	            
    	            var err: NSError?
    	            var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: nil,
                            error: &err) as NSArray
    	            self.delegate?.didReceiveScores(jsonResult)
    	            
    	            if(error != nil) {
    	                println("JSON Error \(err!.localizedDescription)")
    	            }
    	        })
    	        
    	        task.resume()
    	    }
    	}

    このファイルと DateAPIController ファイルとの主な違いは、getScores() メソッドは日付ストリングを引数として受け入れ、それをクエリー・ストリング・パラメーターとして API の URL に追加することです。この点を抜かせば、2 つのファイルはほとんど同じように機能します。ここでも、必ず urlPath 定数を Bluemix でホストされている自分の API アプリの URL に変更してください。

Date API クラスをマスター・ビュー・コントローラーに接続する

すべてを正常に機能させるには、作成した API クラスをビュー・コントローラーに接続する必要があります。

  1. プロジェクト・ナビゲーター・パネルで、MasterViewController.swift をクリックして、このファイルのコードを表示します。このファイルを DatesAPIController に接続するために、いくつかの変更をコードに加えます。
  2. 以下の行を見つけます。この行では、MasterViewController を定義しています。
        class MasterViewController: UITableViewController {
  3. 上記の行を以下のように変更して、DateAPIControllerProtocol プロトコルを実装します。
        class MasterViewController: UITableViewController, DateAPIControllerProtocol {
  4. オブジェクトを NSMutableArray として定義している行を、以下の行で置き換えます。
        var objects = NSArray()
        var api = DateAPIController()

    オブジェクトの配列から項目を追加または削除する必要はないので、この配列を NSMutableArray ではなく通常の NSArray として定義します。さらに、DateAPIController クラスを初期化します。
  5. viewDidLoad() メソッドを以下のように変更します。
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
            refreshControl = UIRefreshControl()
            refreshControl!.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
            tableView.addSubview(refreshControl!)
        }

    この変更により、pull-to-refresh コントロール・サブビューが表ビューに追加されます。このアクションを実行すると、refresh() メソッドが呼び出されます。

  6. viewDidLoad() メソッドの直下に、以下のコードを追加します。
        override func viewWillAppear(animated: Bool) {
            super.viewWillAppear(animated)
            self.refresh(self)
        }
    
        func refresh(sender: AnyObject) {
            self.api.delegate = self
            self.api.getDates()
        }
    
        func didReceiveDates(dates: NSArray) {
            dispatch_async(dispatch_get_main_queue(), {
                self.objects = dates
                self.tableView.reloadData()
                self.refreshControl?.endRefreshing()
            })
        }
    viewWillAppear() メソッドは、ビューが表示される際に常に表を最新の情報に更新します。refresh() メソッドは、ビューが表示される時点、または表の pull-to-refresh コントロールが使用される時点で呼び出されます。最後の didReceiveDates() メソッドは、前に定義したDateAPIControllerProtocol の実装であり、オブジェクトの配列に API 呼び出しの結果を格納し、表ビューのデータをリロードし、pull-to-refresh コントロールに最新情報への更新を停止するように指示します。
  7. insertNewObject() メソッドは、このアプリには必要ないので、メソッド全体を削除します。
  8. prepareForSegue() メソッドに含まれる以下の行を見つけます。
        let object = objects[indexPath.row] as NSDate

    上記の行を以下のように変更します。
        let object = objects[indexPath.row] as NSString
  9. 次のいくつかのメソッドはそのままにして、以下のメソッド定義を見つけます。
        override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  10. この定義に含まれる、以下の行を見つけます。
        let object = objects[indexPath.row] as NSDate

    上記の行を以下のように変更します。
        let object = objects[indexPath.row] as NSString
  11. 最後の 2 つの表ビュー・メソッド canEditRowAtIndexPath および commitEditingStyle は、必要ないので削除します。
  12. ファイルを保存します。

今度は、DetailViewController を接続します。

スコア API を詳細ビュー・コントローラーに接続する

  1. 現状の DetailViewController には、必要な表ビュー・メソッドが含まれていないため、多少のコードを追加する必要があります。まず、以下のクラス定義を見つけます。
        class DetailViewController: UIViewController {
  2. このクラスが ScoreAPIControllerProtocol というプロトコルを実装するように定義を変更します。
        class DetailViewController: UITableViewController, ScoreAPIControllerProtocol {
  3. 以下の行を削除してください。
        @IBOutlet weak var detailDescriptionLabel: UILabel!

    代わりに以下の行で置き換えます。
        var objects = NSArray()
        var api = ScoreAPIController()
  4. detailItem の定義を変更して、AnyObject? タイプではなく NSString? タイプになるようにします。
        var detailItem: NSString? {
            didSet {
                // Update the view
                self.configureView()
            }
        }
  5. configureView() メソッドの中身を、refresh という新しいメソッドを呼び出すコードで置き換えます。この新規メソッドは、この後すぐに作成します。
        func configureView() {
            self.refresh(self)
        }
  6. viewDidLoad() メソッドに含まれる self.configure() の行を以下のコードで置き換えて、pull-to-refresh を実装します。
        refreshControl = UIRefreshControl()
        refreshControl!.addTarget(self, action: "refresh:", forControlEvents: UIControlEvents.ValueChanged)
        tableView.addSubview(refreshControl!)
  7. このメソッドの直下に、以下のコードを追加します。
        override func viewWillAppear(animated: Bool) {
            super.viewWillAppear(animated)
            self.refresh(self)
        }
        
        func refresh(sender: AnyObject) {
            if let detail:NSString = self.detailItem {
                self.title = detail
                self.api.delegate = self
                self.api.getScores(detail)
            }
        }
    
        func didReceiveScores(scores: NSArray) {
            dispatch_async(dispatch_get_main_queue(), {
                self.objects = scores
                self.tableView.reloadData()
                self.refreshControl?.endRefreshing()
            })
        }
  8. didReceiveMemoryWarning() メソッドには変更を加えずに、以下の 3 つのメソッドを追加してクラスを完了します。
      	// MARK: - Table View
          
        override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
            return 1
        }
    
        override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return objects.count
        }
        
        override func tableView(tableView: UITableView, cellForRowAtIndexPath
                indexPath: NSIndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
                as UITableViewCell
            let object = objects[indexPath.row] as NSDictionary
            let homeTeam = object["hometeam"] as NSString
            let awayTeam = object["awayteam"] as NSString
            let homeScore = object["fthg"] as NSInteger
            let awayScore = object["ftag"] as NSInteger
            
            cell.textLabel.text = "\(homeTeam) \(homeScore) - \(awayScore) \(awayTeam)"
            return cell
        }

iOS シミュレーターでアプリを実行する

  1. iOS シミュレーターで、ツールバーの「Play (再生)」ボタンをクリックします。これにより、アプリがビルドされて起動します。ビルドが失敗しない限り、以下の図に示すような画面が iOS シミュレーターに表示されます。シミュレーターでのマスター・ビューのスクリーンショット
    シミュレーターでのマスター・ビューのスクリーンショット
  2. 日付をタップすると、以下のような画面が表示されます。シミュレーターでの詳細ビューのスクリーンショット
    シミュレーターでの詳細ビューのスクリーンショット

ヒント: アプリのビルドは正常に行われる一方で、起動時に異常終了するとしたら、アプリが Bluemix API にうまく接続できていない可能性があります。API クラスに正しい URL が設定されていること、そしてインターネットに接続されていることを確認してください。

実際の端末でアプリを実行する

アプリを実際の端末で実行させるには、いくつものステップを踏む必要があります。特に、プッシュ通知を機能させるには、次のセクションで示すようにいくつものステップを踏むことになります。

  1. 「Project Properties (プロジェクト・プロパティー)」の「Background Modes (バックグラウンド・モード)」をオンにします。「Remote notifications (リモート通知)」オプションにチェック・マークを付けます。「Remote notifications (リモート通知)」オプションを有効にする画面のスクリーンショット
    「Remote notifications (リモート通知)」オプションを有効にする画面のスクリーンショット
  2. 実際の端末でアプリを実行するには、プロビジョニング・プロファイルをインストールする必要があります。プロビジョニング・プロファイルをまだ用意していない場合は、Xcode が自動的にセットアップしてくれます。ただし、その場合のプロビジョニング・プロファイルでは、アプリ ID にワイルドカードが使用されるため、このアプリ ID をそのまま、プッシュ通知を行うアプリに使用することはできません。したがって、アプリに対して明示的なアプリ ID をセットアップする必要があります。それには、Apple Developer Center にアクセスして、Member Center にログインします。
  3. 「Certificates, Identifiers & Profiles (証明書、ID、およびプロファイル)」のリンクをクリックします。
  4. 「Identifiers (ID)」をクリックし、右側の「App ID (アプリ ID)」が選択された状態で、プラス・ボタンをクリックして新規アプリ ID を作成します。
  5. アプリの説明を (例えば、「Occer App ID」と) 入力し、「Explicit App ID (明示的アプリ ID)」が選択されていることを確認します。「Bundle ID (バンドル ID)」の値は、アプリのバンドル ID と一致していなければなりません。アプリのバンドル ID は、「Project Properties (プロジェクト・プロパティー)」の「General (一般)」表で調べることができます (今までのガイドラインに従っていれば、Demo.Occer というようなバンドル ID になっているはずです)。
  6. 「Application Services (アプリケーション・サービス)」のリストで「Push Notifications (プッシュ通知)」にチェック・マークが付いていることを確認してから、「Continue (続行)」をクリックします。
  7. 選択内容を確認して、再び「Continue (続行)」をクリックします。これで、アプリ ID が作成されます。
  8. 次は、作成したアプリ ID に対してプッシュ通知を有効にし、プッシュ通知サービスで使用できるクライアント SSL 証明書を生成する必要があります。「App IDs (アプリ ID)」では、作成したアプリ ID を選択します。「Application Services (アプリケーション・サービス)」のリストで、「Push Notifications (プッシュ通知)」の値が「Configurable」になっていることに注目してください。「Application Services (アプリケーション・サービス)」のスクリーンショット
    「Application Services (アプリケーション・サービス)」のスクリーンショット
  9. 「Edit (編集)」ボタンをクリックします。
  10. 「Push Notifications (プッシュ通知)」には、開発環境および本番環境用の SSL 証明書を作成するオプションがあることに注意してください。「Development SSL Certificate (開発用 SSL 証明書)」という見出しの下にある「Create Certificate (証明書の作成)」ボタンをクリックします。画面に表示される指示に従って証明書署名要求 (CSR) を作成してから、「Continue (続行)」をクリックします。
  11. 作成した CSR ファイルを選択してアップロードします。
  12. 「Generate (生成)」をクリックして証明書を生成します。
  13. .cer ファイルをコンピューターにダウンロードします。このファイルを開いて、鍵チェーンに追加します。
  14. 後で Buemix の Push サービスを扱う際に P12 鍵ファイルをアップロードする必要があるので、ここでそのファイルを生成しておきましょう。「My Certificates (マイ証明書)」カテゴリーから「Apple Development IOS Push Services certificate (Apple Development IOS Push Services 証明書)」を選択して展開し、秘密鍵が見えるようにします。
  15. 証明書と秘密鍵の両方を選択して、「File (ファイル)」 > 「Export Items (項目のエクスポート)」の順に進み、「Personal Information Exchange (.p12)」ファイル形式が選択されていることを確認してから、「Save (保存)」をクリックして証明書をコンピューターにエクスポートします。
  16. パスワードと確認パスワードを入力するよう求められます。このパスワードは、このファイルを IBM Bluemix にアップロードするときにも必要になるので、忘れないでください。
  17. 鍵チェーンのログイン・パスワードを入力するよう求められます。パスワードを入力すると、P12 ファイルが作成されます。
  18. 次に、新規プロビジョニング・プロファイルを作成する必要があります。Apple Developer Center の左側のメニューに表示されている「Provisioning Profiles (プロビジョニング・プロファイル)」から「Development (開発)」を選択します。これを選択した上でプラス・ボタンをクリックし、新規プロファイルを追加します。
  19. 最初の画面で、「iOS App Development (iOS アプリ開発)」を選択してから「Continue (続行)」をクリックします。
  20. 作成したアプリ ID が選択されていることを確認してから、再び「Continue (続行)」をクリックします。
  21. アプリの署名に使用した証明書を選択します (この証明書は、Xcode によって自動的に作成されているはずです。作成されていない場合は、実際の iOS 端末でアプリを実行してみてください)。
  22. 次に、プロビジョニング・プロファイルに含める端末を選択します。
  23. 最後に、プロビジョニング・プロファイルに名前を付けて、「Generate (生成)」をクリックします。生成されたプロビジョニング・プロファイルをダウンロードして、Finder で開きます。
  24. プロビジョニング・プロファイルをインストールした後は、正しいプロビジョニング・プロファイルを使用してアプリに署名を付けようとしていることを確認します。Xcode で、「Project Properties (プロジェクト・プロパティー)」ページを表示し、「Build Settings (ビルド設定)」タブを選択します。表示するセクションが「All (すべて)」になっていることを確認し、その中から「Code Signing (コードの署名)」セクションを見つけて、「Provisioning Profile (プロビジョニング・プロファイル)」オプションを探します。このリストから、作成したプロファイルを選択します。
  25. これで、実際の端末でアプリを実行できるようになったので、続いてプッシュ通知を受信するようにアプリを構成しなければなりません。その方法については、次のセクションで説明します。以下の図に、iPhone 6 Plus で実行中のアプリを示します。iOS シミュレーターで実行したときのアプリと同じように見えるかもしれませんが、キャリア名は実在のキャリアになっていて、ネットワーク信号も表示されています。実際の端末で実行中のアプリのスクリーンショット
    実際の端末で実行中のアプリのスクリーンショット

次は、プッシュ通知機能を追加して、アプリを仕上げましょう。

ステップ 4. プッシュ通知を実装する

  1. IBM Bluemix ダッシュボード (https://www.bluemix.net) にログインします。
  2. ここまでで作成しているアプリを選択して、「ADD A SERVICE OR API (サービスまたは API の追加)」をクリックします。
  3. Push サービスを追加するには、Mobile Application Security サービスも追加しなければならないので、最初に Mobile Application Security サービスを追加します。このサービスに「occer-mas」などの名前 (一意の名前にする必要があります) を付けて、サービス・インスタンスを作成します。
  4. アプリの再ステージングを求められたら、「Cancel (キャンセル)」を選択します。
  5. もう一度「ADD A SERVICE OR API (サービスまたは API の追加)」をクリックします。今回は、「Mobile (モバイル)」カテゴリーから「Push」サービスを選択します。
  6. サービスに名前 (例えば、「occer-push」など) を付けます。
  7. アプリの再ステージングを求められたら、「RESTAGE (再ステージ)」をクリックします。

Bluemix で Push サービスを構成する

  1. 作成した occer-push サービスをクリックし、「Congiguration (構成)」タブの「Apple Push Notification Service (APNS)」の下にある「EDIT (編集)」をクリックします。
  2. 「Sandbox Configuration (サンドボックス構成)」セクションで、前に「Keychain Access (鍵チェーン・アクセス)」で作成した P12 証明書ファイルを選択し、そのときに選んだパスワードを入力します。
  3. 「SAVE (保存)」をクリックします。すると、APNS 構成が検証されます。これにはしばらく時間がかかります。検証が完了すると、緑色の確認メッセージが表示されます。
  4. 次に、プッシュ通知を受け取れるように許可するコードを iOS アプリに追加します。このコードは、ユーザーに対し、アプリからのプッシュ通知の受信を承諾するか、拒否するかを尋ねます。それには、Bluemix iOS SDK をダウンロードする必要があります (https://www.ng.bluemix.net/docs/#starters/mobile/index.html)。
  5. ダウンロードした zip ファイルを解凍した後、IBMBluemix.framework フォルダーと IBMPush.framework フォルダーをコピーして Xcode プロジェクトにインポートします。それには、「File (ファイル)」 > 「Add Files to Occer (ファイルを Occer に追加)」の順に選択します。「Copy items if needed (必要に応じて項目をコピー)」にチェック・マークが付けられていることを確認してください。Bluemix SDK は Objective-C で作成されているため、この SDK を Swift コードで使用するには、その前にもう少し作業が必要です。
  6. プロジェクトに新規ファイルを追加します。テンプレート・ダイアログから「Header File (ヘッダー・ファイル)」を選択し、そのファイルに「Occer-Header-Bridge.h」という名前を付けます。ファイルの中身を削除して、以下のコードで置き換えます。
        #import <IBMBluemix/IBMBluemix.h>
        #import <IBMPush/IBMPush.h>
  7. プロジェクトで、Objective-C ブリッジ・ヘッダー・ファイルとしてこのファイルを検索するように指示します。「Project Properties (プロジェクト・プロパティー)」で、「Build Setings (ビルド設定)」タブを選択し、一番下のほうにある「Swift Compiler - Code Generation (Swift コンパイラー ― コードの生成)」という名前のセクションを見つけます。「Objective-C Bridging Header (Objective-C ブリッジング・ヘッダー)」オプションを編集して、値を「Occer-Header-Bridge.h」に設定します。

    : アプリの構造で、このファイルを Occer サブフォルダー内に追加した場合は、代わりに「Occer/Occer-Header-Bridge.h」という値に設定する必要があります。

  8. Swift コードで Bluemix SDK を使用する前に、IBM Bluemix にアクセスして、Occer アプリの概要ページを表示します。「Mobile Options (モバイル・オプション)」ドロップダウン・リストをクリックして、経路、アプリ ID、アプリ・シークレットなどのアプリに関する情報を表示します。この後すぐ、これらの情報を Xcode プロジェクトに組み込むことになります。
  9. AppDelegate.swift ファイルを開いて、didFinishLaunchingWithOptions メソッドを見つけます。このメソッドを以下のコードで置き換えます。
        func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions:
                  [NSObject: AnyObject]?) -> Bool {
            // Override point for customization after application launch.
            IBMBluemix.initializeWithApplicationId("yourappid", andApplicationSecret: "yourappsecret",
                  andApplicationRoute: "yourapproute.mybluemix.net")
            IBMPush.initializeService()
            
            var type = UIUserNotificationType.Badge | UIUserNotificationType.Alert |
                  UIUserNotificationType.Sound
            var settings: UIUserNotificationSettings = UIUserNotificationSettings(forTypes:
                  type, categories: nil)
            
            application.registerUserNotificationSettings(settings)
            application.registerForRemoteNotifications()
            return true
        }
  10. yourappid、yourappsecret、yourapproute の値は、アプリの実際の値で置き換えてください。このコードは基本的に、IBM SDK を初期化して、アプリをリモート通知へ登録することを試みます。ユーザーが初めてアプリを起動すると、アプリのプッシュ通知を許可するかどうかを尋ねられます。再び尋ねられることはありませんが、アプリを起動するたびに登録が試行されます。ユーザーがプッシュ通知を許可しないことを選択したとしても、後で iOS の「Settings (設定)」を使って手作業でプッシュ通知を許可すると、アプリは次回の起動時にその設定を反映します。

  11. AppDelegate.swift ファイルの終わりにある閉じ波括弧 } の前に、以下のコードを追加します。
        func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken
                deviceToken: NSData) {
            var pushService: IBMPush = IBMPush.service()
            
            pushService.registerDevice("occeralias", withConsumerId: "occerconsumerid", withDeviceToken:
                deviceToken.description).continueWithBlock({
                (task: BFTask!) -> BFTask in
                if task.error() != nil {
                    println(task.error().description)
                } else {
                    println(task.result().description)
                }
                
                return task
            })
        }
        
        func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError
                  error: NSError) {
            println("Push Registration Failed. error: \(error.debugDescription)")
        }

    このコードは基本的に、ユーザーがアプリに対して端末へのプッシュ通知の送信を許可すると、端末を IBM Bluemix Push サービスに登録します。occeraliasoccerconsumerid の値は重要ではありません。この場合の一意の ID は、端末のトークンです。

  12. 以上の変更を行った後、実際の端末でアプリを再度実行します。今回は、アプリからのプッシュ通知の受信を許可をするかどうかを尋ねられるので、「Allow (許可する)」をタップします。誤って「No Now (今は許可しない)」をタップしてしまったとしても、随時、iOS の「Settings (設定)」でプッシュ通知を有効にすることができます。このアラートは一度表示されると、それ以降は端末で二度と表示されなくなることに注意してください。プッシュ通知の許可を求めるアラートのスクリーンショット
    プッシュ通知の許可を求めるアラートのスクリーンショット

Bluemix からテスト用のプッシュ通知を送信する

プッシュ通知に端末が登録されたので、その端末にテスト通知を送信することができます。

  1. IBM Bluemix ダッシュボードで、作成しているアプリの Push サービスを開いて、「Registration (登録)」タブを選択します。すると、登録した端末のエントリーが表示されているはずです。次に、「Notification (通知)」タブを選択します。「Sandbox (サンドボックス)」が選択されていることを確認します。「Message text (メッセージ・テキスト)」フィールドに「Testing push notifications for Occer (Occer に対するプッシュ通知のテスト)」と入力して、「NEXT (次へ)」をクリックします。
  2. 「Choose Recipients (受信側の選択)」画面で、「All registered mobile devices (すべての登録済みモバイル端末)」を選択して、「SEND (送信)」をクリックします。

表示される通知のタイプは、端末が現在ロックされているかどうかによって異なります。端末がロックされている場合、以下のような画面が表示されます。

ロックされている端末での表示を示す図
ロックされている端末での表示を示す図

アラートをタップすると、そのまますぐに Occer アプリが起動されます。

新しいスコアの追加時にプッシュ通知を送信する

Bluemix ダッシュボードからプッシュ通知を送信できても、あまり役には立ちません。そこで、Ruby バックエンドに戻って、新しいスコアがデータベースに追加されるたびに通知が送信されるように、変更を加えます。

  1. Ruby プロジェクトの app.rb ファイルを開いて、/update 経路に含まれる以下の行を見つけます。
        # Push notification!
  2. 上記の行の下に、以下のコードを追加します。
        app_id = 'yourappid'
        app_secret = 'yourappsecret'
        push_url = URI('https://mobile.ng.bluemix.net/push/v1/apps/' + app_id + '/messages')
        https = Net::HTTP.new(push_url.host, push_url.port)
        https.use_ssl = true
    
        push_body = {
            :message => {
                :alert => 'New English Premier League soccer scores are available'
            }
          }.to_json    
        
    
        req = Net::HTTP::Post.new(push_url.path, initheader = {'Content-Type' => 'application/json'})
        req['IBM-Application-Secret'] = app_secret
        req.body = push_body
        res = https.request(req)
  3. ファイルを保存して、以下のコマンドによってアプリを Bluemix にプッシュします。
        $ cf push

当然のことながら、この機能が動作することを実際にテストするには、football-data.co.uk サービスから新しいスコアを検出するための、データベースの更新を実行する必要があります。Bluemix のデータベースで最後にスコアを更新した後に、新しいスコアが追加されているという事態は起こりそうにありませんが、幸い、ローカルで強制的に新しいデータを追加する方法があります。それには、mongo CLI クライアントを使用して MongoDB にログインし、データベースからすべてのスコアを削除します。その上で、以下のコマンドを使用してローカル・アプリを再起動すれば、新しいデータが追加されます。

    $ rackup -p 4567

再び http://localhost:4567/update にアクセスすれば、新しいスコアをダウンロードすることができます。これにより、プッシュ通知が送信されることになります。

毎日自動的に更新の有無をチェックする

アプリを完成させるための最後の作業は、アプリに毎日スコアの更新をチェックさせるようにすることです。それには一般にクーロン・ジョブを使用しますが、あいにく IBM Bluemix にはクーロン・ジョブの概念がありません。その一方で、Workload Scheduler というサービスがあります。このサービスを利用すれば、同じことを実現できます。Workload Scheduler を利用して毎日自動的に Bluemix アプリの /update API エンドポイントに HTTP GET リクエストを送信するアプリを作成するのは簡単です。

あるいは、SetCronJob のような無料の Web サービスを利用して、別のアプリを作成せずに同じことを実現することもできます。

まとめ

このチュートリアルでは Ruby と Sinatra から iOS アプリやプッシュ通知に至るまで、さまざまなテクノロジーを見てきましたが、Bluemix プラットフォームを使用すれば、開発者はサーバーや端末の管理について気を遣うことなく、素晴らしい機能を作成することに専念できます。このチュートリアルで説明したアプリは、Bluemix で実現できることのほんの一例です。皆さんが、Bluemix で実現できることとして、どのようなことを思い付くか楽しみにしています。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Mobile development, Cloud computing, Web development
ArticleID=1011603
ArticleTitle=サッカーのスコアを通知するモバイル・アプリを IBM Bluemix 上で作成する
publish-date=07302015