Анимация перехода траектории SwiftUI

Я борюсь с анимацией представлений при вставке их в стек. В общем, я пытаюсь создать эффект, когда карты вылетают из стопки и перемещаются в конечный пункт назначения. Я перепробовал множество вариантов со встроенными переходами, но так и не смог добиться желаемого результата.

Может быть, есть другой способ решить эту проблему, чтобы анимация выглядела более естественно? Все, что пользователю нужно сделать, это нажать на стопку, и карта вылетит ровно из стопки (центр стопки должен быть отправной точкой анимации). Лучше было бы доставить его прямо к конечному пункту назначения (я знаю итоговое количество карт в самом начале, поэтому могу четко видеть, когда все карты будут размещены, когда они все появятся на экране).

Теперь это выглядит странно и неестественно.

Вот фрагмент кода, но я думаю, он не очень полезен.

        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
                    }

Посмотрите SwiftUI.Layout, в одном из видеороликов WWDC, когда он был представлен, есть анимация/настройка таблицы лидеров, которую вы могли бы использовать.

lorem ipsum 22.02.2024 20:42

@loremipsum, было бы очень любезно с вашей стороны предложить название сеанса, чтобы я мог его найти

Igor 22.02.2024 22:24
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
69
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я предполагаю, что ваши скриншоты описывают движение, которое вы сейчас видите, а не то движение, которое вам действительно нужно.

Если я правильно понимаю, вы хотите, чтобы карты летели прямо на свои конечные позиции. Поэтому, когда раздается вторая карта, первая карта больше не должна двигаться. То же самое касается и дополнительных карт.

Один из способов добиться этого — использовать .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)
    }
}

Animation

Вау, это именно то, что я ищу! Большое спасибо! Я почитаю немного больше о matchedGeometryEffect, но ваш ответ прекрасно решает проблему.

Igor 22.02.2024 22:29

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