LaTeX961590 views
高校化学2922839 views
いろは3008160 views
中学数学623537 views
高校国語788069 views
高校日本史190487 views
りんご207535 views
中学理科1630106 views
教育149462 views
数学講師2882427 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 機能を手軽に実装できる強力なモディファイアです。