23
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

SwiftUIで書くiOSアプリハンズオン(QiitaClientを作ってみよう)

Posted at

SwiftUIを使ったハンズオン用に記事を作成しました

使用するAPI

今回はQiitaAPiを題材とします。(ドキュメント)

登録不要で使えて、例えば、 https://qiita.com//api/v2/items で、投稿を取得できます。

[{
    rendered_body: "...",
    coediting: false,
    comments_count: 0,
    created_at: "2017-11-09T22:50:51+09:00",
    group: null,
    id: "fe412a67b64e793d138b",
    likes_count: 0,
    private: false,
    reactions_count: 0,
    tags: [{
            name: "processing",
            versions: []
        },
        {
            name: "openFrameworks",
            versions: []
        }
    ],
    title: "【openFrameworks 初心者冒険記5】逃げまくってた...三角関数の扉を再ノック。sin() cos() サインコサイン....。メディアアートで避けては通れない?最初の壁",
    updated_at: "2017-11-09T22:50:51+09:00",
    url: "http://qiita.com/39_isao/items/fe412a67b64e793d138b",
    user: {
        description: "鮭とメロンパンが好物な奥田民生になりたいボーイです。 最近はてなブログ始めました http://sudara-bluse.hatenablog.com/",
        facebook_id: "",
        followees_count: 52,
        followers_count: 44,
        github_login_name: null,
        id: "39_isao",
        items_count: 103,
        linkedin_id: "",
        location: "",
        name: "",
        organization: "",
        permanent_id: 76843,
        profile_image_url: "https://qiita-image-store.s3.amazonaws.com/0/76843/profile-images/1508030106",
        twitter_screen_name: "sudara_bluse",
        website_url: "http://sudara-bluse.hatenablog.com/"
    }
}, 
{,,,}
]

今回は、以下のデータを使ってiOSアプリを作ってみます。

  • 投稿タイトル
  • ユーザアイコン
  • URL

完成したアプリ

20200221170444.gif

プロジェクトを作成する

image.png
image.png
image.png

使うライブラリをインストールする

file -> swift package から使用するライブラリを追加します
https://github.com/dmytro-anokhin/url-image.git

image.png
image.png

通信周りのクラスを作成する

JSONを変換するための構造体を作成

import Foundation

struct Item: Decodable, Identifiable {
    let id: String
    let title: String
    let body: String?
    let url: String
    let user: User
}

struct User: Decodable {
    let profileImageUrl: String
}

APIからデータを取得するクラスを実装

SwiftUIの画面から参照するためObservableObject を継承し、画面に表示する値を @Published にする

import Foundation

class ApiFetcher: ObservableObject {
    @Published var items = [Item]()
    
    private let baseURL = "https://qiita.com/api/v2"
    
    func fetchItems(query: String) {
        let url = URL(string: "\(baseURL)/items?query=\(query)&page=1&per_page=50")!
    
        URLSession.shared.dataTask(with: url) {(data,response,error) in
            do {
                if let data = data {
                    let decoder = JSONDecoder()
                    decoder.keyDecodingStrategy = .convertFromSnakeCase
                    let items = try decoder.decode([Item].self, from: data)
                    DispatchQueue.main.async {
                        self.items = items
                    }
                }
            } catch {
                // ignore
            }
            
        }.resume()
    }
}

一覧画面を作る

一覧のセルを作成する

新しく ItemRow.swift というファイルを作成します

image.png

import SwiftUI
import URLImage

struct ItemRow: View {
    let item: Item
    
    var body: some View {
        HStack {
            URLImage(URL(string: item.user.profileImageUrl)!) { proxy in
                proxy.image
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .clipped()
            }.frame(width: 60.0, height: 60.0)

            Text(item.title)
        }
    }
}

struct ItemRow_Previews: PreviewProvider {
    static var previews: some View {
        ItemRow(item: Item(
            id: "d040cf8b2d15bd7e507d",
            title: "[Angular] Angular アプリの構成をみる",
            body: "Angular に関する自身の勉強の復習がてらの備忘録記事。",
            url: "https://qiita.com/ksh-fthr/items/d040cf8b2d15bd7e507d",
            user: User(
                profileImageUrl: "https://qiita-user-profile-images.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F193342%2Fprofile-images%2F1500888159?ixlib=rb-1.2.2&auto=compress%2Cformat&lossless=0&w=300&s=9a22e880d804d67db66a33ac2e7671b5"
            )
        ))
    }
}

一覧画面を作成する

新しく ItemRow.swift というファイルを作成します

import SwiftUI

struct ItemList: View {
    @ObservedObject var fetcher = ApiFetcher()
    @State var keyword: String = ""
    
    var body: some View {
        NavigationView {
            VStack {
                TextField("Search", text: $keyword) {
                    self.fetcher.fetchItems(query: self.keyword)
                }.padding(16)
                
                List(fetcher.items) { item in
                    ItemRow(item: item)
                }
            }
            .navigationBarTitle(Text("投稿一覧"))
        }
    }
}

struct ItemList_Previews: PreviewProvider {
    static var previews: some View {
        ItemList()
    }
}

ここまで作成するとプレビューが以下のように表示されます

image.png

起動画面を一覧画面にする

もともと作成されている ContentView を修正して一覧画面を表示するようにします

struct ContentView: View {
    var body: some View {
        ItemList()
    }
}

起動する

ここまで作ったら一度起動して入力した文字で検索できることを確認してみましょう

詳細画面を作る

詳細画面は一覧でタップした投稿をWebViewで表示します
新しくItemDetail.swiftを作成します

import SwiftUI
import WebKit

struct ItemDetail: View {
    let title: String
    let url: String

    var body: some View {
        WebView(url: URL(string: url)!)
        .navigationBarTitle(Text(title), displayMode: .inline)
    }
}

struct WebView : UIViewRepresentable {
    var url: URL

    func makeUIView(context: Context) -> WKWebView  {
        return WKWebView(frame: .zero)
    }
    
    func updateUIView(_ uiView: WKWebView, context: Context) {
        let req = URLRequest(url: url)
        uiView.load(req)
    }
}

struct ItemDetail_Previews: PreviewProvider {
    static var previews: some View {
        ItemDetail(title: "テスト", url: "https://www.apple.com")
    }
}

一覧から詳細画面に遷移できるようにする

ItemList.swift の List を以下のように修正して画面遷移できるようにします

List(fetcher.items) { item in
    ItemRow(item: item)
}

List(fetcher.items) { item in
    NavigationLink(
        destination: ItemDetail(
            title: item.title,
            url: item.url
        )
    ) {
        ItemRow(item: item)
    }
}

実行してみましょう

最後に

完成はこちらにあります。
https://github.com/decoch/swiftui_example_qiita_client

おつかれさまでした。

23
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?