Я хочу оживить карту, которая летит из верхней половины экрана в нижнюю половину и переворачивается во время полета.
Я управляю логикой перелистывания с помощью пользовательского модификатора .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
).
Вращение не запускается, потому что вы удаляете карту и вставляете другую в представление, поэтому остается только .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()
}
да, смотрите ниже :) тогда вам нужно вытащить анимационную часть из вашего .cardify.
Вот решение исключительно с .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)
}
}
Спасибо за ответ. Если я хочу сохранить удаление и вставку
CardView
s, можно ли создать для этого собственный переход?