🌁

SwiftUIでネットのavator画像を表示

2023/12/17に公開

Overview

海外のサイトで、REQRESというサイトから、人のアバター画像と、名前を取得するデモアプリを作成して、REST APIから取得したデータをViewに表示するDEMOアプリを作りました。

https://reqres.in/

今回は、SwiftUIでAPIと通信するのにCombineという標準で使える機能を使用して、APIからデータを取得するのをやってみようと思います。

ZennでCombineについて書いた記事

Combine フレームワークは、時間の経過とともに値を処理するための宣言型 Swift API を提供します。これらの値は、さまざまな種類の非同期イベントを表すことができます。Combine は、パブリッシャーが時間の経過とともに変化する可能性のある値を公開し、サブスクライバーがパブリッシャーからそれらの値を受け取ることを宣言します。

このPublisherプロトコルは、時間の経過とともに一連の値を配信できる型を宣言します。パブリッシャーには、上流のパブリッシャーから受け取った値に基づいて動作し、それらを再パブリッシュするオペレーターがいます。

📡今回使用したCombineの機能:

https://developer.apple.com/documentation/combine/anycancellable

AnyCancellable
キャンセルされたときに提供されたクロージャを実行する型消去キャンセル可能オブジェクト。

https://developer.apple.com/documentation/combine/anypublisher

AnyPublisher
別のパブリッシャーをラップすることによって型の消去を実行するパブリッシャー。


SwiftUIでインターネットから、画像を表示するには、url-imageというライブラリーが必要なので追加する。

https://github.com/dmytro-anokhin/url-image

summary

新規プロジェクトを作成して、このようにでデモアプリを作成してください。

SwiftUIでインターネットから、画像を表示するには、url-imageというライブラリーが必要なので追加する。
GUIの検索画面でコピー&ペーストするURLはこちら💁

https://github.com/dmytro-anokhin/url-image

追加手順:


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通信は世の中のアプリで普通に使われているので必要な知識です。初学者向けですが誰かの参考になると嬉しいです。

https://qiita.com/JunichiHashimoto/items/11b6b3df4925a808bcd3

こちらが今回作成した完成品になります
https://github.com/sakurakotubaki/AbatorApp

Jboy王国メディア

Discussion