🐹

SwiftUIでカテゴリごとに絞り込みできる画像一覧画面を作ってみた

に公開

SwiftUIとSwiftDataを使って、画像をグリッド表示し、お気に入り登録もできるシンプルな画面を作ってみました。右下には「カテゴリで絞り込み」できるボタンがあり、画像をタップすると内部DBにお気に入り情報が保存されるようになっています。

メインのViewはたったの約120行。細かいUIパーツやデータ保持用のモデルは、分離してスッキリ書いています。

出来上がった画面はこちらです。

💡 技術構成

アーキテクチャ:小さなMVVM構成

データ保存:SwiftData

UIライブラリ:SwiftUI(標準コンポーネントのみ)

🧱 各パーツの責務

Model: ImageData

画像名・お気に入り情報(ピン留め)などを保持する、画像1件ごとのモデルです。

ViewModel: ImageDataManager

画像を読み込み、ImageDataに初期化

SwiftDataに保存&取得

カテゴリでの絞り込み処理を担当

表示用のImageData配列をビューに渡す

View: GridViewWithPinned

画像一覧をグリッドで表示する画面です。渡されたImageDataの配列を3列で表示します。

struct GridViewWithPinned: View {
    let columns = [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]
    let imageData: [ImageData]
    let onTap: (ImageData) -> Void

    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns) {
                ForEach(imageData) { image in
                    IconViewWithPinned(imageData: image, size: 80, fontsize: 10, onTap: {
                        print(image.fileName + " tap")
                        onTap(image)
                    })
                }
            }
        }
    }
}

View: IconViewWithPinned

画像を表示(またはデフォルトアイコン)

お気に入り状態をアイコンで表示

タップで親Viewにイベント通知

struct IconViewWithPinned: View {
    @State var imageData: ImageData
    var size: CGFloat = 150
    var fontsize: CGFloat = 20
    let onTap: () -> Void
    var body: some View {
        ZStack {
            VStack {
                Button(action:{
                    onTap()
                }) {
                    VStack(spacing: 0) {
                        if !imageData.fileName.isEmpty {
                            Image(imageData.fileName)
                                .resizable()
                                .scaledToFit()
                                .frame(width: size * 0.95, height: size * 0.95)
                        } else {
                            Image(systemName: "questionmark.square") // デフォルト画像
                                .resizable()
                                .scaledToFit()
                                .frame(width: size * 0.95, height: size * 0.95)
                        }
                    }
                }
            }
            Image(systemName: imageData.isPinned ? "checkmark.circle.fill": "checkmark.circle")
                .offset(x: size * 0.4, y: -size * 0.4)
                .foregroundStyle(imageData.isPinned ? Color.blue : Color.gray)
        }
        .frame(width: size, height: size)
        .padding()
        .background(Color.white) // 背景白にする場合
        .overlay(
            RoundedRectangle(cornerRadius: 12)
                .stroke(Color.gray, lineWidth: 3)
        )
        .cornerRadius(12)
    }
}

📝 まとめ

SwiftUI + SwiftData の組み合わせは、軽量プロトタイプにかなり便利。

グリッド表示やMVVMの構成も簡潔に書ける。

絞り込みボタンやチェック表示のインタラクションで、体験がぐっと楽しくなる。

今後は、カテゴリごとの絞り込み処理の詳細や、SwiftDataの構成の話も深掘りしたいなと思っています!

実際のソースは以下に掲載しています。
https://github.com/nainai0722/YourRoutineApp/tree/main/YourRoutineApp/YourRoutineApp/Views/Setting/PinnedImages

Discussion