APIで取得したデータをswiftのTableViewに表示する

# 概要

　この記事は初心者の自分がRESTfulなAPIとswiftでiPhone向けのクーポン配信サービスを開発した手順を順番に記事にしています。技術要素を1つずつ調べながら実装したため、とても遠回りな実装となっております。

　前回の[swiftでwebAPIを呼び出してjsonデータを表示させる](https://qiita.com/Ajyarimochi/items/5a6e7a44d3fb2ab37f25)でwebAPIからレスポンスしたデータからjsonをパースして取り出す処理が出来たので、次はjsonのクーポン情報をSwiftのTableViewに一覧表示させます。

（なお、現在のAPIで取得出来るクーポン情報は1件なのでTableViewである必要はありませんが、このあと複数件のクーポン情報を取得できるように改造するのでTableViewを使います。）

### 参考

* やさしくはじめる　iPhoneアプリ作りの教科書　森 巧尚 著　マイナビ
* [【swift入門】apiを叩いてTableViewに表示させる](https://fukatsu.tech/api-tableview)

### 環境
Mac OS 10.15
Swift5
Xcode11.1

### アプリの仕様

* アプリ起動時点で利用可能なクーポンの特典と利用期限をTableViewで一覧表示する。


# 手順

* Xcodeでアプリ画面をデザインする
* TableViewにクーポンの特典と利用期限を表示する。

### Xcodeでアプリ画面をデザインする

`Main.storyboard`を開いて画面のデザインをします。
右上の「＋」ボタンを押すとUIツールキットなどの一覧が表示されるので、Table Viewを選択。
とりあえず画面いっぱいにテーブルを表示するよう、AutoLayoutで上下左右それぞれ縁からの距離を0ポイントに固定。実行すると画面いっぱいにテーブルが表示されます。
<img width="864" alt="ami_coupon_UIdesign-01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526594/bd24bd1d-81b3-7b16-c672-5664c8d18208.png">


レイアウトしたTable Viewを`ViewController`に繋げます。繋げる際のプロトコルとして`dataSource`と`delegate`を選びます。手順はTable Viewをクリックすると線が表示されるので、上部の`ViewController`に線をドラッグし、メニューを表示させます。
<img width="292" alt="UI-connect-viewController-01-mask.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526594/77769c48-3840-2251-3b49-274077ef16e6.png">


次に`dataSource`を選択します。同じ方法で`delegate`も選択します。
<img width="288" alt="UI-connect-viewController-02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526594/f06ed0d1-577e-bf0c-13ef-619f80d6a050.png">




Table View で右クリック（Control + クリック）をして`dataSource`と`delegate`で接続されていることを確認します。
<img width="511" alt="UI-connect-viewController-03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526594/139c3a04-8350-846f-1f8f-50b9627ec8c7.png">



### TableViewにクーポンの特典と利用期限を表示する。

ここから`ViewController.swift`を改造します。

 まず、`class ViewController` に２つのプロトコル`UITableViewDataSource`と`UITableViewDelegate`を追加します。

`class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {`の部分にエラーが表示されますが、テーブルを表示するのに最低限必要な `numberOfRowsInSection`（行数の指定）と`cellForRowAt`（表示するデータ）が設定されていないのが原因です。この後の実装でエラーは消えるので一旦無視します。

次に、テーブルに表示するメンバー変数（クラス内で共有可能な変数）を下記の通り定義します。メンバー変数は多用したくないですが、引数としてテーブルに渡す事が難しいので、とりあえずメンバー変数を使います。

クーポン特典を保持する変数`couponBenefit`と、有効期限を保持する変数`couponDeadline`を定義します。

```swift
var couponBenefit: String = ""
var couponDeadline: String = ""
```

 次に、テーブルを表示するのに最低限必要な`numberOfRowsInSection`（行数の指定）と`cellForRowAt`（表示するデータ）を指定します。指定は関数で行います。

行数の指定はこちらです。現在はwebAPIから1件のクーポン情報しか取得しないので、行数は固定値1を返す仕様にします。

```swift
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }
```

表示する情報の指定はこちらです。

```swift

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //セルを作る
        let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "couponCell")
        //テキストにクーポン特典を設定
        cell.textLabel?.text = self.couponBenefit
        //サブテキストにクーポンの有効期限を設定
        cell.detailTextLabel?.text = "有効期限：" + self.couponDeadline

        return cell
    }
```


次は、`override funk viewDidLoad()` メソッドの中のJSONをパースする処理の後にクーポン特典と有効期限の情報をメンバー変数に格納する処理を追加します。

```swift

self.couponBenefit = couponData["coupon_benefits"] as! String
self.couponDeadline = couponData["coupon_deadline"] as! String
```

ここでアプリを実行してみます。シミュレータの画面には何も表示されません。

これはAPIからのレスポンスとテーブルが描画される時間のタイムラグが原因です。
APIからのレスポンスを受け取って変数の`couponBenefit`と`couponDeadline`へデータを格納しているうちに、テーブルが先に描画されています。

解消するには、APIからのレスポンスを受け取って各変数にデータを格納した後に、テーブルをリロードします。

まず、`Main.storyboardのtableView`から `ViewController`にラインを引っ張って、`@IBOutlet`の設定をします。

```swift

@IBOutlet weak var tableView: UITableView!
```

次にメンバー変数の定義部分を改造します。変数の`couponBenefit`か`couponDeadline`のデータが変更された際にtableViewをリロードするように`didSet{}`を使って`tableView.reloadData()`が実行されるようにします。

```swift

var couponBenefit: String = "" {
        didSet{
            tableView.reloadData()
        }
    }
var couponDeadline: String = "" {
        didSet{
            tableView.reloadData()
        }
    }
```

次に、テーブルの描画が必ずメインスレッドで実行されるようにします。
テーブルはメインスレッドで描画される必要がありますが、他の処理とのタイミングによってバックグラウンドで実行されてしまう事があります。下記のようにプログラムを修正します。

```swift

    DispatchQueue.main.async() { () -> Void in
        self.couponBenefit = couponData["coupon_benefits"] as! String
        self.couponDeadline = couponData["coupon_deadline"] as! String
        }
```

以上で`ViewController`の改造は終わりです。作成したプログラムはこちらになります。

```swift

import UIKit

class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {
    @IBOutlet weak var tableView: UITableView!
    
    var couponBenefit: String = "" {
        didSet{
            tableView.reloadData()
        }
    }
    var couponDeadline: String = "" {
        didSet{
            tableView.reloadData()
        }
    }

    
    override func viewDidLoad() {
        super.viewDidLoad()

        let url: URL = URL(string: "http://127.0.0.1:8000/coupon/?coupon_code=0001")!
        let task: URLSessionTask = URLSession.shared.dataTask(with: url, completionHandler: {(data, response, error) in
            do  {
                let couponData = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.allowFragments) as! [String: Any]
                print(couponData)
                                
                DispatchQueue.main.async() { () -> Void in
                    self.couponBenefit = couponData["coupon_benefits"] as! String
                    self.couponDeadline = couponData["coupon_deadline"] as! String
                    }
                }
            catch {
                print(error)
                }
        })
        task.resume()
    }
    
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //セルを作る
        let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "couponCell")
        //テキストにクーポン特典を設定
        cell.textLabel?.text = self.couponBenefit
        //サブテキストにクーポンの有効期限を設定
        cell.detailTextLabel?.text = "有効期限：" + self.couponDeadline

        return cell
    }
    
}
```

### 動作確認

 djangoのサーバを起動してwebAPIが利用できる状態にし、Xcodeでアプリを実行します。すると、仕様とおりクーポンの特典と有効期限が表示されました。
<img width="382" alt="test-ami-coupon-01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526594/77889989-8817-033d-512b-5a05a59328a8.png">


次は[webAPI側を改造してDBで管理しているデータをレスポンスするようにします](https://qiita.com/Ajyarimochi/items/0835a2719b9b017459e2)