Pringで始める簡単Firebase App開発 for iOS（初心者向け）


⚠️ こちらの記事は非推奨です。

こちらの記事をご参照ください。

__[Cloud Firestore + SwiftUI + Ballcapで始めるFirebaseアプリ開発](
https://qiita.com/1amageek/items/13bb2a08917653e451ce)__
__PringはBallcapの前進となるライブラリで現在はBallcapを推奨しています。__


# Pringで始める簡単Firebase App開発 for iOS

まだFirebaseで開発をしたことがない、開発者向けの記事です。アンドロイド開発者やWeb開発者もすぐに始められるように、なるべく細やかに作業を解説します。


# 準備

### 開発環境
- macOS High Sierra
- Xcode 9.4
- Swift 4
- iOS 11

# Xcodeプロジェクトの作成からFirebaseの準備

## 新しいXcodeプロジェクトを作ろう
まずはXcodeを起動して、新しいプロジェクトを作りましょう。
<img width="836" alt="スクリーンショット 2018-07-10 11.07.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/408f6887-a1b1-b609-f5a7-1204e54fc75c.png">

ここでは、**Master-Detail App**を選択しましょう。

<img width="1512" alt="スクリーンショット 2018-07-10 11.09.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/d8e49ca9-1b97-4a28-6c47-829f93b9d743.png">

**Product Name**には、任意でプロジェクトの名前を入れてください。`Firebase Sample`などで構いません。
**Organization Identifier**から**Bundle Identifier**が生成されるため非常に重要です。お持ちのドメインなどがあればそれを使いましょう。

<img width="658" alt="スクリーンショット 2018-07-10 11.15.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/f2ae4610-230d-d00d-4d83-c1c5481868ef.png">

このようになればOKです。せっかくここまでは出来ましたが一度Xcodeのプロジェクトを閉じて下さい。

## CocoapodsでFirebaseのSDKをインストールしよう

**ターミナル**を使って、プロジェクトのディレクトリまで移動してください。
プロジェクトのディレクトリで次のコマンドを実行します。

```
pod init
```

`Podfile`が生成されて入れば成功です。

<img width="882" alt="スクリーンショット 2018-07-10 11.21.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/99668352-6c6b-8dc6-43cb-45d1ee1f2efb.png">

もしエラーが出た人は、Cocoapodsをインストールしましょう。

https://cocoapods.org/

**Podfile**を次のように編集しましょう。

```ruby
platform :ios, '11.0'

target 'Firebase Sample' do
  use_frameworks!

  pod 'Pring'

end
```

Podfileを保存して、次のコマンドを実行します。

```
pod install
```

次のように出れば成功です。

<img width="797" alt="スクリーンショット 2018-07-10 11.28.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/41f1afb0-0140-fbab-7ace-00d1e26142d5.png">

プロジェクトのディレクトリもファイルが増えていると思います。
`Firebase Sample.xcworkspace`という**Workspace**が出来ているのでこちらを開きましょう。

<img width="882" alt="スクリーンショット 2018-07-10 11.30.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/496c0f97-10f6-7efd-c329-12dfa1486468.png">

次のようにプロジェクトに**Pods**が追加されているのを確認して下さい。
<img width="439" alt="スクリーンショット 2018-07-10 11.32.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/d9d1351a-56a9-017f-dc94-2fd0510ad31f.png">

これでFirebase SDKのインストール作業は終了です。

## Firebaseの準備をしよう
FirebaseのSDKをインストールしましたが、これでFirebaseが使える訳ではありません。Firebaseにアクセスしてデータベースの設定をしましょう。

https://console.firebase.google.com/?pli=1

Firebaseに行って新しいプロジェクトを追加しましょう。

<img width="985" alt="スクリーンショット 2018-07-10 11.49.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/2180d52c-52cf-13dd-3d22-c158077bd6a4.png">

ここからは、既に素晴らしい記事が上がっていたのでこちらをご参考ください。

[Swiftで始めるFirebase入門
](https://qiita.com/Simmon/items/dc3490be59acea7fbe0c)

この作業を終えるとこんな感じに`GoogleService-Info.plist`が追加されているはずです。
<img width="401" alt="スクリーンショット 2018-07-10 11.54.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/535bd332-12de-5a43-b0a2-4fe406a14579.png">

## 開発を始めよう

Xcodeで作成したプロジェクトの初期設定では**Main.storyboard**を使ってViewControllerを起動するようになっていますが、今回はある理由から最初のViewControllerの起動は**AppDelegate**で行うことにします。

まず、プロジェクトの**Deployment Info**の**Main Interface**を空にします。

<img width="1512" alt="スクリーンショット 2018-07-10 12.19.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/f5b5bd03-6870-38f1-7450-58904050ebaa.png">

次に**AppDelegate**を次のように編集します。

```swift
import UIKit
import Firebase

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {

    var window: UIWindow?


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        FirebaseApp.configure()

        let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
        let splitViewController: UISplitViewController = storyboard.instantiateInitialViewController() as! UISplitViewController
        let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController
        navigationController.topViewController!.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
        splitViewController.delegate = self

        self.window = UIWindow(frame: UIScreen.main.bounds)
        self.window?.rootViewController = splitViewController
        self.window?.makeKeyAndVisible()

        return true
    }


    // MARK: - Split view

    func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController:UIViewController, onto primaryViewController:UIViewController) -> Bool {
        guard let secondaryAsNavController = secondaryViewController as? UINavigationController else { return false }
        guard let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return false }
        if topAsDetailController.detailItem == nil {
            // Return true to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
            return true
        }
        return false
    }

}

```

ここで一度ビルドしてみましょう。

真っ白なMasterViewControllerが表示されれば成功です。
<img width="598" alt="スクリーンショット 2018-07-10 12.40.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/d834948c-114c-be37-a829-8f9a45991665.png">

> **Note**
> 今回XcodeからMain.storyboardを起動させなかった理由は、Firebaseの初期化のタイミングにあります。`FirebaseApp.configure()`は`application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool`で記述しますが、Main.storyboardはそれより前にロードされるためMainViewControllerでFirebaseを利用できなくなってしまいます。

### Firebaseに保存しよう

まずデータを保存するモデルを記述しましょう。
まずは、プロジェクトに新しく**Swift File**を追加します。
<img width="2166" alt="スクリーンショット 2018-07-10 13.04.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/aebbb734-0876-13f9-75d5-e35bea892ef5.png">

今回は**Item**というモデルを作ってみましょう。

<img width="737" alt="スクリーンショット 2018-07-10 13.10.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/748f63f6-fba1-99e2-0a82-8159dc9af27c.png">


そしてItemを次のように編集して下さい。
**Pring**はCloud Firestoreのドキュメントをオブジェクトとして扱うことができるライブラリで、以下のようにItemを記述することができます。

```swift

import Pring

@objcMembers
class Item: Object {

    dynamic var name: String?

}
```

それでは早速`Item`をFirebaseに保存してみましょう。
`MasterViewController`の`func insertNewObject(_ sender: Any)`を次のように編集して下さい。

```swift
    @objc
    func insertNewObject(_ sender: Any) {
        let item: Item = Item()
        item.name = "hoge"
        item.save()
    }
```

では、Xcodeでビルドしてみましょう。

右上の「**+**」ボタンを押してみましょう。
<img width="598" alt="スクリーンショット 2018-07-10 13.14.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/a6b8b95f-1b65-7003-6472-26ce2cb9fd29.png">

ではFirebaseの[コンソール](https://console.firebase.google.com/)にアクセスして、CloudFirestoreの中に次のように出ていれば成功です。

もし出ていなければ**ルール**を確認して下さい。今はサンプルコード実行のため非常にゆるいセキュリティルールを適用していますが、これではプロダクトとして出せないので気をつけましょう。

<img width="945" alt="スクリーンショット 2018-07-10 13.17.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/05c82fa8-e696-bc37-e5f4-2180e5bc7cde.png">

> **Note**
> つい先日、Firebaseのセキュリティの緩さがエンジニア界隈で話題になりました。
> この件に関しては、こちらを参考にして下さい。[「Firebaseの脆弱性で1億件超漏洩」の真相とは？](https://qiita.com/Yatima/items/cb1962fb09f827957981)

### MasterViewControllerとFirebaseを繋げよう

Firebaseに`Item`を保存することができました。次にItemを`MasterViewController`のTableViewに表示させてみましょう。

まず、`MasterViewController`にも**Pring**をimportします。

```swift
import UIKit
import Pring // PringをImport
```

次に**DataSource**を準備しましょう。

```swift
var dataSource: DataSource<Item>?
```

`viewDidLoad`の中で次を追記して下さい。ここでは深く説明しませんが、これで繋がります。

```swift
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        navigationItem.leftBarButtonItem = editButtonItem

        let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(insertNewObject(_:)))
        navigationItem.rightBarButtonItem = addButton
        if let split = splitViewController {
            let controllers = split.viewControllers
            detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
        }

        // DataSourceを準備
        self.dataSource = Item.query.dataSource()
            .on({ [weak self] (snapshot, changes) in
                guard let tableView: UITableView = self?.tableView else { return }
                switch changes {
                case .initial:
                    tableView.reloadData()
                case .update(let deletions, let insertions, let modifications):
                    tableView.beginUpdates()
                    tableView.insertRows(at: insertions.map { IndexPath(row: $0, section: 0) }, with: .automatic)
                    tableView.deleteRows(at: deletions.map { IndexPath(row: $0, section: 0) }, with: .automatic)
                    tableView.reloadRows(at: modifications.map { IndexPath(row: $0, section: 0) }, with: .automatic)
                    tableView.endUpdates()
                case .error(let error):
                    print(error)
                }
            }).listen()

    }
```

TableViewのDelegateを次のように書き直して作業は完了です。

```swift
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.dataSource?.count ?? 0
    }
```

```swift
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        let item: Item = self.dataSource![indexPath.row]
        cell.textLabel!.text = item.name
        return cell
    }
```

```swift
    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            self.dataSource!.removeDocument(at: indexPath.row)
        } else if editingStyle == .insert {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
        }
    }
```

MasterViewControllerを載せると次のようになります。
早速ビルドしてみましょう。

```swift
import UIKit
import Pring

class MasterViewController: UITableViewController {

    var detailViewController: DetailViewController? = nil

    var dataSource: DataSource<Item>?

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        navigationItem.leftBarButtonItem = editButtonItem

        let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(insertNewObject(_:)))
        navigationItem.rightBarButtonItem = addButton
        if let split = splitViewController {
            let controllers = split.viewControllers
            detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
        }

        self.dataSource = Item.query.dataSource()
            .on({ [weak self] (snapshot, changes) in
                guard let tableView: UITableView = self?.tableView else { return }
                switch changes {
                case .initial:
                    tableView.reloadData()
                case .update(let deletions, let insertions, let modifications):
                    tableView.beginUpdates()
                    tableView.insertRows(at: insertions.map { IndexPath(row: $0, section: 0) }, with: .automatic)
                    tableView.deleteRows(at: deletions.map { IndexPath(row: $0, section: 0) }, with: .automatic)
                    tableView.reloadRows(at: modifications.map { IndexPath(row: $0, section: 0) }, with: .automatic)
                    tableView.endUpdates()
                case .error(let error):
                    print(error)
                }
            }).listen()

    }

    override func viewWillAppear(_ animated: Bool) {
        clearsSelectionOnViewWillAppear = splitViewController!.isCollapsed
        super.viewWillAppear(animated)
    }

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

    @objc
    func insertNewObject(_ sender: Any) {
        let item: Item = Item()
        item.name = "hoge"
        item.save()
    }

    // MARK: - Segues

//　　　　　　　　ここでは一旦コメントアウト
//    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//        if segue.identifier == "showDetail" {
//            if let indexPath = tableView.indexPathForSelectedRow {
//                let object = objects[indexPath.row] as! NSDate
//                let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
//                controller.detailItem = object
//                controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
//                controller.navigationItem.leftItemsSupplementBackButton = true
//            }
//        }
//    }

    // MARK: - Table View

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.dataSource?.count ?? 0
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        let item: Item = self.dataSource![indexPath.row]
        cell.textLabel!.text = item.name
        return cell
    }

    override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        // Return false if you do not want the specified item to be editable.
        return true
    }

    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            self.dataSource!.removeDocument(at: indexPath.row)
        } else if editingStyle == .insert {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
        }
    }


}
```

次のようにHogeが出ましたか？そうすれば成功です。

<img width="598" alt="スクリーンショット 2018-07-10 13.50.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/a40f6b4b-5049-3d87-f5df-2f0691310562.png">

### Firebaseのリアルタイム性を体験しよう
さてさてここで、Firebaseのリアルタイム感を体験してみましょう。
右上の「**+**」ボタンを押してみましょう。**Hoge**が増えましたね？

### Firebaseの分断耐性を体験しよう
次にMacのWiFiをOffにしてオフライン環境を作りましょう。そして同じく右上の「**+**」ボタンを押してみましょう。
シミュレーターの**Hoge**が増えることが確認できたらFirebaseの[コンソール](https://console.firebase.google.com/)にアクセスしてデータがまだ増えてないことを確認して下さい。（別の端末でみないとWiFi切れてますよ。）
データが増えてないことを確認したらWiFiを復活させてみましょう。WiFiを復活させて少し待つとコンソールにデータが反映されたことが確認できるはずです。

### データの削除もしてみましょう。

左上の「Edit」ボタンを押すことでデータの削除をすることができます。削除した後にコンソールからデータが消えることを確認しましょう。

<img width="598" alt="スクリーンショット 2018-07-10 14.05.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/799d60c5-29b4-d8e6-6487-6321795bc00a.png">


## データを更新する

保存したデータを更新するために、まず`DetailViewController`を修正して行きます。まず`detailItem`を`Item`に変更して下さい。

```swift
    var detailItem: Item? {
        didSet {
            // Update the view.
            configureView()
        }
    }
```

```swift
    func configureView() {
        // Update the user interface for the detail item.
        if let item = detailItem {
            if let label = detailDescriptionLabel {
                label.text = item.name
            }
        }
    }
```

次に`MasterViewController`も修正しましょう。コメントアウトしてたところを次の用に変更します。

```swift
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "showDetail" {
            if let indexPath = tableView.indexPathForSelectedRow {
                let object: Item = self.dataSource![indexPath.row]
                let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
                controller.detailItem = object
                controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
                controller.navigationItem.leftItemsSupplementBackButton = true
            }
        }
    }
```

実行して見ましょう。次のように**hoge**が表示されれば成功です。

<img width="598" alt="スクリーンショット 2018-07-13 10.50.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/f098138f-b1a8-d68b-0388-7fe773a84914.png">

次にStoryboardを修正して、`TextField`と`Button`を追加しましょう。これを`DetailViewController`をIBOutlet, IBActionで繋いで下さい。
<img width="437" alt="スクリーンショット 2018-07-13 10.53.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/bcb13563-e235-629d-4b18-7124f572d2a3.png">

最終的に`DetailViewController`は次のようになります。

```swift

import UIKit
import Pring

class DetailViewController: UIViewController {

    @IBOutlet weak var detailDescriptionLabel: UILabel!
    @IBOutlet weak var textField: UITextField!
    @IBAction func updateAction(_ sender: Any) {
        self.detailItem?.name = self.textField.text
        self.detailItem?.update()
    }

    func configureView() {
        // Update the user interface for the detail item.
        if let item = detailItem {
            if let label = detailDescriptionLabel {
                label.text = item.name
            }
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        configureView()
    }

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

    var disposer: Disposer<Item>?

    var detailItem: Item? {
        didSet {
            // Update the view.
            configureView()
            self.disposer = detailItem?.listen { [weak self] (item, error) in
                if let error: Error = error {
                    print(error)
                    return
                }
                self?.configureView()
            }
        }
    }

    deinit {
        self.disposer?.dispose()
    }
}
```

では実行してみましょう。適当な文字を入れて更新ボタンを押して下さい。

TextLabelに入力した文字が表示されれば成功です。

<img width="598" alt="スクリーンショット 2018-07-13 11.14.21.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/409ffc51-7240-1b99-0195-23758de2905d.png">


ここで注目して頂きたいのが、listenの機能です。次のようにObjectに対して`listen`とするとリアルタイムでデータベースを監視することが可能です。非常に簡単ですね。

```swift
self.disposer = detailItem?.listen { [weak self] (item, error) in
                if let error: Error = error {
                    print(error)
                    return
                }
                self?.configureView()
            }
```

## 画像をアップロードする

サービスを作るためには、メディアをアップロードする必要があります。Pringでの写真アップロードの方法をご説明します。
`Item`を次のように修正して下さい。**Pring**では`File`という型を使って画像をアップロードすることが可能です。`File`を配列にすることで複数画像をアップロードすることも可能ですが、今回は一枚だけアップロードします。

```swift
import Pring

@objcMembers
class Item: Object {

    dynamic var name: String?
    dynamic var image: File?
    // 複数画像をアップロードしたい場合は配列にもOK
    dynamic var images: [File] = []

}
```

Storyboardをさらに修正してDetailViewControllerに`ImageView`を追加してIBOutletでつなぎましょう。

<img width="435" alt="スクリーンショット 2018-07-13 11.16.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/04ade64f-2b93-a1d7-5cbe-b87bf3c0c7f4.png">

UIImagePickerControllerで画像を選択するようにするので、次のように`DetailViewController`を修正して下さい。

`viewDidLoad`でImageViewをタップするとUIImagePickerを呼び出せるようにします。

```swift
    override func viewDidLoad() {
        super.viewDidLoad()
        configureView()

        let tapGestureRecognizer: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImageView))
        self.imageView.addGestureRecognizer(tapGestureRecognizer)
        self.imageView.isUserInteractionEnabled = true
    }

    @objc func didTapImageView() {
        let imagePickerController: UIImagePickerController = UIImagePickerController()
        imagePickerController.sourceType = .photoLibrary
        imagePickerController.delegate = self
        self.present(imagePickerController, animated: true, completion: nil)
    }
```

次に選択した画像をアップロードします。画像のアップロードはitemにFileをセットして`update`を実行するだけです。


```swift
extension DetailViewController: UINavigationControllerDelegate, UIImagePickerControllerDelegate {

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        guard let image: UIImage = info[UIImagePickerControllerOriginalImage] as? UIImage else { return }
        let data: Data = UIImageJPEGRepresentation(image, 0.5)!
        self.detailItem?.image = File(data: data)
        self.uploadTasks = self.detailItem?.update()
        picker.dismiss(animated: true, completion: nil)
    }

}
```

アップロード中のプログレスは`uploadTasks`の中に含まれる`StorageUploadTask`で取得することができます。

```swift
    var uploadTasks: [String: StorageUploadTask]? {
        didSet {
            let uploadTask: StorageUploadTask = uploadTasks![uploadTasks!.keys.first!]!
            uploadTask.observe(.progress) { (snapshot) in
                print(snapshot.progress)
            }
        }
    }
```

`configureView`も少し修正しましょう。

```swift
    var donwloadTask: StorageDownloadTask?

    func configureView() {
        // Update the user interface for the detail item.
        if let item = detailItem {
            if let label = detailDescriptionLabel {
                label.text = item.name
            }
            donwloadTask = item.image?.getData(completion: { [weak self](data, error) in
                if let error = error {
                    print(error)
                    return
                }
                self?.imageView.image = UIImage(data: data!)
            })
        }
    }
```

最終的に`DetailViewController`は次のようになります。

```swift
import UIKit
import Firebase
import Pring

class DetailViewController: UIViewController {

    @IBOutlet weak var detailDescriptionLabel: UILabel!
    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var textField: UITextField!
    @IBAction func updateAction(_ sender: Any) {
        self.detailItem?.name = self.textField.text
        self.detailItem?.update()
    }

    var uploadTasks: [String: StorageUploadTask]? {
        didSet {
            let uploadTask: StorageUploadTask = uploadTasks![uploadTasks!.keys.first!]!
            uploadTask.observe(.progress) { (snapshot) in
                print(snapshot.progress)
            }
        }
    }

    var donwloadTask: StorageDownloadTask?

    func configureView() {
        // Update the user interface for the detail item.
        if let item = detailItem {
            if let label = detailDescriptionLabel {
                label.text = item.name
            }
            donwloadTask = item.image?.getData(completion: { [weak self](data, error) in
                if let error = error {
                    print(error)
                    return
                }
                self?.imageView.image = UIImage(data: data!)
            })
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        configureView()

        let tapGestureRecognizer: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImageView))
        self.imageView.addGestureRecognizer(tapGestureRecognizer)
        self.imageView.isUserInteractionEnabled = true
    }

    @objc func didTapImageView() {
        let imagePickerController: UIImagePickerController = UIImagePickerController()
        imagePickerController.sourceType = .photoLibrary
        imagePickerController.delegate = self
        self.present(imagePickerController, animated: true, completion: nil)
    }

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

    var disposer: Disposer<Item>?

    var detailItem: Item? {
        didSet {
            // Update the view.
            configureView()
            self.disposer = detailItem?.listen { [weak self] (item, error) in
                if let error: Error = error {
                    print(error)
                    return
                }
                self?.configureView()
            }
        }
    }

    deinit {
        self.disposer?.dispose()
    }
}

extension DetailViewController: UINavigationControllerDelegate, UIImagePickerControllerDelegate {

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        guard let image: UIImage = info[UIImagePickerControllerOriginalImage] as? UIImage else { return }
        let data: Data = UIImageJPEGRepresentation(image, 0.5)!
        self.detailItem?.image = File(data: data)
        self.uploadTasks = self.detailItem?.update()
        picker.dismiss(animated: true, completion: nil)
    }
}
```

では実行してみましょう。

ImageViewをタップして画像を選択すれば数秒で画像がアップロードされるはずです。そしてImageViewに画像が表示されれば成功です。

<img width="598" alt="スクリーンショット 2018-07-13 11.54.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/b83a1c92-cffc-0999-3251-89562d9c3dff.png">

また、プログレスが`print`で表示されいるので見てみましょう。

<img width="796" alt="スクリーンショット 2018-07-13 11.55.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/80287/b28c41d7-1734-f74e-4674-5752646ad05b.png">

このように**Pring**はApp開発に置いて必要になってくる機能を助けてくれる強力な機能を提供しています。

https://github.com/1amageek/Pring