SwiftUIでネットのavator画像を表示
Overview
海外のサイトで、REQRESというサイトから、人のアバター画像と、名前を取得するデモアプリを作成して、REST APIから取得したデータをViewに表示するDEMOアプリを作りました。
今回は、SwiftUIでAPIと通信するのにCombine
という標準で使える機能を使用して、APIからデータを取得するのをやってみようと思います。
Combine フレームワークは、時間の経過とともに値を処理するための宣言型 Swift API を提供します。これらの値は、さまざまな種類の非同期イベントを表すことができます。Combine は、パブリッシャーが時間の経過とともに変化する可能性のある値を公開し、サブスクライバーがパブリッシャーからそれらの値を受け取ることを宣言します。
このPublisherプロトコルは、時間の経過とともに一連の値を配信できる型を宣言します。パブリッシャーには、上流のパブリッシャーから受け取った値に基づいて動作し、それらを再パブリッシュするオペレーターがいます。
📡今回使用したCombineの機能:
AnyCancellable
キャンセルされたときに提供されたクロージャを実行する型消去キャンセル可能オブジェクト。
AnyPublisher
別のパブリッシャーをラップすることによって型の消去を実行するパブリッシャー。
SwiftUIでインターネットから、画像を表示するには、url-image
というライブラリーが必要なので追加する。
summary
新規プロジェクトを作成して、このようにでデモアプリを作成してください。
SwiftUIでインターネットから、画像を表示するには、url-image
というライブラリーが必要なので追加する。
GUIの検索画面でコピー&ペーストするURLはこちら💁
https://github.com/dmytro-anokhin/url-image
追加手順:
📁ディレクトリ構成:
SwiftUIは、ViewModelで開発するそうなので、モデルは、Modelディレクトリに、APIとやりとりするロジックは、Repositoryディレクトリに、APIのデータを表示するのはViewディレクトリに、ModelとView側の状態の保持とRepositoryのロジックを呼び出して、やりとりするのは、ViewModelディレクトリにファイルを配置しております。
💽モデル:
構造体を使ってAPIから取得したデータを保持するモデルを作成します。id, 名字, 名前, メールアドレス,画像のURLを定義しています。
Identifiableとは:
安定したアイデンティティを持つエンティティの値をインスタンスが保持する型のクラス。
idを構造体で扱うときに、必要でつけないとエラーが発生します!
Codableとは:
自分自身を外部表現に変換したり、外部表現から外部表現に変換したりできる型。
データを保持するモデル
import Foundation
struct User: Codable, Identifiable {
let id: Int
let email: String
let first_name: String
let last_name: String
let avatar: String
}
struct UserList: Codable {
let data: [User]
}
APIのデータを取得するRepository
APIとやりとりするロジックは、Repositoryというクラスに書くのが一般的だそうです。設計を考えるとファイルって分けますよね。インターフェースまで定義すると疎結合とかするので、難しいお話になってしまいます😅
APIとやりとりするロジック
import Foundation
import Combine
class Api: ObservableObject {
var cancellables = Set<AnyCancellable>()
func getUsers() -> AnyPublisher<UserList, Error> {
guard let url = URL(string: "https://reqres.in/api/users") else {
fatalError("Invalid URL")
}
return URLSession.shared.dataTaskPublisher(for: url)
.map { $0.data }
.decode(type: UserList.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}
📶ViewとやりとりするViewModel
今回は、必要なのかなと思いつつロジックを直接呼び出さずに、画面を状態を持たせるViewModelを定義しました。普段は、Flutterで仕事してる人なので、Swift, KotlinのviewModelってどんなのだろうと毎回これで良いのかなと疑問に思いつつコード書いてます。
ViewModel
import Foundation
import Combine
class ViewModel: ObservableObject {
@Published var users = [User]()
private var api = Api()
// Combineをimportしないと、Cannot find type 'AnyCancellable' in scopeになる
private var cancellables = Set<AnyCancellable>()
func fetchUsers() {
api.getUsers()
.sink { completion in
switch completion {
case .failure(let error):
print(error)
case .finished:
break
}
} receiveValue: { userList in
self.users = userList.data
}
.store(in: &cancellables)
}
}
データを表示するView
Viewディレクトリに、移動させたプロジェクト作成時からあるContentView.swift
でAPIから取得した人間のアバター画像とテキストを表示します。ViewModelのクラスをインスタンス化して、ListViewを使いユーザーの一覧画面のようなものを作成しました。
View
import SwiftUI
import Foundation
import URLImage
struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
var body: some View {
List(viewModel.users, id: \.id) { user in
HStack {
URLImage(URL(string: user.avatar)!) { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 50, height: 50)
.clipShape(Circle())
}
Text(user.first_name + " " + user.last_name)
}
}
.onAppear {
viewModel.fetchUsers()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
ビルドして、Viewにこのような画面が表示されれば成功です!
thoughts
今回は、以前Qiitaの記事で解説したFlutterで作ってみた機能をSwiftUIで再現してみました。API通信は世の中のアプリで普通に使われているので必要な知識です。初学者向けですが誰かの参考になると嬉しいです。
こちらが今回作成した完成品になります
Discussion