【问题标题】:SwiftUI matchedGeometryEffect only animates one waySwiftUImatchedGeometryEffect 仅以一种方式设置动画
【发布时间】:2021-03-23 01:35:22
【问题描述】:

我有一个用于文件浏览器网格的 ZStack,当您导航到文件夹时它会添加图层。文件夹图标显示文件夹内容的预览。我在预览项目上有一个matchedGeometryEffect,以便它们可以动画到打开的文件夹中。问题是动画只在关闭文件夹时发生,而不是在打开时发生。

(我知道这段代码还不能推广到包含更多内容的文件夹。如果我能让这些动画正常工作,我会做到这一点。

很抱歉一次转储大量代码,但一切都应该在这里,以便它可以在项目中试用。

struct File: Hashable {
    var symbol: String?
    var name: String
    var contents: [File]? = nil
}

struct GridNavNew: View {
    
    @Namespace private var ns
    
    static var files: [File] = [
        File(name: "Folder", contents: [
            File(symbol: "cloud", name: "Cloudy"),
            File(symbol: "cloud.hail", name: "Hail"),
            File(symbol: "cloud.snow", name: "Snow"),
            File(symbol: "cloud.fog", name: "Fog")
        ]),
        File(symbol: "cube", name: "Cube"),
        File(symbol: "books.vertical", name: "Books")
    ]
    
    static let root = File(symbol: nil, name: "/", contents: files)
    
    @State private var path: [File] = [root]
    
    var body: some View {
        ZStack {
            // This exists because otherwise the transition doesn't play on the way out
            // See https://sarunw.com/posts/how-to-fix-zstack-transition-animation-in-swiftui/
            Text("A")
                .opacity(0)
                .zIndex(1)
            
            ForEach(Array(path.enumerated()), id: \.offset) { index, dir in
                NavigationView {
                    if let content = dir.contents {
                        ScrollView {
                            LazyVGrid(columns: [GridItem(), GridItem()]) {
                                ForEach(content, id: \.self) { file in
                                    Button {
                                        withAnimation {
                                            path.append(file)
                                        }
                                    } label: {
                                        FileCell(file: file,
                                                    onTop: path.last == dir,
                                                    ns: ns)
//                                            .animation(.default)
                                    }
                                }
                            }
                            .padding()
                        }
                        .navigationTitle(dir.name)
                        .toolbar {
                            ToolbarItem(placement: .navigation) {
                                if path.first != dir {
                                    Button("Back") {
                                        withAnimation {
                                            path.removeLast()
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                .transition(.move(edge: .trailing))
            }
        }
        .listStyle(PlainListStyle())
    }
}

struct FileCell: View {
    
    var file: File
    var compact = false
    var onTop: Bool
    
    var ns: Namespace.ID
    
    private func previewGrid(_ contents: [File]) -> some View {
        LazyVGrid(columns: [GridItem(), GridItem()]) {
            ForEach(contents, id: \.self) { file in
                FileCell(file: file, compact: true, onTop: onTop, ns: ns)
            }
        }
    }
    
    private var icon: some View {
        RoundedRectangle(cornerRadius: compact ? 10 : 20)
            .aspectRatio(1, contentMode: .fill)
            .foregroundColor(.accentColor)
            .opacity(file.contents == nil ? 1 : 0)
            .overlay(
                Group {
                    if let symbol = file.symbol {
                        Image(systemName: symbol)
                            .resizable()
                            .scaledToFit()
                            .padding()
                            .foregroundColor(.primary)
                    } else if onTop,
                              let contents = file.contents {
                        previewGrid(contents)
                    }
                }
            )
    }
    
    var body: some View {
        VStack {
            icon
                .matchedGeometryEffect(id: file, in: ns)
            if !compact {
                Text(file.name)
            }
        }
    }
}

matchedGeometryEffect 在其body 中位于FileCell 的底部。

我认为可能导致问题的一件事是,由于.transition(.move(edge: .trailing)),在转换过程中匹配的几何图形不在屏幕上,但使用任何其他转换或根本不使用,也会出现同样的问题。

我还认为 ZStack 可能会导致问题,因为它已经在转换时出现了(请参阅 https://sarunw.com/posts/how-to-fix-zstack-transition-animation-in-swiftui/),但是为了测试而将 ZStack 更改为 VStack 并没有解决问题。

您可能还会注意到 FileCell 上已注释掉的 .animation(.default)。这确实允许在打开文件夹时进行动画转换,但是动画效果与在该行不存在时关闭文件夹的效果不匹配,并且还会导致文件夹关闭时文件单元格重复,从而导致错误- 外观动画。

编辑:我还想提一下,我没有收到 Multiple inserted views in matched geometry group 错误,所以这也不是问题。

【问题讨论】:

    标签: ios swift swiftui


    【解决方案1】:

    我遇到了同样的问题。您需要在 .matchedGeometryEffect() 修饰符之后添加一个动画修饰符。

    这两种方式都具有动画效果:

    Text("TEST")
        .matchedGeometryEffect(id: "test", in: namespace)
        .animation(.easeInOut)
    

    这只动画前半部分

    Text("TEST")
        .animation(.easeInOut)
        .matchedGeometryEffect(id: "test", in: namespace)
    

    【讨论】:

      【解决方案2】:

      我进行了一些研究,并能够防止这种行为。所以现在据我了解,matchedGeometryEffect 通常是一个修饰符,它使一个视图具有另一个视图的大小/位置,除了动画目的。

      这样我得出结论,如果尺寸以某种方式损坏(它们可能是zero),动画可能会损坏。解决方案是为两个视图设置显式框架。

      someView
          .matchedGeometryEffect(id: "view", in: namespace)
          .animation(.easeInOut)
          .frame(width: 100, height: 100) // or .fixedSize()
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-03-16
        • 1970-01-01
        • 2019-01-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多