1
1

More than 1 year has passed since last update.

SwiftUIでローディング中のスケルトン(プレースホルダー)を適用する方法

Last updated at Posted at 2022-02-24

はじめに

ListLazyVGridなどの画面初期化時にリスト数が決まっていないViewに対し、YoutubeやFacebookで使われているような、ロード中のスケルトンを表示する方法を書いてみました。

ちなみにスケルトンスクリーンとは、このようなやつです。

実装イメージは下記です。

dummy real

結論

当たり前っちゃ当たり前なのですが、
リスト形式の場合、画面表示時には、表示数が0件なので、スケルトンを適用しても効果がありません。

なので、適当なDummyデータを作っておき、APIなどで本来表示すべき値が取得できたら、Dummyデータと置き換えて表示してあげればうまくいきます。

実装

結論で書いたことが、全てですので、簡単な実装だけご紹介します。
実際にAPIなどへリクエストすることを想定し、Combineを利用していますが、ここではそれについては触れません。

Combineについては、以前この記事で書かせていただきましたのでもし良かったら、ご覧いただければと思います。

まず、ロード中にスケルトンを表示するためには、redacted(reason:)が標準で用意されているので、それを使います。

ContentViewModel.swift
struct UserInfo: Identifiable {
    let id: Int
    let name: String
    let age: Int
    let email: String
}

class ContentViewModel: ObservableObject {
    var cancellables = Set<AnyCancellable>()
    
    @Published var userInfoList = dummyData
    @Published var isFirstLoading = true
    
    let fetchUserSubject = PassthroughSubject<Void, Never>()
    
    static var dummyData: [UserInfo] {
        return (1...10).map {
            UserInfo(id: $0, name: "DummyName", age: 99, email: "dummy@test.com")
        }
    }
    
    init() {
        fetchUserSubject
            .flatMap({ _ -> AnyPublisher<[UserInfo], Never> in
                let userInfoList = (1...3).map {
                    UserInfo(id: $0, name: "RealName", age: 10, email: "real@real.com")
                }
                return Just(userInfoList).eraseToAnyPublisher()
            })
            .delay(for: 2, scheduler: DispatchQueue.main)
            .sink(receiveValue: { userInfoList in
                self.userInfoList = userInfoList
                self.isFirstLoading = false
            })
            .store(in: &cancellables)
    }
}

static var dummyData: [UserInfo]を定義して、@Published var userInfoList = dummyDataのようにして、初期化時はダミーデータが入るようにします。

ViewModelでは、let fetchUserSubject = PassthroughSubject<Void, Never>()を購読していますので、View側から値がsend()されたら、リアルデータをクラス変数にセットする処理が走ります。

本来は、APIによってデータを取得しますが、今回は、2秒後に固定値のリアルデータが帰ってくるように、処理を書いています。

ContentView.swift
struct ContentView: View {
    
    @ObservedObject var viewModel = ContentViewModel()
    
    var body: some View {
        VStack {
            List(viewModel.userInfoList) { user in
                HStack {
                    Text(user.name)
                    Spacer()
                    Text("\(user.age)歳")
                    Spacer()
                    Text(user.email)
                }
            }
            .redacted(reason: viewModel.isFirstLoading ? .placeholder : [])
        }
        .onAppear {
            viewModel.fetchUserSubject.send(())
        }
    }
}

.redacted(reason: viewModel.isFirstLoading ? .placeholder : [])
Listに対して適用することで、List{}で囲まれたViewがすべてスケルトン適用されます。

viewModel.isFirstLoadingは、リアルデータが取得できた時にfalseになるようにしているので、データが取れたら、自動的にスケルトンが解除されます。

以上となります。
List形式のViewで、ロード中のスケルトンを実装したい方の参考になれば幸いです。

参考にさせていただいた記事

1
1
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
1
1