【问题标题】:SwiftUI Card Flip animation with two views, one of which is embedded within a Stack具有两个视图的 SwiftUI Card Flip 动画,其中一个嵌入在 Stack 中
【发布时间】:2020-07-06 11:23:20
【问题描述】:

我正在尝试在两个视图之间创建“卡片翻转”动画:

  • 视图“A”是CardView 内的LazyVGrid
  • 视图“B”是自定义模式叠加视图

LazyVGrid 和视图“B”在一个 ZStack

具体来说,ContentView 的组织方式如下:

var body: some View {
    ZStack {
        NavigationView {
            ScrollView {
                LazyVGrid(columns: columns, spacing: 10) {
                    ForEach(model.events, id: \.self) { event in
                        SmallCardView(event: event)
                            .opacity(!showModal || event != modifiableEvent ? 1.0 : 0.0)
                    }
                }
            }
        }
        .brightness(self.showModal ? -0.1 : 0)
        .blur(radius: self.showModal ? 16 : 0)
        
        if self.showModal {
            AddEventView(
                showModal: $showModal,
                existingEvent: modifiableEvent,
            )
            .opacity(showModal ? 1.0 : 0.0)
            .padding(.horizontal, 16)
        }            
    }
}

我遇到了this SO 帖子,答案似乎很有希望,但是答案没有考虑到其中一个视图是否在堆栈/网格中,对我来说就是这种情况。所以,我的问题是,如果其中一个视图确实嵌入在堆栈或网格中,我该如何调整链接解决方案以使其按预期工作。

编辑:另外需要注意的是Views的大小和位置不同

我尝试将.modifier(FlipEffect(flipped: $showModal, angle: animate3d ? 180 : 0, axis: (x: 0, y: 1))) 添加到ZStackSmallCardView,但是都没有产生预期的结果。

谢谢!

编辑:为了清楚起见,我想在这两个视图之间以卡片翻转样式制作动画:

