Одновременный matchedGeometryEffect и rotate3DEffect

Я хочу оживить карту, которая летит из верхней половины экрана в нижнюю половину и переворачивается во время полета.

Я управляю логикой перелистывания с помощью пользовательского модификатора .cardify. Кажется, работает один, например. когда я переворачиваю карту на onTapGesture { withAnimation { ... } }.

Я также заставил карту летать сверху вниз и наоборот с помощью matchedGeometryEffect.

Но при тапе карта вылетает без вращения.

Я попытался применить .transition(.assymetric(...)) для обеих ветвей if (см. код ниже), но это не помогло.

Итак, код

import SwiftUI

struct ContentView: View {
    @State var cardOnTop = true
    @State var theCard = Card(isFaceUp: true)

    @Namespace private var animationNameSpace
    
    var body: some View {
        VStack {
            if cardOnTop {
                CardView(card: theCard)
                    .matchedGeometryEffect(id: 1, in: animationNameSpace)
                    .onTapGesture {
                        withAnimation {
                            theCard.toggle()
                            cardOnTop.toggle()  // comment me to test flipping alone
                        }
                    }
                Color.white
            } else {
                Color.white
                CardView(card: theCard)
                    .matchedGeometryEffect(id: 1, in: animationNameSpace)
                    .onTapGesture {
                        withAnimation {
                            theCard.toggle()
                            cardOnTop.toggle()
                        }
                    }
            }
        }
        .padding()
    }

    struct Card {
        var isFaceUp: Bool
        
        mutating func toggle() {
            isFaceUp.toggle()
        }
    }
    
    struct CardView: View {
        var card: Card

        var body: some View {
            Rectangle()
                .frame(width: 100, height: 50)
                .foregroundColor(.red)
                .cardify(isFaceUp: card.isFaceUp)
        }
    }
}

/* Cardify ViewModifier */

struct Cardify: ViewModifier, Animatable {
    init(isFaceUp: Bool){
        rotation = isFaceUp ? 0 : 180
    }
    
    var rotation: Double  // in degrees

    var animatableData: Double {
        get { return rotation }
        set { rotation = newValue }
    }
    
    func body(content: Content) -> some View {
        ZStack {
            let shape = RoundedRectangle(cornerRadius: DrawingConstants.cornerRadius)
            if rotation < 90 {
                shape.fill().foregroundColor(.white)
                shape.strokeBorder(lineWidth: DrawingConstants.lineWidth).foregroundColor(.gray)
            } else {
                shape.fill().foregroundColor(.gray)
            }
            
            content
                .opacity(rotation < 90 ? 1 : 0)
        }
        .rotation3DEffect(Angle.degrees(rotation), axis: (0, 1, 0))
    }
    
    private struct DrawingConstants {
        static let cornerRadius: CGFloat = 15
        static let lineWidth: CGFloat = 2
    }
}

extension View {
    func cardify(isFaceUp: Bool) -> some View {
        return self.modifier(Cardify(isFaceUp: isFaceUp))
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Как я могу заставить карту летать с вращением?

Кроме того, я обнаружил, что это работает (если поместить только в тело VStack, без ветвей if)

CardView(card: theCard)
    .offset(x: 0, y: cardOnTop ? 0 : 100)
    .onTapGesture {
        withAnimation {
            theCard.toggle()
            cardOnTop.toggle()  // comment me to test flipping alone
        }
    }

но в моем приложении у меня есть колода перевернутых карт, которые раздаются на доске, перевернутой вверх. Я попытался смоделировать поведение с помощью ветки if в коде ContentView выше (появляются и исчезают CardView).

Animista - анимация на ходу!
Animista - анимация на ходу!
Если вы веб-дизайнер или разработчик, вы знаете, что добавление анимации на ваш сайт может помочь сделать его более привлекательным и динамичным....
Повысьте уровень своего сайта с помощью анимации CSS и JavaScript: Пошаговое руководство
Повысьте уровень своего сайта с помощью анимации CSS и JavaScript: Пошаговое руководство
Если вы хотите добавить визуальный интерес к своему сайту, то внедрение анимации с помощью CSS и JavaScript может стать отличным способом сделать это....
0
0
28
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вращение не запускается, потому что вы удаляете карту и вставляете другую в представление, поэтому остается только .matchedGeometryEffect.

Что вам нужно, так это одна карта, которая остается в поле зрения, чтобы она могла вращаться, но также и менять свое положение.

Вы можете добиться этого, например. с .offset. GeometryReader просто чтобы найти правильное значение смещения.

var body: some View {
    GeometryReader { geo in
        VStack {
            CardView(card: theCard)
                .offset(x: 0, y: cardOnTop ? 0 : geo.size.height / 2)
                .onTapGesture {
                    withAnimation {
                        cardOnTop.toggle() 
                        theCard.toggle()
                    }
                }
            Color.clear
        }
    }
    .padding()
}

Спасибо за ответ. Если я хочу сохранить удаление и вставку CardViews, можно ли создать для этого собственный переход?

Stepan Zakharov 23.03.2022 08:28

да, смотрите ниже :) тогда вам нужно вытащить анимационную часть из вашего .cardify.

ChrisR 24.03.2022 10:04
Ответ принят как подходящий

Вот решение исключительно с .transition с использованием пользовательского перехода:

struct ContentView: View {
    @State var cardOnTop = true
    
    var body: some View {
        VStack(spacing:0) {
            if cardOnTop {
                RoundedRectangle(cornerRadius: 15)
                    .fill(.blue)
                    .transition(.rotate3D(direction: 1).combined(with: .move(edge: .bottom)))
                Color.clear
            } else {
                Color.clear
                RoundedRectangle(cornerRadius: 15)
                    .fill(.green)
                    .transition(.rotate3D(direction: -1).combined(with: .move(edge: .top)))
            }
        }
        .onTapGesture {
            withAnimation(.easeInOut(duration: 2)) {
                cardOnTop.toggle()
            }
        }
        .padding()
    }
}

/* Transition Modifier */

extension AnyTransition {
    static func rotate3D(direction: Double) -> AnyTransition {
        AnyTransition.modifier(
            active: Rotate3DModifier(value: 1, direction: direction),
            identity: Rotate3DModifier(value: 0, direction: direction))
    }
}

struct Rotate3DModifier: ViewModifier {
    let value: Double
    let direction: Double
    
    func body(content: Content) -> some View {
        content
            .rotation3DEffect(Angle(degrees: 180 * value * direction), axis: (x: 0, y: 1, z: 0))
            .opacity(1 - value)
    }
}

Другие вопросы по теме