小学算数1196702 views
英語609347 views
いろは2993462 views
教育149067 views
MathPython493120 views
高校国語786245 views
高校化学2915516 views
高校物理158628 views
数学講師2862298 views
高校生物550226 views

Pull to Refresh(refreshable)

リストを下に引っ張って更新する「Pull to Refresh」は、多くのアプリで採用されている UI パターンです。SwiftUI では refreshable モディファイアを使って簡単に実装できます。

refreshable の基本

refreshable モディファイアを List に適用するだけで、Pull to Refresh 機能が有効になります。

struct ContentView: View {
    @State private var items = ["項目1", "項目2", "項目3"]
    
    var body: some View {
        List(items, id: \.self) { item in
            Text(item)
        }
        .refreshable {
            // 更新処理
            await loadData()
        }
    }
    
    func loadData() async {
        // API からデータを取得するなど
        try? await Task.sleep(nanoseconds: 1_000_000_000)  // 1秒待機
        items.append("新しい項目")
    }
}

refreshable のクロージャは async コンテキストで実行されるため、await を使った非同期処理が可能です。更新インジケーターは処理が完了するまで自動的に表示されます。

API からデータを取得する例

実践的な例として、API からデータを取得してリストを更新するパターンを見てみましょう。

struct Article: Identifiable, Codable {
    let id: Int
    let title: String
}

struct ArticleListView: View {
    @State private var articles: [Article] = []
    @State private var isLoading = true
    
    var body: some View {
        NavigationStack {
            List(articles) { article in
                Text(article.title)
            }
            .refreshable {
                await fetchArticles()
            }
            .overlay {
                if isLoading {
                    ProgressView()
                }
            }
            .navigationTitle("記事一覧")
            .task {
                await fetchArticles()
            }
        }
    }
    
    func fetchArticles() async {
        defer { isLoading = false }
        
        guard let url = URL(string: "https://api.example.com/articles") else { return }
        
        do {
            let (data, _) = try await URLSession.shared.data(from: url)
            articles = try JSONDecoder().decode([Article].self, from: data)
        } catch {
            print("Error: \(error)")
        }
    }
}

更新処理のポイント

自動的なインジケーター管理

refreshable のクロージャが完了するまでインジケーターが表示され、完了後に自動で消える。

async/await 対応

非同期処理を自然に記述できる。Task.sleep で擬似的な遅延を入れてテストすることも可能。

エラーハンドリング

do-catch でエラーをキャッチし、適切に処理する。ユーザーへのフィードバックも忘れずに。

ScrollView での refreshable

List だけでなく、ScrollView でも refreshable を使うことができます。

struct ContentView: View {
    @State private var counter = 0
    
    var body: some View {
        ScrollView {
            VStack(spacing: 20) {
                ForEach(0..<10) { i in
                    Text("カード \(i + counter)")
                        .frame(maxWidth: .infinity)
                        .padding()
                        .background(Color.blue.opacity(0.1))
                        .cornerRadius(10)
                }
            }
            .padding()
        }
        .refreshable {
            try? await Task.sleep(nanoseconds: 500_000_000)
            counter += 10
        }
    }
}

RefreshAction 環境値

refreshable で設定した更新処理は、子ビューから RefreshAction 環境値を通じて呼び出すこともできます。

struct ParentView: View {
    @State private var data = ["A", "B", "C"]
    
    var body: some View {
        List(data, id: \.self) { item in
            ChildRow(item: item)
        }
        .refreshable {
            try? await Task.sleep(nanoseconds: 1_000_000_000)
            data.shuffle()
        }
    }
}

struct ChildRow: View {
    let item: String
    @Environment(\.refresh) private var refresh
    
    var body: some View {
        HStack {
            Text(item)
            Spacer()
            Button("更新") {
                Task {
                    await refresh?()
                }
            }
        }
    }
}

refreshable はシンプルな API ながら、モダンなアプリに欠かせない Pull to Refresh 機能を手軽に実装できる強力なモディファイアです。