【问题讨论】:

    标签: ios swift animation view swiftui


    【解决方案1】:

    这个非常简单的结构应该可以帮助您理解所需的必要结构:

    为此目的有一个特定的rotation3DEffect 修饰符。

    struct ContentView: View {
        
        // What is the current status
        @State var flipped: Bool = false
        
        // Whats the initial "flip" degree
        @State var degrees: Double = 180.0
        
        @State var width: CGFloat = 200
        @State var height: CGFloat = 300
        
        var body: some View {
            ZStack {
                if flipped {
                    //Cart Back
                    CardBack(width: self.$width, height: self.$height)
                      
                } else {
                    //Cart front
    
                    CardFront(width: self.$width, height: self.$height)
                     
                }
            }//Styling
            .background(Color.gray)
            .cornerRadius(20)
            .rotation3DEffect(.degrees(degrees), axis: (x: 0, y: 1, z: 0))
                
                // When tapped turn it around
            .onTapGesture {
                if self.flipped {
                    self.flipped = false
                    withAnimation {
                        self.degrees += 180
                        self.width = 200 // add other animated stuff here
                        self.height = 300
                    }
                } else {
                    self.flipped = true
                    withAnimation {
                        self.degrees -= 180
                        self.width = 300 // add other animated stuff here
                        self.height = 500
                    }
                }
            }
        }
    }
    
    struct CardBack: View {
        
        @Binding var width: CGFloat
        @Binding var height: CGFloat
        
        var body: some View {
            Rectangle().foregroundColor(Color.red).frame(width: self.width, height: self.height).overlay(Text("Back"))
        }
    }
    
    struct CardFront: View {
        
        @Binding var width: CGFloat
        @Binding var height: CGFloat
        
        var body: some View {
            Rectangle().foregroundColor(Color.blue).frame(width: self.width, height: self.height).overlay(Text("Front"))
        }
    }
    
    

    这会产生以下视图:

    【讨论】:

    • 当你两个视图的大小和位置不同时,这并不能很好地工作,在我的情况下它们是
    • 我必须承认我并没有真正看到你的问题。这是一个模型,应该可以帮助您理解这个概念。不实施成品。只需添加其他属性并在 tapGesture 中更改它们。我添加了卡片宽度和高度的示例。
    • 对我的不清楚的地方表示歉意。具体来说,我想要一个网格/堆栈内的视图,以及该堆栈的 顶部 的另一个视图(即,将网格内的视图移动到整个网格之上),而不仅仅是一个视图来回翻转。因此,动画必须从 Grid 内部移动到其上方作为示例,请参阅 iOS 14 Widgets 的编辑动画
    【解决方案2】:

    当卡片在 LazyVGrid 中使用 .matchedGeometryEffect() 时,我从来没有设法让它正常工作。所以这是我在项目中使用的滥用偏移和缩放的相当混乱的解决方案:

    struct TestView: View {
       @State var flippedCard: Int?
       @State var frontCard: Int?
       let cards = [1,2,3,4,5,6,7,8,9,10]
       
       var body: some View {
          let columns = [
             GridItem(.flexible(), spacing: 0),
             GridItem(.flexible(), spacing: 0),
             GridItem(.flexible(), spacing: 0)
          ]
          
          GeometryReader { screenGeometry in
             ZStack {
                ScrollView {
                   LazyVGrid(columns: columns, alignment: .center, spacing: 0) {
                      ForEach(cards, id: \.self) { card in
                         let isFaceUp = flippedCard == card
                         GeometryReader { cardGeometry in
                            ZStack {
                               CardBackView(card: card)
                                  .modifier(FlipOpacity(pct: isFaceUp ? 0 : 1))
                                  .rotation3DEffect(Angle.degrees(isFaceUp ? 180 : 360), axis: (0,1,0))
                                  .frame(width: cardGeometry.size.width, height: cardGeometry.size.height)
                                  .scaleEffect(isFaceUp ? screenGeometry.size.width / cardGeometry.size.width: 1)
                               CardFrontView(card: card)
                                  .modifier(FlipOpacity(pct: isFaceUp ? 1 : 0))
                                  .rotation3DEffect(Angle.degrees(isFaceUp ? 0 : 180), axis: (0,1,0))
                                  .frame(width: screenGeometry.size.width, height: screenGeometry.size.height)
                                  .scaleEffect(isFaceUp ? 1 : cardGeometry.size.width / screenGeometry.size.width)
                            }
                            .offset(x: isFaceUp ? -cardGeometry.frame(in: .named("mainFrame")).origin.x: -screenGeometry.size.width/2 + cardGeometry.size.width/2,
                                    y: isFaceUp ? -cardGeometry.frame(in: .named("mainFrame")).origin.y: -screenGeometry.size.height/2 + cardGeometry.size.height/2)
                            .onTapGesture {
                               withAnimation(.linear(duration: 1.0)) {
                                  if flippedCard == nil {
                                     flippedCard = card
                                     frontCard = card
                                  } else if flippedCard == card {
                                     flippedCard = nil
                                  }
                               }
                            }
                         }
                         .aspectRatio(1, contentMode: .fit)
                         .zIndex(frontCard == card ? 1 : 0)
                      }
                   }
                }
             }
             .background(Color.black)
          }
          .coordinateSpace(name: "mainFrame")
       }
    }
    
    struct FlipOpacity: AnimatableModifier {
       var pct: CGFloat = 0
       
       var animatableData: CGFloat {
          get { pct }
          set { pct = newValue }
       }
       
       func body(content: Content) -> some View {
          return content.opacity(Double(pct.rounded()))
       }
    }
    
    struct CardBackView: View {
       var card: Int
       
       var body: some View {
          ZStack {
             RoundedRectangle(cornerRadius: 10)
                .fill(Color.red)
                .padding(5)
             Text("Back \(card)")
          }
       }
    }
    
    struct CardFrontView: View {
       var card: Int
       
       var body: some View {
          ZStack {
             RoundedRectangle(cornerRadius: 10)
                .fill(Color.blue)
                .padding(10)
                .aspectRatio(1.0, contentMode: .fit)
             Text("Front \(card)")
          }
       }
    }
    

    【讨论】:

    • 非常感谢您分享这个示例和 gif 可视化。该代码允许基于这种工作方式理解和创建我自己的代码。
    【解决方案3】:

    所以为了解释答案,我想解释你需要实现什么。 您希望您的 view/editView 在它出现在前面时进行动画处理。这意味着我们需要使用transition 修饰符。

    现在 Apple 的内置转场修饰符使用了许多转场,例如easeIn、out 等,而没有这个转场,所以我们需要创建自定义转场来实现它。让我们先这样做。

    extension AnyTransition {
        static var rotate: AnyTransition { get {
            AnyTransition.modifier(active: RotateTransition(percent: 0), identity: RotateTransition(percent: 1))
            }
        }
    }
    
    
    struct RotateTransition: GeometryEffect {
        var percent: Double
        
        var animatableData: Double {
            get { percent }
            set { percent = newValue }
        }
        
        func effectValue(size: CGSize) -> ProjectionTransform {
    
            let rotationPercent = percent
            let a = CGFloat(Angle(degrees: 170 * (1-rotationPercent)).radians)
            
            var transform3d = CATransform3DIdentity;
            transform3d.m34 = -1/max(size.width, size.height)
            
            transform3d = CATransform3DRotate(transform3d, a, 0, 1, 0)
            transform3d = CATransform3DTranslate(transform3d, -size.width/2.0, -size.height/2.0, 0)
            
            let affineTransform1 = ProjectionTransform(CGAffineTransform(translationX: size.width/2.0, y: size.height / 2.0))
            let affineTransform2 = ProjectionTransform(CGAffineTransform(scaleX: CGFloat(percent * 2), y: CGFloat(percent * 2)))
            
            if percent <= 0.5 {
                return ProjectionTransform(transform3d).concatenating(affineTransform2).concatenating(affineTransform1)
            } else {
                return ProjectionTransform(transform3d).concatenating(affineTransform1)
            }
        }
    }
    

    现在我们有了自定义过渡,我们需要应用到那个视图。

    所以这是考虑到你有一个 cardView 的代码。

    cardView(card: cardName)
    .transition(.rotate)
    .matchedGeometryEffect(id: "popup", in: animation)
    

    在您的情况下,父视图就像您单击编辑的视图

    添加这个

      ParentView() //your view
    
      .matchedGeometryEffect(id: "popup", in: animation)
    

    你可以在这里看到输出:

    https://imgur.com/pIhBQ72

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-09-24
      • 1970-01-01
      • 2021-10-15
      • 1970-01-01
      • 2020-07-15
      • 2019-11-18
      • 2020-02-18
      相关资源
      最近更新 更多