I'm trying to create a 'card flip' animation between two Views:
CardView within a LazyVGridThe LazyVGrid and View 'B' are together in a ZStack
Specifically, the ContentView is organized like so:
var body: some View {
ZStack {
NavigationView {
ScrollView {
LazyVGrid(columns: columns, spacing: 10) {
ForEach(model.events, id: \.self) { event in
SmallCardView(event: event)
.opacity(!showModal || event != modifiableEvent ? 1.0 : 0.0)
}
}
}
}
.brightness(self.showModal ? -0.1 : 0)
.blur(radius: self.showModal ? 16 : 0)
if self.showModal {
AddEventView(
showModal: $showModal,
existingEvent: modifiableEvent,
)
.opacity(showModal ? 1.0 : 0.0)
.padding(.horizontal, 16)
}
}
}
I came across this SO post, and the answer seems super promising, however the answer doesn't take into account if one of the views is within a Stack / Grid, which is the case for me. So, my question is, how can I adapt the linked solution so that it works as expected if one of the views is indeed embedded within a Stack or a Grid.
Edit: Another thing to note is that the size and position of the Views are different
I tried adding .modifier(FlipEffect(flipped: $showModal, angle: animate3d ? 180 : 0, axis: (x: 0, y: 1))) to both the ZStack and SmallCardView, however neither yielded the expected results.
Thanks!
Edit: For clarity, I want to animate in a card flip style between these two views:


I never managed to get it working without glitches when the cards are in a LazyVGrid using .matchedGeometryEffect(). So this is the rather messy solution abusing offsets and scaling I am using in my project:
import SwiftUI
import PlaygroundSupport
struct GridTestView: View {
@State var flippedCard: Int?
@State var frontCard: Int?
let cards = [1,2,3,4,5,6,7,8,9,10]
var body: some View {
let columns = [
GridItem(.flexible(), spacing: 0),
GridItem(.flexible(), spacing: 0),
GridItem(.flexible(), spacing: 0)
]
GeometryReader { screenGeometry in
ZStack {
ScrollView {
LazyVGrid(columns: columns, alignment: .center, spacing: 0) {
ForEach(cards, id: \.self) { card in
let isFaceUp = flippedCard == card
GeometryReader { cardGeometry in
ZStack {
CardBackView(card: card)
.modifier(FlipOpacity(pct: isFaceUp ? 0 : 1))
.rotation3DEffect(Angle.degrees(isFaceUp ? 180 : 360), axis: (0,1,0))
.frame(width: cardGeometry.size.width, height: cardGeometry.size.height)
.scaleEffect(isFaceUp ? screenGeometry.size.width / cardGeometry.size.width: 1)
CardFrontView(card: card)
.modifier(FlipOpacity(pct: isFaceUp ? 1 : 0))
.rotation3DEffect(Angle.degrees(isFaceUp ? 0 : 180), axis: (0,1,0))
.frame(width: screenGeometry.size.width, height: screenGeometry.size.height)
.scaleEffect(isFaceUp ? 1 : cardGeometry.size.width / screenGeometry.size.width)
}
.offset(x: isFaceUp ? -cardGeometry.frame(in: .named("mainFrame")).origin.x: -screenGeometry.size.width/2 + cardGeometry.size.width/2,
y: isFaceUp ? -cardGeometry.frame(in: .named("mainFrame")).origin.y: -screenGeometry.size.height/2 + cardGeometry.size.height/2)
.onTapGesture {
withAnimation(.linear(duration: 1.0)) {
if flippedCard == nil {
flippedCard = card
frontCard = card
} else if flippedCard == card {
flippedCard = nil
}
}
}
}
.aspectRatio(1, contentMode: .fit)
.zIndex(frontCard == card ? 1 : 0)
}
}
}
}
.background(Color.black)
}
.coordinateSpace(name: "mainFrame")
}
}
struct FlipOpacity: AnimatableModifier {
var pct: CGFloat = 0
var animatableData: CGFloat {
get { pct }
set { pct = newValue }
}
func body(content: Content) -> some View {
return content.opacity(Double(pct.rounded()))
}
}
struct CardBackView: View {
var card: Int
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 10)
.fill(Color.red)
.padding(5)
Text("Back \(card)")
}
}
}
struct CardFrontView: View {
var card: Int
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 10)
.fill(Color.blue)
.padding(10)
.aspectRatio(1.0, contentMode: .fit)
Text("Front \(card)")
}
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.setLiveView(GridTestView().frame(width: 400, height: 600))

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With