ios - 匹配的几何效果向后动画似乎在 View 后面

标签 ios swift swiftui swiftui-list

我正在尝试在列表上使用匹配的几何效果,并且至少对于详细 View 的动画来说,它的工作方式与预期的一样。 问题是从详细 View 返回列表单元格的动画似乎在列表后面。

enter image description here

我怎样才能让它正常工作,以便详细 View 缩小回列表单元格? 这是我手机的代码:

struct Cell: View {

var exercise: Exercise
var index: Int
var namespace: Namespace.ID

var body: some View {
    VStack {
        Text(exercise.title)
            .matchedGeometryEffect(id: exercise.id, in: namespace)
            .font(.title)
            .foregroundColor(.white)
            .frame(maxWidth: .infinity, alignment: .leading)
            .frame(height: 100)
    }
    .padding(.horizontal)
    .padding(.top)
    .padding(.bottom, 4)
    .background(
        RoundedRectangle(cornerRadius: 5, style: .continuous)
            .matchedGeometryEffect(id: exercise.id + "background", in: namespace)
            .background(Color.clear)
            .foregroundColor(Color.gray)
    )
    .clipped()
}
}

这是列表本身:

struct ListView: View {

var testData: [Exercise]
var namespace: Namespace.ID
@Binding var tappedCellIndex: Int?

var body: some View {
    ScrollView {
        LazyVStack {
            ForEach(testData.indices) { index in
                Cell(exercise: testData[index], index: index, namespace: namespace)
                .onTapGesture {
                    withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
                        self.tappedCellIndex = index
                    }
                }
            }
        }
    }
}
}

这是内容 View :

struct ContentView: View {
let testData = [Exercise(title: "Hallo"), Exercise(title: "Bankdrücken"), Exercise(title: "Squats"), Exercise(title: "Seitheben"), Exercise(title: "Klimmzüge"), Exercise(title: "Bizepscurls"), Exercise(title: "Dips"), Exercise(title: "Aufroller"), Exercise(title: "Muscle"), Exercise(title: "Dragon Flies"), Exercise(title: "Hallo"), Exercise(title: "Bankdrücken"), Exercise(title: "Squats"), Exercise(title: "Seitheben"), Exercise(title: "Klimmzüge"), Exercise(title: "Bizepscurls"), Exercise(title: "Dips"), Exercise(title: "Aufroller"), Exercise(title: "Muscle"), Exercise(title: "Dragon Flies")]

@State var tappedCellIndex: Int? = nil
@Namespace var namespace

var body: some View {
    ZStack {
        VStack {
            ListView(testData: testData, namespace: namespace, tappedCellIndex: $tappedCellIndex)
                .opacity(tappedCellIndex == nil ? 1 : 0)
                .transition(.scale(scale: 1))
        }
        
        if let tappedCellIndex = tappedCellIndex {
            DetailView(exercise: testData[tappedCellIndex], selectedIndex: $tappedCellIndex, namespace: namespace)
                .transition(.scale(scale: 1))
        }
    }
}
}

这是它应该动画返回的详细 View :

struct DetailView: View {

var exercise: Exercise
@Binding var selectedIndex: Int?
var namespace: Namespace.ID

var body: some View {
    VStack(alignment: .leading) {
        HStack {
            Text(exercise.title)
                .matchedGeometryEffect(id: exercise.id, in: namespace)
                .font(.title)
                .frame(maxWidth: .infinity, alignment: .leading)
                .foregroundColor(Color.white)
                .padding(.top, 12)
            Spacer()
            Button {
                withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
                    selectedIndex = nil
                }
            } label: {
                Text("Klicke hier!")
            }
        }

        Spacer()
    }
    .frame(maxWidth: .infinity, maxHeight: .infinity)
    .padding()
    .background(
        RoundedRectangle(cornerRadius: 30, style: .continuous)
            .matchedGeometryEffect(id: exercise.id + "background", in: namespace)
            .foregroundColor(Color.gray)
    )
}
}

最后这是模型:

struct Exercise: Identifiable, Hashable, Equatable {
var id = UUID().uuidString
var title: String

static func == (lhs: Exercise, rhs: Exercise) -> Bool {
    return lhs.id == rhs.id
}

func hash(into hasher: inout Hasher) {
    hasher.combine(id)
}

}

对不起,代码太多了。我希望我添加了所有需要的内容。

顺便说一句:我还收到错误“在匹配的几何组中插入多个 View ”。我认为那是因为我在显示详细 View 时没有隐藏列表。 我在 stackoverflow 上阅读了关于此的不同意见,在我看来,并不是在所有情况下都需要在显示另一个 View 时隐藏一个 View ,我也许可以忽略错误消息。

我也无法隐藏列表,因为重新加载会刷新列表,我会再次从列表顶部开始,因此向后动画也将不起作用。

如果有任何帮助,我将不胜感激

最佳答案

