SwiftUImatchedGeometryEffect 仅以一种方式设置动画

Posted

技术标签:

【中文标题】SwiftUImatchedGeometryEffect 仅以一种方式设置动画【英文标题】:SwiftUI matchedGeometryEffect only animates one way 【发布时间】: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 错误,所以这也不是问题。

【问题讨论】:

【参考方案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()

【讨论】:

以上是关于SwiftUImatchedGeometryEffect 仅以一种方式设置动画的主要内容,如果未能解决你的问题,请参考以下文章