Help us understand the problem. What is going on with this article?

iOS開発: 再入門 apiを叩いてtableViewに表示する (Qiita編)

More than 1 year has passed since last update.

昨日@koogawaさんがコメントをくださったので
AlamofireとSwiftyJSONでAPIを叩くチュートリアル
せっかくなので1年くらい前にお世話になったこちらの記事をほぼそのまま自分なりに書いて見ようと思います。ただAlamofireやSwiftyJSONは使わずにやってみようと思います。

ライブラリーを使わないでどうやるのか少しでも参考になるかも・・・


参考記事に書いてありますがこちらを使います。
https://qiita.com/api/v2/docs#get-apiv2items
このJSONから記事のタイトルと著者を取得し表示していきます。

完成図

image_1.png

Article構造体を作成する。

struct Article {
    var title: String = ""
    var userId: String = ""
}

レスポンスのJSONからArticleのインスタンスを生成したいので下記のようなイニシャライザを用意しました。swiftではjsonを[String: Any]の辞書型として扱うので下記のように適宜キャストしていけば値を取得できます。

[
  {
    ・・・
    "title": "Example title",
    "user": {
      "id": "yaotti",
      ・・・
    },
    ...
  }
]
extension Article {

    init(_ json: [String: Any]) {

        if let title = json["title"] as? String {
            self.title = title
        }

        if let user = json["user"] as? [String: Any] {
            if let userId = user["id"] as? String {
                self.userId = userId
            }
        }
    }
}

APIを叩いてみる

URLSessionクラスが通信タスクを管理してくれるクラスで
用途ごとにURLSessionTaskを管理する。

  • URLSessionDataTask: とりあえずAPI叩いてJSONデータ取得する場合はこれを使う。
  • URLSessionUploadTask: サーバーにデータをアプロードするために使う。
  • URLSessionDownloadTask:  サーバーからデータを取得し、ファイルに保存するために使う
  open func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Swift.Void) -> URLSessionDataTask

第一引数のDataが取得したバイナリデータで
HTTP通信の場合、第二引数のURLResponseを、HTTPURLResponse型にキャストすることでステータスコードとか取得できる。HTTPURLResponse型はHTTPレスポンスのメタデータをカプレス化したもの。

let url = "https://qiita.com/api/v2/items"

guard var urlComponents = URLComponents(string: url) else {
    return
}
//50件取得する
urlComponents.queryItems = [
    URLQueryItem(name: "per_page", value: "50"),
]
//urlComponents.string: "https://qiita.com/api/v2/items?per_page=50"

let task = URLSession.shared.dataTask(with: urlComponents.url!) { data, response, error in

    guard let jsonData = data else {
        return
    }

    //今回の処理には関係ないが一応。
    if let httResponse = response as? HTTPURLResponse {
            print(httResponse.statusCode)
    }
    //レスポンスのバイナリデータがJSONとして解釈できる場合、JSONSerializationクラスのメソッドを使って Data型 -> Any型 に変換できる。
    do {
        let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: [])

        //今回のレスポンスはJSONの配列なので Any -> [Any] -> [String: Any] とキャストし、Articleのインスタンスを生成した。
        guard let jsonArray = jsonObject as? [Any] else {
            return
        }

       let articles = jsonArray.flatMap { $0 as? [String: Any] }.map { Article($0) }
       debugPrint(articles)
    } catch {
        print(error.localizedDescription)
    }
}
//戻り値のURLSessionDataTaskクラスのresume()メソッドを実行すると通信が開始される。
task.resume()

[Article(title: "google url-shortener", userId: "HadaGunjyo"), 
Article(title: "templateタグをjQueryで操作しようとしたらハマった話", userId: "naoqoo2"),
Article(title: "レザー製ペアブレスレット", userId: "bouwoo"), 
Article(title: "UIStackViewのサイズをmultiplierで設定すると中のViewが微妙にズレるときがある", userId: "alt_yamamoto"), 
・・・
]

上記のように値を取得できました。
ただswift4から登場したcodableを使うことでもっと楽ができます。

Codableを使ってマッピング

codableについては色々と奥が深いようですが
Codableについて色々まとめた[Swift4]

struct Article: Codable {
    var title: String
    var user: User
    struct User: Codable {
        var id: String
    }
}

*中略

let task = URLSession.shared.dataTask(with: urlComponents.url!) { data, response, error in

    guard let jsonData = data else {
        return
    }

    do {
        //swift4からはたったこれだけでjsonのマッピングができるようになった。
        let articles = try JSONDecoder().decode([Article].self, from: jsonData)
    } catch {
        print(error.localizedDescription)
    }
}

あとは表示するだけです

最終的なコード

*ライブラリー使ってないのでコピペで動きます

ViewController.swift
import UIKit

struct Article: Codable {
    var title: String
    var user: User
    struct User: Codable {
        var id: String
    }
}

struct Qiita {

    static func fetchArticle(completion: @escaping ([Article]) -> Swift.Void) {

        let url = "https://qiita.com/api/v2/items"

        guard var urlComponents = URLComponents(string: url) else {
            return
        }

        urlComponents.queryItems = [
            URLQueryItem(name: "per_page", value: "50"),
        ]

        let task = URLSession.shared.dataTask(with: urlComponents.url!) { data, response, error in

            guard let jsonData = data else {
                return
            }

            do {
                let articles = try JSONDecoder().decode([Article].self, from: jsonData)
                completion(articles)
            } catch {
                print(error.localizedDescription)
            }
        }
        task.resume()
    }
}


class ViewController: UIViewController {

    private var tableView = UITableView()
    fileprivate var articles: [Article] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        title = "最新記事"

        setUpTableView: do {
            tableView.frame = view.frame
            tableView.dataSource = self
            view.addSubview(tableView)
        }

        Qiita.fetchArticle(completion: { (articles) in
            self.articles = articles
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        })
    }
}

extension ViewController: UITableViewDataSource {

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "cell")
        let article = articles[indexPath.row]
        cell.textLabel?.text = article.title
        cell.detailTextLabel?.text = article.user.id
        return cell
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return articles.count
    }
}

その後...

タグとかアイコン画像とかもうちょっと情報量増やすと一気にアプリっぽくなる??

struct Article: Codable {
    var title: String
    var user: User
    struct User: Codable {
        var id: String
        var iconUrl: String
        enum CodingKeys: String, CodingKey {
            case id = "id"
            case iconUrl = "profile_image_url"
        }
    }
    var url: String
    var tags: [Tag]
    struct Tag: Codable {
        var name: String
    }
}

image_2.png

abouch
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away