Как вывести представление поверх VStack, содержащего ZStacks - PullRequest
0 голосов
/ 09 июля 2020

Во-первых, я новичок в Swiftui.

Мне нужна сетка просмотра, в которой размер каждого элемента изменяется в зависимости от количества элементов в сетке, и мне нужно расширять каждый элемент сетки при нажатии.

В конце концов мне удалось разложить элементы по мере необходимости (см. Рис. 1) и расширить их.

Fig 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.

Fig 2 Рис 3

Я также попытался встроить VStack и HStack в ZStack, но ничего не изменилось.

Как я могу поместить расширенный элемент наверх?

Под моим кодом .

    
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()
    }
}

1 Ответ

1 голос
/ 09 июля 2020

Вы разместили много кода. Я попытался немного упростить. В основном вы злоупотребляли свойствами size / zoomedSize / originalSize.

Сначала вы можете создать ItemInfo структуру и удалить все size связанные свойства:

struct ItemInfo {
    var symbol: String
    var color: Color

    init(symbol: String, color: Color) {
        self.symbol = symbol
        self.color = color
    }
}

Затем упростите ViewModel снова, удалив все связанные свойства size :

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]
    }
}

Затем обновите ItemView (снова удалите все свойства size из внешних представлений и используйте a 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)
    }
}

Теперь вы можете обновить 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)
                    }
            }
        }
    }
}

Наконец, в zoomedItemView я повторно использовал ItemView, но вы можете создать другой вид только для увеличенного элемента:

extension ContentView {
    var zoomedItemView: some View {
        Group {
            if zoomedItem != nil {
                ItemWiew(itemInfo: zoomedItem!)
                    .onTapGesture {
                        self.zoomedItem = nil
                    }
            }
        }
        .padding()
    }
}

Примечание: для простоты я сделал ItemInfo структурой. Это рекомендуется, если вы не планируете изменять его внутри zoomedView и применять изменения к сетке. Но если по какой-то причине вам нужно, чтобы это был класс и ObservableObject, вы можете легко восстановить исходное объявление:

class ItemInfo: ObservableObject, Identifiable { ... }

Не выбран элемент:

введите описание изображения здесь

С увеличенным элементом:

введите описание изображения здесь

...