[英]How to bring a view on top of VStack containing ZStacks
I need a view grid where each item resizes depending on the number of items in the grid and I need to expand each item of the grid when tapped.我需要一个视图网格,其中每个项目根据网格中项目的数量调整大小,并且我需要在点击时扩展网格的每个项目。
I eventually managed to layout out the items as required (see Fig 1) and to expand them.我最终设法根据需要布置项目(见图 1)并展开它们。
Unfortunately I am not able to properly bring the expanded item in front of all other views (see Fig 2 and Fig 3) using zIndex.不幸的是,我无法使用 zIndex 将展开的项目正确地放在所有其他视图的前面(参见图 2 和图 3)。
I also tried to embed both VStack and HStack into a ZStack but nothing changes.我还尝试将 VStack 和 HStack 都嵌入到 ZStack 中,但没有任何变化。
How can I bring the expanded item on top?我怎样才能把展开的项目放在上面?
Below is my code.下面是我的代码。
struct ContentViewNew: View {
private let columns: Int = 6
private let rows: Int = 4
@ObservedObject var viewModel: ViewModel
var cancellables = Set<AnyCancellable>()
init() {
viewModel = ViewModel(rows: rows, columns: columns)
viewModel.objectWillChange.sink { _ in
print("viewModel Changed")
}.store(in: &cancellables)
}
var body: some View {
GeometryReader { geometryProxy in
let hSpacing: CGFloat = 7
let vSpacing: CGFloat = 7
let hSize = (geometryProxy.size.width - hSpacing * CGFloat(columns + 1)) / CGFloat(columns)
let vSize = (geometryProxy.size.height - vSpacing * CGFloat(rows + 1)) / CGFloat(rows)
let size = min(hSize, vSize)
VStack {
ForEach(0 ..< viewModel.rows, id: \.self) { row in
Spacer()
HStack {
Spacer()
ForEach(0 ..< viewModel.columns, id: \.self) { column in
GeometryReader { widgetProxy in
ItemWiew(info: viewModel.getItem(row: row, column: column), size: size, zoomedSize: 0.80 * geometryProxy.size.width)
.offset(x: viewModel.getItem(row: row, column: column).zoomed ? (geometryProxy.size.width / 2.0 - (widgetProxy.frame(in: .global).origin.x + widgetProxy.size.width / 2.0)) : 0,
y: viewModel.getItem(row: row, column: column).zoomed ? geometryProxy.size.height / 2.0 - (widgetProxy.frame(in: .global).origin.y + widgetProxy.size.height / 2.0) : 0)
.onTapGesture {
viewModel.zoom(row: row, column: column)
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.zIndex(viewModel.getItem(row: row, column: column).zoomed ? 10000 : 0)
.background(Color.gray)
}
Spacer()
}
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.background(Color.blue)
Spacer()
}
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.background(Color.yellow)
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.background(Color.green)
}
}
struct ItemWiew: View {
@ObservedObject var info: ItemInfo
var size: CGFloat
init(info: ItemInfo, size: CGFloat, zoomedSize: CGFloat) {
self.info = info
self.size = size
if self.info.size == 0 {
self.info.size = size
self.info.zoomedSize = zoomedSize
}
}
var body: some View {
VStack {
Print("Drawing Widget with size \(self.info.size)")
Image(systemName: info.symbol)
.font(.system(size: 30))
.frame(width: info.size, height: info.size)
.background(info.color)
.cornerRadius(10)
}
}
}
class ItemInfo: ObservableObject, Identifiable {
var symbol: String
var color: Color
var zoomed = false
@Published var size: CGFloat
@Published var originalSize: CGFloat
@Published var zoomedSize: CGFloat
init(symbol: String, color: Color) {
self.symbol = symbol
self.color = color
size = 0.0
originalSize = 0.0
zoomedSize = 0.0
}
func toggleZoom() {
if zoomed {
size = originalSize
color = .red
} else {
size = zoomedSize
color = .white
}
zoomed.toggle()
}
}
class ViewModel: ObservableObject {
private var symbols = ["keyboard", "hifispeaker.fill", "printer.fill", "tv.fill", "desktopcomputer", "headphones", "tv.music.note", "mic", "plus.bubble", "video"]
private var colors: [Color] = [.yellow, .purple, .green]
@Published var listData = [ItemInfo]()
var rows = 0
var columns = 0
init(rows: Int, columns: Int) {
self.rows = rows
self.columns = columns
for _ in 0 ..< rows {
for j in 0 ..< columns {
listData.append(ItemInfo(symbol: symbols[j % symbols.count], color: colors[j % colors.count]))
}
}
}
func getItem(row: Int, column: Int) -> ItemInfo {
return listData[columns * row + column]
}
func zoom(row: Int, column: Int) {
listData[columns * row + column].toggleZoom()
objectWillChange.send()
}
}
There is a lot of code you posted.您发布了很多代码。 I tried to simplify it a bit.我试着把它简化一点。 Mostly you overused size/zoomedSize/originalSize properties.大多数情况下,您过度使用了 size/zoomedSize/originalSize 属性。
First you can make ItemInfo
a struct and remove all size related properties:首先,您可以使ItemInfo
成为结构并删除所有与大小相关的属性:
struct ItemInfo {
var symbol: String
var color: Color
init(symbol: String, color: Color) {
self.symbol = symbol
self.color = color
}
}
Then simplify your ViewModel
again by removing all size related properties:然后通过删除所有与大小相关的属性再次简化您的ViewModel
:
class ViewModel: ObservableObject {
private var symbols = ["keyboard", "hifispeaker.fill", "printer.fill", "tv.fill", "desktopcomputer", "headphones", "tv.music.note", "mic", "plus.bubble", "video"]
private var colors: [Color] = [.yellow, .purple, .green]
@Published var listData = [ItemInfo]()
let rows: Int
let columns: Int
init(rows: Int, columns: Int) {
self.rows = rows
self.columns = columns
for _ in 0 ..< rows {
for j in 0 ..< columns {
listData.append(ItemInfo(symbol: symbols[j % symbols.count], color: colors[j % colors.count]))
}
}
}
func getItem(row: Int, column: Int) -> ItemInfo {
return listData[columns * row + column]
}
}
Then update your ItemView
(again remove all size related properties from outer views and use a GeometryReader
directly):然后更新您的ItemView
(再次从外部视图中删除所有与大小相关的属性并直接使用GeometryReader
):
struct ItemWiew: View {
let itemInfo: ItemInfo
var body: some View {
GeometryReader { proxy in
self.imageView(proxy: proxy)
}
}
// extracted to another function as you can't use `let` inside a `GeometryReader` closure
func imageView(proxy: GeometryProxy) -> some View {
let sideLength = min(proxy.size.width, proxy.size.height) // to make it fill all the space but remain a square
return Image(systemName: itemInfo.symbol)
.font(.system(size: 30))
.frame(maxWidth: sideLength, maxHeight: sideLength)
.background(itemInfo.color)
.cornerRadius(10)
}
}
Now you can update the ContentView
:现在您可以更新ContentView
:
struct ContentView: View {
private let columns: Int = 6
private let rows: Int = 4
@ObservedObject var viewModel: ViewModel
// zoomed item (nil if no item is zoomed)
@State var zoomedItem: ItemInfo?
init() {
viewModel = ViewModel(rows: rows, columns: columns)
}
var body: some View {
ZStack {
gridView
zoomedItemView
}
}
var gridView: some View {
let spacing: CGFloat = 7
return VStack(spacing: spacing) {
ForEach(0 ..< viewModel.rows, id: \.self) { rowIndex in
self.rowView(rowIndex: rowIndex)
}
}
.padding(.all, spacing)
}
func rowView(rowIndex: Int) -> some View {
let spacing: CGFloat = 7
return HStack(spacing: spacing) {
ForEach(0 ..< viewModel.columns, id: \.self) { columnIndex in
ItemWiew(itemInfo: self.viewModel.getItem(row: rowIndex, column: columnIndex))
.onTapGesture {
// set zoomed item on tap gesture
self.zoomedItem = self.viewModel.getItem(row: rowIndex, column: columnIndex)
}
}
}
}
}
Lastly in the zoomedItemView
I reused the ItemView
but you can create some other view just for the zoomed item:最后在zoomedItemView
我重用了ItemView
但您可以为缩放项目创建一些其他视图:
extension ContentView {
var zoomedItemView: some View {
Group {
if zoomedItem != nil {
ItemWiew(itemInfo: zoomedItem!)
.onTapGesture {
self.zoomedItem = nil
}
}
}
.padding()
}
}
Note: for simplicity I made ItemInfo
a struct.注意:为简单起见,我将ItemInfo
结构。 This is recommended if you don't plan to modify it inside the zoomedView
and apply changes to the grid.如果您不打算在zoomedView
内对其进行修改并将更改应用于网格,则建议您这样做。 But if for some reason you need it to be a class and an ObservableObject
you can easily restore your original declaration:但是如果由于某种原因你需要它是一个class和一个ObservableObject
你可以很容易地恢复你原来的声明:
class ItemInfo: ObservableObject, Identifiable { ... }
No item selected:未选择项目:
With a zoomed item:使用缩放项目:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.