首页 > 解决方案 > 在复杂的 UI 中使用 SwiftUI 的matchedGeometryEffect

问题描述

在我的应用程序中,我想在卡片和全屏覆盖之间创建一个“英雄”动画,这matchedGeometryEffect似乎很适合。但是,无论我尝试什么,我都无法让动画按预期工作,而且它看起来一点也不像通常的matchedGeometryEffect动画。这是到目前为止的样子。这是我目前所拥有的:(为大量代码道歉,但这是必要的,因为对于一个简单的视图来说,它工作正常)

东西.swift

struct Something: Identifiable {
    let id = UUID()
    let image: Image
}

内容视图.swift

struct ContentView: View {
    @Namespace var namespace
    
    let items: [Something] = [
        Image("a"), Image("b")
    ].map { Something(image: $0 )}
    
    @State var selectedItem: Something?

    var body: some View {
        ZStack {
            VStack {
                ScrollView {
                    VStack(alignment: .leading) {
                        ForEach(items) { item in
                            CardView(
                                image: item.image,
                                namespace: namespace,
                                isSource: self.selectedItem == nil,
                                id: item.id
                            )
                            .background(Color.white)
                            .contentShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
                            .zIndex(1)
                            .onTapGesture {
                                withAnimation(.spring()) {
                                    self.selectedItem = item
                                }
                            }
                        }
                    }
                }
            }
            .overlay(EmptyView())
            
            if let item = selectedItem {
                EventView(
                    image: item.image
                ) {
                    self.selectedItem = nil
                }
                .matchedGeometryEffect(id: item.id, in: namespace, isSource: false)
                .zIndex(2)
            }
        }
        .animation(.spring())
        .transition(.scale)
    }
}

CardView.swift

struct CardView: View {
    let image: Image
    let namespace: Namespace.ID
    let isSource: Bool
    let id: UUID

    var body: some View {
        VStack(alignment: .leading) {
            ZStack(alignment: .bottomTrailing) {
                image
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(height: 225)
                    .clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
                    .matchedGeometryEffect(id: id, in: namespace, isSource: isSource)
            }
        }
    }
}

事件视图.swift

struct EventView: View {
    let image: Image
    let onDismiss: () -> Void
        
    var body: some View {
        image
            .resizable()
            .aspectRatio(contentMode: .fill)
            .edgesIgnoringSafeArea(.all)
            .onTapGesture(perform: onDismiss)
    }
}

任何关于添加或更改以使其正常工作的建议将非常感谢,谢谢!

标签: iosanimationswiftuitransition

解决方案


我为同样的要求所做的是添加properties: .positionmatchedGeometryEffect. 然后,您需要指定“如何”从一个视图(例如缩略图卡片视图)转换到另一个视图(例如全屏卡片视图)。这是通过自定义转换完成的,例如:

extension AnyTransition
    {
    // This transition will pass a value (0.0 - 1.0), indicating how much of the
    // transition has passed. To communicate with the view, it will
    // use the custom environment key .modalTransitionPercent
    // it will also make sure the transitioning view is not faded in or out and it
    // stays visible at all times.
    static var modal: AnyTransition
        {
        AnyTransition.modifier(active: ThumbnailExpandedModifier(pct: 0), identity: ThumbnailExpandedModifier(pct: 1))
        }

    struct ThumbnailExpandedModifier: AnimatableModifier
        {
        var pct: CGFloat
        
        var animatableData: CGFloat
            {
            get { pct }
            set { pct = newValue }
            }

        func body(content: Content) -> some View
            {
            return content
                .environment(\.modalTransitionPercent, pct)
                .opacity(1)
            }
        }
  }

extension EnvironmentValues
    {
    var modalTransitionPercent: CGFloat
        {
        get { return self[ModalTransitionKey.self] }
        set { self[ModalTransitionKey.self] = newValue }
        }
    }

public struct ModalTransitionKey: EnvironmentKey
    {
    public static let defaultValue: CGFloat = 0
    }

推荐阅读