帮助理解 SwiftUI 视图层次结构和 .zIndex

问题描述 投票:0回答:1

我正在尝试学习 SwiftUI 并且相处得很好,但我遇到了 .zIndex / 视图放置的问题,教程和 AI 答案似乎无法帮助我解决。非常感谢 BenzyNeez 的投入,我已经解决了使其全屏显示的问题之一。子视图应该全屏显示并位于所有其他视图之上,就像最终图片一样。但子视图位于全屏视图之上。

struct PeopleView: View {
    
    struct Person: Identifiable {
        var id: UUID = UUID()
        var first: String
        var last: String
        var isFullScreen: Bool
        var tag: Int
    }
    
    func getColor(index: Int) -> Color {
        switch index {
        case 1: return .blue
        case 2: return .green
        case 3: return .yellow
        case 4: return .orange
        case 5: return .purple
        case 6: return .pink
        default: return .gray
        }
    }
    @Namespace private var animationNamespace
    
    @State private var selectedPerson: Person? = nil
    private let personId = UUID()
    private let cardWidth: CGFloat = UIScreen.main.bounds.width / 3
    @State private var fullscreen: Bool = false
    
    var columns: [GridItem] { Array(repeating: .init(.flexible()), count: 2) }
    
    @State private var people: [Person] = [
        Person(first: "John", last: "Doe", isFullScreen: false, tag: 1),
        Person(first: "Jane", last: "Doe", isFullScreen: false, tag: 2),
        Person(first: "Fred", last: "Doe", isFullScreen: false, tag: 3),
        Person(first: "Bill", last: "Doe", isFullScreen: false, tag: 4),
        Person(first: "Jack", last: "Doe", isFullScreen: false, tag: 5),
        Person(first: "Mary", last: "Doe", isFullScreen: false, tag: 6)
    ]
    
    
    private func personView(person: Person) -> some View {
        RoundedRectangle(cornerRadius: 5)
            .foregroundStyle(getColor(index: person.tag))
            .shadow(radius: 5)
            .overlay {
                Text(person.first)
            }
            .matchedGeometryEffect(
                id: selectedPerson?.id == person.id ? personId : person.id,
                in: animationNamespace,
                isSource: false
            )
    }
    
    private var floatingPersonViews: some View {
        LazyVGrid(columns: columns, spacing: 20) {
            ForEach(people.indices, id: \.self) { index in
                personView(person: people[index])
                    .allowsHitTesting(false)
                    .frame(height: 320)
            }
        }
    }
    
    
    private var cardBases: some View {
        HStack {
            LazyVGrid(columns: columns, spacing: 20) {
                ForEach(people.indices, id: \.self) { index in
                    RoundedRectangle(cornerRadius: 5)
                        .fill(Color.black)
                        .frame(width: cardWidth, height: 100)
                        .onTapGesture {
                            withAnimation(.interactiveSpring(response: 0.3, dampingFraction: 0.8, blendDuration: 0.8)) {
                                selectedPerson = people[index]
                                people[index].isFullScreen = true
                                fullscreen = true
                            }
                        }
                        .matchedGeometryEffect(
                            id: people[index].id,
                            in: animationNamespace,
                            isSource: true
                        )
                        .zIndex(people[index].isFullScreen ? 1 : -1)
                }
            }
        }
        .padding()
    }
    
    
    private var detailBase: some View {
        Rectangle()
            .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
            .opacity(0)
            .matchedGeometryEffect(
                id: personId,
                in: animationNamespace,
                isSource: true
            )
    }
    
    
    private var detailView: some View {
        VStack {
            detailBase
        }
        .contentShape(Rectangle())
        .onTapGesture {
            withAnimation(.interactiveSpring(response: 0.3, dampingFraction: 0.8, blendDuration: 0.8)) {
                selectedPerson?.isFullScreen = false
                fullscreen = false
                selectedPerson = nil
            }
        }
    }
    
    var body: some View {
        ZStack {
            cardBases//.zIndex(fullscreen ? -3 : 0)
            floatingPersonViews//.zIndex(fullscreen ? -2 : 0)
            detailView.zIndex(fullscreen ? 1 : -1)
        }
    }
}

现在可以全屏显示并占据整个视图,但是除了右侧最后一个视图之外,LazyGrid 中的视图仍然在顶部可见。

Home Screen John Selected Mary Selected

我尝试将 DetailView 的 .zIndex 设置为高,并使用全屏变量将其他两个视图设置为低。但 .zIndex 设置为什么似乎并不重要。

