GoogleアカウントにサインインからのAlamofireでGoogle Drive APIを叩くまで

最近、iOSアプリでGoogle Driveのスプレッドシート一覧を取得したくて色々と調べてました。
サンプルとして、GoogleアカウントにサインインしてREST APIを叩くまでのアプリを作ってみたので、
似たようなことをしたい方がいたら参考にしてください。

# 環境

- Swift 2.1.1
- Xcode 7.2
- CocoaPods 0.39.0

# 1. プロジェクトの作成
まずはXcodeでプロジェクトを新規に作成し、CocoaPodsで必要なライブラリをインストールします。

```ruby:Podfile
platform :ios, '8.0'

use_frameworks!

pod 'Google/SignIn' # Googleアカウント認証
pod 'Alamofire'     # HTTPクライアント
pod 'SwiftyJSON'    # JSONパーサ

target 'TestGoogle' do

end
```

# 2. Google Developers Consoleでプロジェクト作成
[このページ](https://developers.google.com/identity/sign-in/ios/start)の`GET A CONFIGURATION FILE`というリンクからGoogleのサービスを有効にするための設定ファイルを取得します。

1. アプリ名、Bundle IDを入力します。アプリ名は適当、Bundle IDは先ほど作ったXcodeプロジェクトのそれを入力してください。
2. Google Sign-Inを有効にして`Generate configuration files`をクリックしてください。
3. GoogleService-info.plistをダウンロードします。このファイルは後で使います。

# 3. Drive APIを有効にする
先ほど作成したGoogleアプリのプロジェクトを[ここ](https://console.developers.google.com/project)から選択し、Drive APIを有効にします。

1. ダッシュボードで「Google APIを利用する」をクリックします。
![api.png](https://qiita-image-store.s3.amazonaws.com/0/17731/2d681d45-6985-f635-7fef-00c2ede5fa44.png)

2. Drive APIをクリックし、「APIを有効にする」をクリックします。
![api2.png](https://qiita-image-store.s3.amazonaws.com/0/17731/dbca8d8d-1470-5db9-f08b-2f029dbd99ef.png)

# 4. iOSアプリにGoogleプロジェクトの情報を組み込む
コードを書く前の最後の下準備になります。

1. ダウンロードしたGoogleService-Info.plistをXcodeのプロジェクトトップに配置します。
![plist.png](https://qiita-image-store.s3.amazonaws.com/0/17731/aa8da42e-7a1d-6396-a15e-614423ae62d9.png)

2. Info > URL TypesにGoogleService-Info.plistに書かれている`REVERSED_CLIENT_ID`とiOSアプリのBundle IDを入力します。
![info.png](https://qiita-image-store.s3.amazonaws.com/0/17731/866f1b77-9a4e-2e7f-70a0-b1ee9ef8a996.png)

3. `<プロジェクト名>-Bridging-Header.h`というヘッダーファイルを作成し、プロジェクトトップに配置します。ヘッダーファイルには`#import <Google/SignIn.h>`とだけ記述してください。

4. 最後にBuild Settings > Swift Compiler - Code Generation > Objective-C Bridging Headerに先ほど作成した`<プロジェクト名>-Bridging-Header.h`を入力してください。

# 5. コーディング
いよいよコーディング（＋ストーリーボードの編集）を行います。最終的に、ボタンを押すとログインユーザ自身がオーナーのスプレッドシート一覧が取得できるようなアプリを作ってみます。
![sample.gif](https://qiita-image-store.s3.amazonaws.com/0/17731/a6cf2938-80c4-706a-ce4a-6dadc3a90a86.gif)

## 5.1. AppDelegate.swift

```swift:AppDelegate.swift
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GIDSignInDelegate {

    var window: UIWindow?

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        var configureError: NSError?
        GGLContext.sharedInstance().configureWithError(&configureError)
        assert(configureError == nil, "Error configuring Google services: \(configureError)")

        GIDSignIn.sharedInstance().delegate = self
        return true
    }

    func application(application: UIApplication,
        openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {
            return GIDSignIn.sharedInstance().handleURL(url,
                sourceApplication: sourceApplication,
                annotation: annotation)
    }

    @available(iOS 9.0, *)
    func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool {
        return GIDSignIn.sharedInstance().handleURL(url,
            sourceApplication: options[UIApplicationOpenURLOptionsSourceApplicationKey] as! String?,
            annotation: options[UIApplicationOpenURLOptionsAnnotationKey])
    }

    func signIn(signIn: GIDSignIn!, didSignInForUser user: GIDGoogleUser!,
        withError error: NSError!) {
            if (error == nil) {
                let name = user.profile.name
                NSNotificationCenter.defaultCenter().postNotificationName(
                    "ToggleAuthUINotification",
                    object: nil,
                    userInfo: ["statusText": "Signed in user: \(name)"])
            } else {
                print("\(error.localizedDescription)")
                NSNotificationCenter.defaultCenter().postNotificationName(
                    "ToggleAuthUINotification", object: nil, userInfo: nil)
            }
    }

    func signIn(signIn: GIDSignIn!, didDisconnectWithUser user:GIDGoogleUser!,
        withError error: NSError!) {
            NSNotificationCenter.defaultCenter().postNotificationName(
                "ToggleAuthUINotification",
                object: nil,
                userInfo: ["statusText": "User has disconnected."])
    }
}
```

## 5.2. Main.storyboard
ログイン状況を表すラベル、ログインボタン、スプレッドシート一覧を取得するボタンを配置します。

![main.png](https://qiita-image-store.s3.amazonaws.com/0/17731/13cb881e-3b79-01e4-bc85-5599b730d388.png)

## 5.3. ViewController.swift

```swift:ViewController.swift
import UIKit
import Alamofire
import SwiftyJSON

class ViewController: UIViewController, GIDSignInUIDelegate {

    // 関連付けを忘れないように
    @IBOutlet weak var statusText: UILabel!
    @IBOutlet weak var signInButton: GIDSignInButton!
    @IBOutlet weak var fileListButton: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        GIDSignIn.sharedInstance().uiDelegate = self
        // Drive APIを叩くのでこれをscopeに足すこと
        GIDSignIn.sharedInstance().scopes.append("https://www.googleapis.com/auth/drive.readonly")

        // 自動ログイン
        GIDSignIn.sharedInstance().signInSilently()

        NSNotificationCenter.defaultCenter().addObserver(self,
            selector: "receiveToggleAuthUINotification:",
            name: "ToggleAuthUINotification",
            object: nil)

        statusText.text = "Initialized Swift app..."
        toggleAuthUI()
    }

    deinit {
        NSNotificationCenter.defaultCenter().removeObserver(self,
            name: "ToggleAuthUINotification",
            object: nil)
    }

    func toggleAuthUI() {
        if (GIDSignIn.sharedInstance().hasAuthInKeychain()){
            signInButton.hidden = true
        } else {
            signInButton.hidden = false
            statusText.text = "Google Sign in\niOS Demo"
        }
    }

    func receiveToggleAuthUINotification(notification: NSNotification) {
        if (notification.name == "ToggleAuthUINotification") {
            self.toggleAuthUI()
            if notification.userInfo != nil {
                let userInfo:Dictionary<String,String!> =
                notification.userInfo as! Dictionary<String,String!>
                self.statusText.text = userInfo["statusText"]
            }
        }
    }

    // 関連付けを忘れないように
    @IBAction func getFileList(sender: AnyObject) {
        let me = GIDSignIn.sharedInstance().currentUser
        let email = me.profile.email
        let token = me.authentication.accessToken

        let url = "https://www.googleapis.com/drive/v2/files"
        let method = Alamofire.Method.GET
        // MIME Typeがスプレッドシートで、オーナーに自身を含むものを検索
        let query = "mimeType = 'application/vnd.google-apps.spreadsheet' and '" + email + "' in owners"

        Alamofire.request(method, url, parameters: [ "access_token": token, "q":  query ])
            .responseJSON { response in
                let json = JSON(response.result.value!)
                for (_, sub): (String, JSON) in json["items"] {
                    print(sub["title"].string! + ", " + sub["id"].string!)
                }
        }
    }
}
```

以上で完成です！
公式のドキュメントが古かったりして少し苦労しましたが、
これでサインインからREST APIを叩くまで一通りできるようになりました。