フロントエンドを Swift で、バックエンドを Rails でアプリを開発してみます。
Web API 側実装
grape という gem を使って Web API を作っていきます。
以下の記事も参考にしてみてください。
Grape で Web API 開発 - kzy52's blog
Grape::Entity の使い方 - kzy52's blog
Rails プロジェクト作成
$ mkdir api-demo;cd api-demo $ echo api-demo > .rbenv-gemsets $ rbenv local 2.2.0 $ gem install bundler $ gem install rails $ rails new . --skip-sprockets
データベース作成
$ rake db:create
ユーザテーブルの作成
$ rails g model User name:string email:string $ rake db:migrate
API 基盤の実装
gem の追加
# Gemfile ... gem 'grape' gem 'grape-entity'
$ bundle install
ディレクトリの作成
$ mkdir -p app/apis/api/v1 $ mkdir -p app/apis/entity/v1
app/apiをオートロードに追加
# config/application.rb module ApiDemo class Application < Rails::Application ... # 以下を追加 config.paths.add File.join('app', 'apis'), glob: File.join('**', '*.rb') config.autoload_paths += Dir[Rails.root.join('app', 'apis', '*')] end end
API のルーティング追加
# config/routes.rb Rails.application.routes.draw do mount API::Base => '/' end
ベースクラスの作成
# app/apis/api/base.rb module API class Base < Grape::API end end
API のルーティングを確認するタスクの追加
# lib/tasks/routes.rake namespace :api do desc 'API Routes' task routes: :environment do API::Base.routes.each do |api| method = api.route_method.ljust(10) path = api.route_path.gsub(':version', api.route_version) puts " #{method} #{path}" end end end
「rake api:routes」でgrapeで定義した API のルーティングを確認できます。
APIの実装
# app/apis/api/v1/base.rb module API module V1 class Base < Grape::API format :json default_format :json prefix 'api' version 'v1', using: :path end end end
# app/apis/api/base.rb module API class Base < Grape::API # 以下を追加 mount V1::Base end end
# app/apis/entity/v1/users_entity.rb module Entity module V1 class UsersEntity < Grape::Entity expose :id, :name, :email end end end
# app/apis/api/v1/users.rb module API module V1 class Users < Grape::API resource :users do get do present User.all, with: Entity::V1::UsersEntity end end end end end
# app/apis/api/v1/base.rb module API module V1 class Base < Grape::API ... mount V1::Users end end end
データを入れて curl コマンドで API を叩いてみると以下のように返ってくると思います。
$ curl http://localhost:3000/api/v1/users [{"id":1,"name":"user1","email":"user1@example.com"},{"id":2,"name":"user2","email":"user2@example.com"},{"id":3,"name":"user3","email":"user3@example.com"},{"id":4,"name":"user4","email":"user4@example.com"},{"id":5,"name":"user5","email":"user5@example.com"}]%
アプリ側実装
テンプレートは Single View Application で言語は Swift を選択してプロジェクトを作成します。
ここでは APIDemo というプロジェクト名にします。
Xcode はいったん閉じてください。
プロジェクトに移動する
先ほど作成した Xcode のプロジェクトに移動します。
$ cd APIDemo
Alamofire のインストール
今回はWeb APIをコールするので Alamofire/Alamofire · GitHub というHTTP通信用ライブラリ を使用します。
CocoaPods のインストール
CocoaPods はiOSライブラリ管理ツールです。
インストールされていない場合はインストールしてください。
$ gem install cocoapods
Podfileの作成
$ pod init $ vim Podfile
# Podfile source 'https://github.com/CocoaPods/Specs.git' platform :ios, '8.0' use_frameworks! target 'APIDemo' do pod 'Alamofire', '~> 1.1' end target 'APIDemoTests' do end
ライブラリのインストール
$ pod install
プロジェクトを開く
$ open APIDemo.xcworkspace
UITableView を使う準備
UITableView はテーブル表示にしたい場合に使用します。
今回はユーザ一覧の表示に使います。
// ViewController.swift import UIKit // UITableViewを使用する際はUITableViewDataSourceプロトコルとUITableViewDelegateプロトコルを実装する必要がある class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { let cellId = "MyCell" // 今回はテーブル表示にしたいので UITableView を使う var tableView : UITableView? override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. // 横幅、高さ、ステータスバーの高さを取得する let width: CGFloat! = self.view.bounds.width let height: CGFloat! = self.view.bounds.height let statusBarHeight: CGFloat! = UIApplication.sharedApplication().statusBarFrame.height self.tableView = UITableView(frame: CGRectMake(0, statusBarHeight, width, height - statusBarHeight)) // デリゲートを指定する self.tableView!.delegate = self self.tableView!.dataSource = self // UITableViewにセルとして使うクラスを登録する self.tableView!.registerClass(UITableViewCell.self, forCellReuseIdentifier: cellId) // Viewに追加する。 self.view.addSubview(self.tableView!) } // セルの総数を返す(表示するテーブルの行数) // UITableViewDataSource を使う場合は 必須 func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // とりあえずは適当に返しておく return 1 } // 表示するセルを生成して返す // UITableViewDataSource を使う場合は 必須 func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { // UITableViewCellはテーブルの一つ一つのセルを管理するクラス。 let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellId, forIndexPath: indexPath) as UITableViewCell return cell } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }
ユーザを保持するクラスの作成
- [APIDemo] を右クリック > [New Group] を選択しグループ名を 「Models」 します。
- [Models]を選択し[ユーティリティエリア] > [Identity and type]の フォルダアイコンをクリックする。
- [New Folder] をクリックし、ダイアログが表示されるので「Models」ディレクトリを作成します。
- [Choose] ボタンをクリックします。
- [Models]を右クリック > [New File...] > [iOS] > [Source] > [Swift File] を選択し [Next] ボタンをクリックします。
- ファイル名を「User.swift」にし3で作成した[Models]に保存します。
// Models/User.swift import UIKit struct User { var name: String, email: String } class UserDataManager: NSObject { var users: [User] // シングルトンにする // UserDataManager.sharedInstanceで常に同じインスタンスを取り出すことができる。 class var sharedInstance : UserDataManager { struct Static { static let instance : UserDataManager = UserDataManager() } return Static.instance } override init() { self.users = [] } // ユーザーの総数を返す。 var size : Int { return self.users.count } // 配列のように[n]で要素を取得できるようにする。 subscript(index: Int) -> User { return self.users[index] } func set(user: User) { self.users.append(user) } }
// ViewController.swift class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { ... var tableView : UITableView? var users = UserDataManager.sharedInstance ... // セルの総数を返す(表示するテーブルの行数) // UITableViewDataSource を使う場合は必須 func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.users.size } // 表示するセルを生成して返す // UITableViewDataSource を使う場合は必須 func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { // UITableViewCellはテーブルの一つ一つのセルを管理するクラス。 let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellId, forIndexPath: indexPath) as UITableViewCell // Cellに値を設定する. let user: User = self.users[indexPath.row] as User cell.textLabel?.text = user.email return cell } ... }
Web API へのリクエスト
[APIDemo] を右クリック > [New File...] > [iOS] > [Source] > [Swift File]を選択し、[Next]ボタンをクリックします。
[APIDemo] 配下に 「Router.swift」という名前で保存します。
ここは Alamofire/Alamofire · GitHub を参考にしました。
// Router.swift import Alamofire enum Router: URLRequestConvertible { static let baseURLString = "http://localhost:3000" case GetUsers() var URLRequest: NSURLRequest { let (method: Alamofire.Method, path: String, parameters: [String: AnyObject]?) = { switch self { case .GetUsers: return (.GET, "/api/v1/users", nil) } }() let URL = NSURL(string: Router.baseURLString)! let URLRequest = NSURLRequest(URL: URL.URLByAppendingPathComponent(path)) let encoding = Alamofire.ParameterEncoding.URL return encoding.encode(URLRequest, parameters: parameters).0 } }
// ViewController.swift import Alamofire class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { ... override func viewWillAppear(animated: Bool) { self.request() } // Web API をコールする func request() { Alamofire.request(Router.GetUsers()).responseJSON { (request, response, json, error) -> Void in if let json = json as? Array<Dictionary<String,AnyObject>> { for j in json { var user: User = User( name: j["name"] as NSString, email: j["email"] as NSString ) self.users.set(user) } dispatch_async(dispatch_get_main_queue(), { self.tableView!.reloadData() }) } } } ...