Skip to content

Instantly share code, notes, and snippets.

@bigmountainstudio
Created January 16, 2024 04:53
Show Gist options
  • Save bigmountainstudio/780e95801d3412a34df239eb9b4ac6f4 to your computer and use it in GitHub Desktop.
Save bigmountainstudio/780e95801d3412a34df239eb9b4ac6f4 to your computer and use it in GitHub Desktop.
Cool transitions between List and Grid using SwiftData and MatchedGeometryEffect.
// Copyright © 2024 Big Mountain Studio. All rights reserved. Twitter: @BigMtnStudio
// Website for awesome SwiftUI picture books 😃: bigmountainstudio.com
import SwiftData
import SwiftUI
import UIKit
// Refer to SwiftData Mastery book for more info.
@Model
class ArtistModel {
var name = ""
var number = 0
var image: Data
init(name: String = "", number: Int = 0, image: Data) {
self.name = name
self.number = number
self.image = image
}
@MainActor
static var preview: ModelContainer {
let container = try! ModelContainer(for: ArtistModel.self,
configurations: ModelConfiguration(isStoredInMemoryOnly: true))
container.mainContext.insert(ArtistModel(name: "Adele", number: 3, image: UIImage(resource: .adele).pngData()!))
container.mainContext.insert(ArtistModel(name: "Ariana Grande", number: 3, image: UIImage(resource: .ariana).pngData()!))
container.mainContext.insert(ArtistModel(name: "Beyoncé", number: 4, image: UIImage(resource: .beyonce).pngData()!))
container.mainContext.insert(ArtistModel(name: "Billie Eilish", number: 5, image: UIImage(resource: .billie).pngData()!))
container.mainContext.insert(ArtistModel(name: "Billy Joel", number: 2, image: UIImage(resource: .billyJoel).pngData()!))
container.mainContext.insert(ArtistModel(name: "Bring Me the Horizon", number: 5, image: UIImage(resource: .bringMeTheHorizon).pngData()!))
container.mainContext.insert(ArtistModel(name: "Bruno Mars", number: 3, image: UIImage(resource: .brunoMars).pngData()!))
container.mainContext.insert(ArtistModel(name: "Green Day", number: 7, image: UIImage(resource: .greenDay).pngData()!))
container.mainContext.insert(ArtistModel(name: "Hozier", number: 5, image: UIImage(resource: .hozier).pngData()!))
return container
}
var viewImage: UIImage {
UIImage(data: image) ?? UIImage(resource: .adele)
}
}
struct ArtistsView: View {
@Namespace var namespace
@Query(sort: \ArtistModel.name) private var artists: [ArtistModel]
@State var display = "list"
var body: some View {
NavigationStack {
Group {
if display == "list" {
List(artists) { artist in
NavigationLink {
} label: {
Label(
title: { Text(artist.name) },
icon: {
Image(uiImage: artist.viewImage)
.resizable()
.scaledToFill()
.clipShape(Circle())
.matchedGeometryEffect(id: artist.name, in: namespace) // This is what makes the magic happen. Refer to SwiftUI Animations Mastery book for more options.
}
)
}
}
} else {
ScrollView {
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())], spacing: 8) {
ForEach(artists) { artist in
VStack {
Image(uiImage: artist.viewImage)
.resizable()
.scaledToFill()
.frame(width: 70, height: 70)
.clipShape(Circle())
.matchedGeometryEffect(id: artist.name, in: namespace) // This is what makes the magic happen
.overlay(alignment: .bottomTrailing) {
Text(artist.number, format: .number)
.foregroundStyle(.white)
.padding(6)
.background(.green, in: .circle)
.offset(x: 6, y: 6)
}
Text(artist.name)
.lineLimit(2)
.multilineTextAlignment(.center)
}
.padding()
.frame(maxWidth: .infinity)
.frame(height: 150, alignment: .top)
.background(.quaternary.opacity(0.3), in: .rect(cornerRadius: 16))
}
}
.padding(.horizontal)
}
.font(.footnote)
}
}
.navigationTitle("Artists")
.toolbar {
Button {
withAnimation {
display = display == "list" ? "grid" : "list"
}
} label: {
Image(systemName: display == "list" ? "list.bullet" : "square.grid.2x2")
}
}
}
}
}
#Preview("List") {
ArtistsView(display: "list")
.modelContainer(ArtistModel.preview)
}
#Preview("Grid") {
ArtistsView(display: "grid")
.modelContainer(ArtistModel.preview)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment