我正在尝试学习 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 中的视图仍然在顶部可见。
我尝试将 DetailView 的 .zIndex 设置为高,并使用全屏变量将其他两个视图设置为低。但 .zIndex 设置为什么似乎并不重要。
您肯定已经非常接近解决方案了。通过以下更改,您可以使其正常工作:
浮动人物视图应该是
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
}
}
}