教育148875 views
高校化学2913383 views
LaTeX957300 views
高校物理158224 views
小学算数1194618 views
中学社会667106 views
いろは2986023 views
高校国語785655 views
Computer365120 views
小学社会308636 views
Help
Tools

English

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 機能を手軽に実装できる強力なモディファイアです。