🐹
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の構成の話も深掘りしたいなと思っています!
実際のソースは以下に掲載しています。
Discussion