【API連携編】Qiitaクライアントアプリを作ろう

iOSアプリチュートリアルとして、Qiitaのクライアントアプリを一緒に作っていきましょう。

今回は最終章です!

STEP1
UI実装

Qiitaの記事一覧を表示するためのUIを実装します。

【UI編】Qiitaクライアントアプリを作ろう
STEP2
WebView実装

記事一覧を選択した時にQiitaの記事を表示するためにWebViewをおきます。

【WebView編】Qiitaクライアントアプリを作ろう
STEP3
API連携
【本記事】QiitaのAPIと連携し実際にQiitaの記事を取得して表示します。

完成形はこちら

goal_image

参考記事

チュートリアル上で出てくるいくつかのスキルは下記記事で詳細に解説してますので必要であれば参考にしてください。

【入門】WEBページをアプリで表示 WebView(WKWebView)の使い方UITableViewの基本的な使い方を解説【入門】Alamofireチュートリアル【入門】Carthageとは?使い方解説

ライブラリ導入

今回のアプリではAPI通信をするためのAlamofireURLから画像を取得するNukeというライブラリを使用します。

Carthageを使用してこの2つのライブラリを導入していきましょう。

Carthage導入

まずは Cartfile を作成します。

ターミナルで作成したプロジェクトのrootに移動して以下のコマンドを実行してください。

$ vim Cartfile

内容は以下のようにしましょう。

github "Alamofire/Alamofire" "4.8.2"
github "kean/Nuke" "7.6.3"

作成できたら以下のコマンドを実行しましょう。少し時間がかかります。

$ carthage update --platform iOS

AlamofireとNuke導入

Xcodeで設定していきます。

左メニューの一番上を選択して QiitaClient -> General を開いてください。

下の方に Linked Frameworks and Libraries があるのでそこのプラスボタンを押しましょう。

open_general

Add Other... から Carthage -> Build -> iOS -> Alamofire.framework を選択してください。
同様にして Nuke.framework も選択してください。

select_alamofire

すると Linked Frameworks and LibrariesAlamofire.frameworkNuke.framework が追加されます。

review_framework

次に General タブから Build Phases タブに移動してください。

move_to_build_phases

赤枠で囲っているプラスボタンから New Run Script Phase を選択しましょう。

create_run_script

次に作成された Run Script を開き、一番上の黒枠には以下を入力して、

/usr/local/bin/carthage copy-frameworks

Input files には以下を入力してください。

$(SRCROOT)/Carthage/Build/iOS/Alamofire.framework
$(SRCROOT)/Carthage/Build/iOS/Nuke.framework

以下のようになればOKです

input_script

これでCarthageの設定は完了しました!

Qiita APIとの接続・連携

次はQiita APIとの接続と連携をしていきましょう!

まずはこのURLをブラウザなどで開いてみてください。

https://qiita.com/api/v2/tags/iOS/items

するとiOSタグのついた記事一覧のjsonデータが返ってきているかと思います。

この中で今回のプロジェクトで使用するのは以下のみです。

これらのモデルを作成していきます。

[
    {
        title: "title",
        url: "https://xxx"
        user: {
            id: "user_id"
            profile_image_url: "https://yyy"
        }
    }
]

モデル作成

まずはQiitaユーザーのモデルから作成していきます。

QiitaUser.swift というファイルを作成して以下のようなstructを作成してください。

import Foundation

struct QiitaUser: Codable {
    let id: String
    let imageUrl: String // ①

    enum CodingKeys: String, CodingKey {
        case id
        case imageUrl = "profile_image_url" // ②
    }
}

今回は Codable を使用しています。

user の中で必要なのは idprofile_image_url のみなのでその他は定義していません。

また一般的にiOS開発ではスネークケースを使用しないので①のようにキャメルケースで定義することが多いです。

しかしレスポンスとしてはスネークケースであるため CodingKeys を使用して profile_image_url がきたら imageUrl である、というように定義しています。

次は記事のモデルを作成します。

QiitaArticle.swift というファイルを作成して以下のようなstructを入力してください。

import Foundation

struct QiitaArticle: Codable {
    let title: String
    let url: String
    let user: QiitaUser // ⓵
}

記事モデルの中で必要な情報は titleurl と先ほど定義した user のみです。

CodableでもStringやIntの他に自分で定義したモデルも⓵のようにプロパティとして持たせることは可能ですが、その型もCodableである必要があります。

これでモデルの定義は完了です!

API繋ぎこみ

次はAlamofireを使用してAPIを叩きUITableViewへの反映まで実装しましょう。

まずは ViewController クラスを以下のように変更してください。

import UIKit
import Alamofire // ➀importの追加

