SwiftUI карта флип с двумя видами - PullRequest
2 голосов
/ 23 марта 2020

Я пытаюсь создать эффект переворачивания карты между двумя представлениями SwiftUI. При нажатии на исходный вид он поворачивается в 3D-направлении по оси Y, как при переворачивании карты, и второй вид должен начать отображаться после создания 90 градусов.

Использование .rotation3DEffect() Я могу легко повернуть вид, проблема в том, что с animation() я не знаю, как вызвать изменение вида, когда угол достигнет 90 градусов ...

@State var flipped = false

 var body: some View {

    return VStack{
        Group() {
            if !self.flipped {
                MyView(color: "Blue")
            } else {
                MyView(color: "Red")
            }
        }
        .animation(.default)
        .rotation3DEffect(self.flipped ? Angle(degrees: 90): Angle(degrees: 0), axis: (x: CGFloat(0), y: CGFloat(10), z: CGFloat(0)))
        .onTapGesture {
            self.flipped.toggle()
        }

    }

Как добиться такого поворота между двумя видами

1 Ответ

2 голосов
/ 23 марта 2020

Простое решение Подход, который вы выбираете, может быть реализован путем помещения двух представлений в ZStack, а затем показа / скрытия их при изменении состояния flipped. Вращение второго вида должно быть смещено. Но это решение основано на перекрестном переходе двух представлений. Это может быть хорошо для некоторых случаев использования. Но есть и лучшее решение - хотя оно и немного сложнее (см. Ниже).

Вот способ заставить ваш подход работать:

struct SimpleFlipper : View {
      @State var flipped = false

      var body: some View {

            let flipDegrees = flipped ? 180.0 : 0

            return VStack{
                  Spacer()

                  ZStack() {
                        Text("Front").placedOnCard(Color.yellow).flipRotate(flipDegrees).opacity(flipped ? 0.0 : 1.0)
                        Text("Back").placedOnCard(Color.blue).flipRotate(-180 + flipDegrees).opacity(flipped ? 1.0 : 0.0)
                  }
                  .animation(.easeInOut(duration: 0.8))
                  .onTapGesture { self.flipped.toggle() }
                  Spacer()
            }
      }
}

extension View {

      func flipRotate(_ degrees : Double) -> some View {
            return rotation3DEffect(Angle(degrees: degrees), axis: (x: 1.0, y: 0.0, z: 0.0))
      }

      func placedOnCard(_ color: Color) -> some View {
            return padding(5).frame(width: 250, height: 150, alignment: .center).background(color)
      }
}

Лучшее решение SwiftUI имеет несколько полезных инструментов анимации, таких как GeometryEffect, которые могут генерировать действительно плавную версию этого эффекта. Об этом topi c в SwiftUI Lab есть несколько отличных постов в блоге. В частности, см .: https://swiftui-lab.com/swiftui-animations-part2/

Я упростил и адаптировал один из примеров в этом посте для обеспечения возможности переворачивания карты.

struct FlippingView: View {

      @State private var flipped = false
      @State private var animate3d = false

      var body: some View {

            return VStack {
                  Spacer()

                  ZStack() {
                        FrontCard().opacity(flipped ? 0.0 : 1.0)
                        BackCard().opacity(flipped ? 1.0 : 0.0)
                  }
                  .modifier(FlipEffect(flipped: $flipped, angle: animate3d ? 180 : 0, axis: (x: 1, y: 0)))
                  .onTapGesture {
                        withAnimation(Animation.linear(duration: 0.8)) {
                              self.animate3d.toggle()
                        }
                  }
                  Spacer()
            }
      }
}

struct FlipEffect: GeometryEffect {

      var animatableData: Double {
            get { angle }
            set { angle = newValue }
      }

      @Binding var flipped: Bool
      var angle: Double
      let axis: (x: CGFloat, y: CGFloat)

      func effectValue(size: CGSize) -> ProjectionTransform {

            DispatchQueue.main.async {
                  self.flipped = self.angle >= 90 && self.angle < 270
            }

            let tweakedAngle = flipped ? -180 + angle : angle
            let a = CGFloat(Angle(degrees: tweakedAngle).radians)

            var transform3d = CATransform3DIdentity;
            transform3d.m34 = -1/max(size.width, size.height)

            transform3d = CATransform3DRotate(transform3d, a, axis.x, axis.y, 0)
            transform3d = CATransform3DTranslate(transform3d, -size.width/2.0, -size.height/2.0, 0)

            let affineTransform = ProjectionTransform(CGAffineTransform(translationX: size.width/2.0, y: size.height / 2.0))

            return ProjectionTransform(transform3d).concatenating(affineTransform)
      }
}

struct FrontCard : View {
      var body: some View {
            Text("One thing is for sure – a sheep is not a creature of the air.").padding(5).frame(width: 250, height: 150, alignment: .center).background(Color.yellow)
      }
}

struct BackCard : View {
      var body: some View {
            Text("If you know you have an unpleasant nature and dislike people, this is no obstacle to work.").padding(5).frame(width: 250, height: 150).background(Color.green)
      }
}

Обновление

ОП запрашивает управление состоянием переворачивания за пределами представления. Это можно сделать с помощью привязки. Ниже приведен фрагмент, который реализует и демонстрирует это. И ОП также спрашивает о переключении с анимацией и без нее. Это зависит от того, выполняется ли изменение состояния переворота (здесь с помощью showBack var) в анимационном блоке или нет. (Фрагмент не включает FlipEffect struct, которая точно такая же, как и код выше.)

struct ContentView : View {

      @State var showBack = false

      let sample1 = "If you know you have an unpleasant nature and dislike people, this is no obstacle to work."
      let sample2 = "One thing is for sure – a sheep is not a creature of the air."

      var body : some View {

            let front = CardFace(text: sample1, background: Color.yellow)
            let back = CardFace(text: sample2, background: Color.green)
            let resetBackButton = Button(action: { self.showBack = true }) { Text("Back")}.disabled(showBack == true)
            let resetFrontButton = Button(action: { self.showBack = false }) { Text("Front")}.disabled(showBack == false)
            let animatedToggle = Button(action: {
                  withAnimation(Animation.linear(duration: 0.8)) {
                        self.showBack.toggle()
                  }
            }) { Text("Toggle")}


            return
                  VStack() {
                        HStack() {
                              resetFrontButton
                              Spacer()
                              animatedToggle
                              Spacer()
                              resetBackButton
                        }.padding()
                        Spacer()
                        FlipView(front: front, back: back, showBack: $showBack)
                        Spacer()
            }
      }
}


struct FlipView<SomeTypeOfViewA : View, SomeTypeOfViewB : View> : View {

      var front : SomeTypeOfViewA
      var back : SomeTypeOfViewB

      @State private var flipped = false
      @Binding var showBack : Bool

      var body: some View {

            return VStack {
                  Spacer()

                  ZStack() {
                        front.opacity(flipped ? 0.0 : 1.0)
                        back.opacity(flipped ? 1.0 : 0.0)
                  }
                  .modifier(FlipEffect(flipped: $flipped, angle: showBack ? 180 : 0, axis: (x: 1, y: 0)))
                  .onTapGesture {
                        withAnimation(Animation.linear(duration: 0.8)) {
                              self.showBack.toggle()
                        }
                  }
                  Spacer()
            }
      }
}

struct CardFace<SomeTypeOfView : View> : View {
      var text : String
      var background: SomeTypeOfView

      var body: some View {
            Text(text)
                  .multilineTextAlignment(.center)
                  .padding(5).frame(width: 250, height: 150).background(background)
      }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...