我肯定会建议显示一个空的占位符以防止“多个插入的 View ”。此外,.frame(maxWidth, maxHeight) 必须位于 .matchedGeometry 之前,因此 View 可以在动画中缩小或增大。

enter image description here

修改后的代码,查看注释:

struct Cell: View {
    
    var exercise: Exercise
    var index: Int
    var namespace: Namespace.ID
    
    var body: some View {
        VStack {
            Text(exercise.title)
                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) // moved + maxHeight!
                .font(.title)
                .foregroundColor(.white)
                .matchedGeometryEffect(id: exercise.id, in: namespace)
        }
        .frame(height: 100) // moved
        .padding(.horizontal)
        .padding(.top)
        .padding(.bottom, 4)
        .background(
            RoundedRectangle(cornerRadius: 5, style: .continuous)
                .matchedGeometryEffect(id: exercise.id + "background", in: namespace)
                .background(Color.clear)
                .foregroundColor(Color.gray)
        )
        //    .clipped()
    }
}


struct ListView: View {
    
    var testData: [Exercise]
    var namespace: Namespace.ID
    @Binding var tappedCellIndex: Int?
    
    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(testData.indices) { index in
                    // check if tapped
                    if index != tappedCellIndex {
                        Cell(exercise: testData[index], index: index, namespace: namespace)
                            .onTapGesture {
                                withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
                                    self.tappedCellIndex = index
                                }
                            }
                    } else { // display empty space
                        Color.clear
                            .frame(height: 100)
                    }
                }
            }
        }
    }
}


struct ContentView: View {
    let testData = [Exercise(title: "Hallo"), Exercise(title: "Bankdrücken"), Exercise(title: "Squats"), Exercise(title: "Seitheben"), Exercise(title: "Klimmzüge"), Exercise(title: "Bizepscurls"), Exercise(title: "Dips"), Exercise(title: "Aufroller"), Exercise(title: "Muscle"), Exercise(title: "Dragon Flies"), Exercise(title: "Hallo"), Exercise(title: "Bankdrücken"), Exercise(title: "Squats"), Exercise(title: "Seitheben"), Exercise(title: "Klimmzüge"), Exercise(title: "Bizepscurls"), Exercise(title: "Dips"), Exercise(title: "Aufroller"), Exercise(title: "Muscle"), Exercise(title: "Dragon Flies")]
    
    @State var tappedCellIndex: Int? = nil
    @Namespace var namespace
    
    var body: some View {
        ZStack {
            VStack {
                ListView(testData: testData, namespace: namespace, tappedCellIndex: $tappedCellIndex)
                    .opacity(tappedCellIndex == nil ? 1 : 0)
                //                .transition(.scale(scale: 1))
            }
            
            if let tappedCellIndex = tappedCellIndex {
                DetailView(exercise: testData[tappedCellIndex], selectedIndex: $tappedCellIndex, namespace: namespace)
                //                .transition(.scale(scale: 1))
            }
        }
    }
}


struct DetailView: View {
    
    var exercise: Exercise
    @Binding var selectedIndex: Int?
    var namespace: Namespace.ID
    
    var body: some View {
        VStack(alignment: .leading) {
            HStack {
                Text(exercise.title)
                    .frame(maxWidth: .infinity, alignment: .leading) // moved
                    .matchedGeometryEffect(id: exercise.id, in: namespace)
                    .font(.title)
                    .foregroundColor(Color.white)
                    .padding(.top, 12)
                Spacer()
                Button {
                    withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
                        selectedIndex = nil
                    }
                } label: {
                    Text("Klicke hier!")
                }
            }
            
            Spacer()
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .padding()
        .background(
            RoundedRectangle(cornerRadius: 30, style: .continuous)
                .matchedGeometryEffect(id: exercise.id + "background", in: namespace)
                .foregroundColor(Color.gray)
        )
    }
}


struct Exercise: Identifiable, Hashable, Equatable {
    var id = UUID().uuidString
    var title: String
    
    static func == (lhs: Exercise, rhs: Exercise) -> Bool {
        return lhs.id == rhs.id
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
    
}

关于ios - 匹配的几何效果向后动画似乎在 View 后面,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72922210/

相关文章:

swift - URLSession.shared.dataTaskPublisher 不适用于 IOS 13.3

ios - 如何使用具有多列的 UIpickerView 填充 TextField?

ios - 如果从 Storyboard 添加约束,GMSMapView 不显示任何内容

swift - 粒子效果后如何从屏幕上删除 Sprite 节点

iOS:快速将 UnsafeMutablePointer<Int8> 转换为字符串?

macos - 如何删除 macOS SwiftUI 应用程序中的 "New window"选项?

ios - 如何在 swift 中将月份名称数组转换为月份编号

ios - Firebase Messenger 发送通知,但 iOS 设备未收到任何消息

cocoa - NSView 的 subview 如何动态定位?

ios - 在纵向模式下拍摄的图像选择器 (UIImagePickerController) 中的照片显示拉伸(stretch) - Xcode 11/SwiftUI