Я борюсь с анимацией представлений при вставке их в стек. В общем, я пытаюсь создать эффект, когда карты вылетают из стопки и перемещаются в конечный пункт назначения. Я перепробовал множество вариантов со встроенными переходами, но так и не смог добиться желаемого результата.
Может быть, есть другой способ решить эту проблему, чтобы анимация выглядела более естественно? Все, что пользователю нужно сделать, это нажать на стопку, и карта вылетит ровно из стопки (центр стопки должен быть отправной точкой анимации). Лучше было бы доставить его прямо к конечному пункту назначения (я знаю итоговое количество карт в самом начале, поэтому могу четко видеть, когда все карты будут размещены, когда они все появятся на экране).
Теперь это выглядит странно и неестественно.
Вот фрагмент кода, но я думаю, он не очень полезен.
VStack {
HStack {
if viewModel.firstCardShown {
RotatableCardView(cardLayout: viewModel.firstCardLayout, width: cardWidth - 20, isFlipped: $isFirstCardFlipped)
.transition(.move(edge: .bottom))
}
if viewModel.secondCardShown {
RotatableCardView(cardLayout: viewModel.secondCardLayout, width: cardWidth - 20, isFlipped: $isSecondCardFlipped)
.transition(.move(edge: .bottom))
}
if viewModel.thirdCardShown {
RotatableCardView(cardLayout: viewModel.thirdCardLayout, width: cardWidth - 20, isFlipped: $isThirdCardFlipped)
.transition(.move(edge: .bottom))
}
}
.padding(.top, 40)
.containerRelativeFrame(.vertical) { height, _ in
height / 2
}
CardsDeckView()
.onTapGesture(perform: {
withAnimation(.easeIn(duration: 0.6)) {
if !viewModel.firstCardShown {
viewModel.firstCardShown.toggle()
} else if !viewModel.secondCardShown {
viewModel.secondCardShown.toggle()
} else if !viewModel.thirdCardShown {
viewModel.thirdCardShown.toggle()
}
}
})
.containerRelativeFrame(.vertical) { height, _ in
height / 2
}
@loremipsum, было бы очень любезно с вашей стороны предложить название сеанса, чтобы я мог его найти





Я предполагаю, что ваши скриншоты описывают движение, которое вы сейчас видите, а не то движение, которое вам действительно нужно.
Если я правильно понимаю, вы хотите, чтобы карты летели прямо на свои конечные позиции. Поэтому, когда раздается вторая карта, первая карта больше не должна двигаться. То же самое касается и дополнительных карт.
Один из способов добиться этого — использовать .matchedGeometryEffect:
Скрытие заполнителей с помощью .hidden() не работает, а .opacity(0) работает.
struct ContentView: View {
private let nCards = 3
@State private var nDealtCards = 0
@Namespace private var nsCards
private var aCard: some View {
ZStack {
RoundedRectangle(cornerRadius: 10)
.fill(.red)
RoundedRectangle(cornerRadius: 8)
.stroke(.yellow)
.padding(4)
Rectangle()
.fill(.image(Image(systemName: "xmark")))
.foregroundStyle(.yellow)
.padding(8)
}
.frame(width: 110, height: 150)
}
var body: some View {
VStack(spacing: 30) {
HStack {
// The target locations for the cards
ForEach(1...nCards, id: \.self) { n in
aCard
.opacity(0)
.matchedGeometryEffect(id: n, in: nsCards, isSource: true)
}
}
ZStack {
// The floating cards
ForEach(1...nCards, id: \.self) { n in
aCard
.matchedGeometryEffect(
id: n > nDealtCards ? 0 : n,
in: nsCards,
properties: .position,
isSource: false
)
}
// A few cards to form the top of the stack
aCard.rotationEffect(.degrees(3)).offset(x: -2, y: 4)
aCard.rotationEffect(.degrees(-2)).offset(x: -4, y: 1)
aCard.rotationEffect(.degrees(4)).offset(x: -1, y: 3)
}
.matchedGeometryEffect(id: 0, in: nsCards, isSource: true)
.onTapGesture { nDealtCards += 1 }
Button("Reset") { nDealtCards = 0 }
.buttonStyle(.borderedProminent)
.opacity(nDealtCards > 0 ? 1 : 0)
}
.animation(.easeInOut, value: nDealtCards)
}
}

Вау, это именно то, что я ищу! Большое спасибо! Я почитаю немного больше о matchedGeometryEffect, но ваш ответ прекрасно решает проблему.
Посмотрите SwiftUI.Layout, в одном из видеороликов WWDC, когда он был представлен, есть анимация/настройка таблицы лидеров, которую вы могли бы использовать.