class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!
    private var articles: [QiitaArticle] = [] // ②取得した記事一覧を保持しておくプロパティ

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        tableView.delegate = self

        let nib = UINib(nibName: "QiitaTableViewCell", bundle: nil)
        tableView.register(nib, forCellReuseIdentifier: "QiitaTableViewCell")
        tableView.rowHeight = 80
        loadQiita() // 関数呼び出し
    }

    // loadする関数の定義
    private func loadArticles() {
        // ③Qiita APIを叩く
        Alamofire.request("https://qiita.com/api/v2/tags/iOS/items").response { response in
            guard let data = response.data else {
                return
            }
            let decoder = JSONDecoder()
            do {
                // ④レスポンスを[QiitaArticle]にデコード
                let articles: [QiitaArticle] = try decoder.decode([QiitaArticle].self, from: data)
                // ⑤取得した記事をarticlesに代入
                self.articles = articles
                // ⑥tableViewを更新
                self.tableView.reloadData()
            } catch {
                print(error)
            }
        }
    }
}

1つ1つ見ていきましょう。

①ではAlamofireをimportしています。これでAlamofireが使用できるようになります。

次に articles という配列を用意します。これは記事一覧のレスポンスを保持しておくためです。

実際にAPIを叩いているのは loadArticles() という関数です。③でAlamofireを使っています。④ではレスポンスデータを [QiitaArticle] にデコードしています。

このデコードされた articles を⑤で先ほど用意した ViewControllerarticles プロパティに代入して⑥で tableView のデータに更新をかけています。

次に UITableViewDataSource のdelegate関数も修正していきましょう。

extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // ⑦cell数をarticlesの数に設定
        return articles.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "QiitaTableViewCell", for: indexPath) as? QiitaTableViewCell else {
            return UITableViewCell()
        }
        // ⑧indexPathを用いてarticlesから該当のarticleを取得する
        let article = articles[indexPath.row]
        // ⑨cellへの反映
        cell.set(title: article.title, author: article.user.id)
        return cell
    }
}

⑦ではcellの数を返しています。cellの数は articles と同じ数であって欲しいので articles.count を返すようにします。

次に⑧では indexPath がcellを上から数えた時の番号となります。
これを使用して articles から1つの QiitaArticle を取得しています。

⑨ではその article をcellに反映させています。

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

このような動きになればOKです!

build

画像取得

Nuke

次はNukeを使ってユーザーの画像を取得していきます。

まずは QiitaTableViewCell を以下のように修正してください。

import UIKit
import Nuke // ①Nukeをimport

class QiitaTableViewCell: UITableViewCell {

    @IBOutlet weak var iconImageView: UIImageView!
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var authorLabel: UILabel!

    // ②引数にimageUrlを追加
    func set(title: String, author: String, imageUrl: String) {
        // ③Nukeを使用して画像を取得
        Nuke.loadImage(with: URL(string: imageUrl)!, into: iconImageView)
        titleLabel.text = title
        authorLabel.text = author
    }
}

まずは①でNukeをimportします。

次に set() 関数の引数に imageUrl を追加します。この imageUrl を元に③ではNukeを使って画像を取得しています。

into にImageViewを指定することで取得が完了すると画像が反映されます。

set() 関数の引数を変更したので呼び出し箇所の ViewController も修正しましょう。以下は ViewController の一部しか表示していません。

extension ViewController: UITableViewDataSource {
    ...
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "QiitaTableViewCell", for: indexPath) as? QiitaTableViewCell else {
            return UITableViewCell()
        }
        let article = articles[indexPath.row]
        // ④imageUrlの引数にarticle.user.imageUrlを追加
        cell.set(title: article.title, author: article.user.id, imageUrl: article.user.imageUrl)
        return cell
    }
}

④で追加された imageUrl の引数に article.user.imageUrl を指定しています。

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

このような動きになればOKです!

build

WebView

QiitaのWebページを表示

最後にcellを選択した時にその記事を表示するように修正しましょう。

import UIKit
import WebKit

class WebViewController: UIViewController {
    private let webView = WKWebView()

    // ①表示するURLを持っておく
    var url: String!

    override func viewDidLoad() {
        super.viewDidLoad()
        webView.frame = view.frame
        view.addSubview(webView)

        // ②googleのページからプロパティのurlに変更
        let url = URL(string: self.url)
        let request = URLRequest(url: url!)
        webView.load(request)
    }
}

①で表示させるurlをプロパティとして持っておきます。
これは外部から変更する想定なのでpublicとしておきます。

②で今までgoogleのページを表示していた箇所を先ほど定義したurlに変更しています。

次にcellを選択した時の実装も修正していきます。

extension ViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let storyboard = UIStoryboard(name: "WebViewController", bundle: nil)
        let webViewController = storyboard.instantiateInitialViewController() as! WebViewController
        // ③indexPathを使用してarticlesから選択されたarticleを取得
        let article = articles[indexPath.row]
        // ④urlとtitleを代入
        webViewController.url = article.url
        webViewController.title = article.title
        navigationController?.pushViewController(webViewController, animated: true)
    }
}

③でindexPathを使用してarticlesから選択されたarticleを取得します。

④ではそのarticleを使用して先ほど定義した url とナビゲーションの title に代入しています。

それではここでビルドしてみましょう。

このような動きになればOKです!

build

最後に

お疲れ様でした。

今回は実際にAPIと連携してデータを表示し、WebViewに遷移して表示できるようにしました。

こちらのチュートリアルのUI/API連携は、iOSアプリ開発の基本になるので、ぜひ何回か作成したり他のAPIを利用したりしてみましょう!