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


概要

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

 前回のswiftでwebAPIを呼び出してjsonデータを表示させるでwebAPIからレスポンスしたデータからjsonをパースして取り出す処理が出来たので、次はjsonのクーポン情報をSwiftのTableViewに一覧表示させます。

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

参考

環境

Mac OS 10.15
Swift5
Xcode11.1

アプリの仕様

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

手順

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

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

Main.storyboardを開いて画面のデザインをします。
右上の「+」ボタンを押すとUIツールキットなどの一覧が表示されるので、Table Viewを選択。
とりあえず画面いっぱいにテーブルを表示するよう、AutoLayoutで上下左右それぞれ縁からの距離を0ポイントに固定。実行すると画面いっぱいにテーブルが表示されます。

レイアウトしたTable ViewをViewControllerに繋げます。繋げる際のプロトコルとしてdataSourcedelegateを選びます。手順はTable Viewをクリックすると線が表示されるので、上部のViewControllerに線をドラッグし、メニューを表示させます。

次にdataSourceを選択します。同じ方法でdelegateも選択します。

Table View で右クリック(Control + クリック)をしてdataSourcedelegateで接続されていることを確認します。

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

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

まず、class ViewController に2つのプロトコルUITableViewDataSourceUITableViewDelegateを追加します。

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

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

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

var couponBenefit: String = ""
var couponDeadline: String = ""

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

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

    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
    }

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


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

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

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

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

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


@IBOutlet weak var tableView: UITableView!

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


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

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


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

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


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でアプリを実行します。すると、仕様とおりクーポンの特典と有効期限が表示されました。

次はwebAPI側を改造してDBで管理しているデータをレスポンスするようにします