ios swift xcode swiftui tvos
1个回答
0
投票

您肯定已经非常接近解决方案了。通过以下更改,您可以使其正常工作:

  • 浮动人物视图应该是

    ZStack
    的顶层,并且点击手势应该附加到这些视图。

  • 浮动视图的位置是使用

    .matchedGeometryEffect
    确定的,因此这些视图不需要位于
    LazyVGrid
    内。事实上,这似乎会导致
    zIndex
    工作出现问题。一个简单的
    ZStack
    可以用作人物视图的容器。

  • 占位符都应具有固定的 id

    .matchedGeometryEffect
    。然后,使用人员视图的 id 来控制人员视图与哪个占位符匹配(这可以是网格中的卡片,也可以是全屏详细视图)。

  • 不需要任何布尔标志。只需状态变量

    selectedPerson
    即可控制缩放效果。

  • 选择一个人后,需要抬起其

    zIndex
    ,使其浮在其他卡片上方。

其他建议:

  • 结构体

    Person
    Identifable
    ,所以
    ForEach
    不需要使用数组的索引进行迭代。

  • 不要使用

    UIScreen.main
    来查找屏幕尺寸。它不适用于 iPad 分屏,并且已被弃用。请使用
    GeometryReader
    代替。

  • Color.clear
    可用作全屏视图的占位符。与所有颜色一样,这是贪婪的,因此它使用尽可能多的空间(全屏)。如果它也应该进入安全区域插图,请添加
    .ignoresSafeArea()

  • 不需要在 switch 语句的每种情况下都使用

    return
    。您可以只使用相同的 switch 语句,但不带返回值。

这是更新的示例:

struct PeopleView: View {

    struct Person: Identifiable {
        var id: UUID = UUID()
        var first: String
        var last: String
        var tag: Int
    }

    func getColor(index: Int) -> Color {
        switch index {
        case 1: .blue
        case 2: .green
        case 3: .yellow
        case 4: .orange
        case 5: .purple
        case 6: .pink
        default: .gray
        }
    }

    @Namespace private var animationNamespace
    @State private var selectedPerson: Person? = nil
    private let fullScreenId = UUID()
    private let columns: [GridItem] = Array(repeating: .init(.flexible()), count: 2)

    @State private var people: [Person] = [
        Person(first: "John", last: "Doe", tag: 1),
        Person(first: "Jane", last: "Doe", tag: 2),
        Person(first: "Fred", last: "Doe", tag: 3),
        Person(first: "Bill", last: "Doe", tag: 4),
        Person(first: "Jack", last: "Doe", tag: 5),
        Person(first: "Mary", last: "Doe", tag: 6)
    ]

    private func personView(person: Person) -> some View {
        RoundedRectangle(cornerRadius: 5)
            .foregroundStyle(getColor(index: person.tag))
            .shadow(radius: 5)
            .overlay {
                Text(person.first)
            }
            .zIndex(selectedPerson?.id == person.id ? 1 : 0)
            .onTapGesture {
                withAnimation(.interactiveSpring(response: 0.3, dampingFraction: 0.8, blendDuration: 0.8)) {
                    selectedPerson = selectedPerson == nil ? person : nil
                }
            }
            .matchedGeometryEffect(
                id: selectedPerson?.id == person.id ? fullScreenId : person.id,
                in: animationNamespace,
                isSource: false
            )
    }

    private var floatingPersonViews: some View {
        ZStack {
            ForEach(people) { person in
                personView(person: person)
                    .frame(height: 320)
            }
        }
    }

    private var cardBases: some View {
        GeometryReader { proxy in
            LazyVGrid(columns: columns, spacing: 20) {
                ForEach(people) { person in
                    RoundedRectangle(cornerRadius: 5)
                        .fill(Color.black)
                        .frame(width: proxy.size.width / 3, height: 100)
                        .matchedGeometryEffect(
                            id: person.id,
                            in: animationNamespace,
                            isSource: true
                        )
                }
            }
            .padding()
        }
    }

    private var detailView: some View {
        Color.clear
            .matchedGeometryEffect(
                id: fullScreenId,
                in: animationNamespace,
                isSource: true
            )
            .ignoresSafeArea()
    }

    var body: some View {
        ZStack {
            cardBases
            detailView
            floatingPersonViews
        }
    }
}

Animation

© www.soinside.com 2019 - 2024. All rights reserved.