如何将手势数据传递到父视图以及为什么我的所有图像都拖到一起?

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

我只是一个尝试学习 swiftui 的业余爱好者,所以我可能没有提出正确的问题,但这是我创建的视图,用于构建可用于跳棋的基本方形网格。它的工作原理与我想要的完全一样,但现在我正在尝试添加手势。

问题 1:在主 VStack 上的 .onTapGesture 下,“if x != nil { print(x!) } ”正确打印出被点击的方块。如何将该信息返回到调用它的视图或让它更改模型中的数据以注册选择和/或触发事件以突出显示正方形?该结构位于其自己的文件中。我是否必须将整个结构放入我的主结构中才能正常工作?我觉得我在概念上遗漏了一些东西。我可以通过按钮触发子视图的突出显示,这些子视图是更大视图的一部分,但无法弄清楚主结构之外的“通用视图”的方法是什么。

问题 2:它似乎将 .gesture(dragGesture) 的所有 imageItems 集中在一起。我只能通过单击网格中的最后一个项目来进行拖动,当我移动它时,所有图像项目都会作为一个整体移动。我感觉我之前读过一篇堆栈溢出解决了这个问题,但现在找不到了。

PS,如果你只是想教一点,哈哈,你会建议什么改变吗?

import SwiftUI

struct FixedSquareGrid<Item: Identifiable, ItemView: View>: View {
    let lightSquare: String
    let darkSquare: String
    
    @State private var translation = CGSize.zero
    @State private var lastTranslation = CGSize.zero

    var items: [Item]
    @ViewBuilder var content: (Item) -> ItemView
    
    init(_ items: [Item], lightSquare: String, darkSquare: String, @ViewBuilder content: @escaping (Item) -> ItemView) {
        self.items = items
        self.content = content
        self.lightSquare = lightSquare
        self.darkSquare = darkSquare
    }
    
    var body: some View {
        GeometryReader { geo in
            let numberOfRows: Int = Int(sqrt(Double(items.count)))
            let gridItemSize = gridItemWidthThatFits(
                count: numberOfRows,
                size: geo.size)
            let boardSize = maxBoardSize(
                boardSquaresSize: gridItemSize * CGFloat(numberOfRows),
                size: geo.size)
            
            VStack(spacing: 0) {
                ForEach (0..<numberOfRows, id: \.self) { row in
                    HStack(spacing: 0) {
                        ForEach (0..<numberOfRows, id: \.self) { col in
                            let itemImage = content(items[((row * numberOfRows) + col)])
                            ZStack {
                                Image(((row % 2) + col) % 2 == 0 ? lightSquare : darkSquare)
                                    .padding(0)
                                    .frame(width: gridItemSize , height: gridItemSize)
                                    .clipped()
                                itemImage
                                    .padding(.bottom, 1)
                                    .frame(width: gridItemSize, height: gridItemSize)
                                    .clipped()
                                    .gesture(dragGesture)
                                    .offset(
                                        x: lastTranslation.width + translation.width,
                                        y: lastTranslation.height + translation.height
                                    )
                           }
                            .border(Color.black, width: 1)
                        }
                    }
                }
            }
            .frame(width: boardSize + 10, height: boardSize + 10 )
            .background(Color.black)
            .onTapGesture( perform: { location in
                let x = squareTapped(clickedAt: location, itemSize: gridItemSize, rowCount: numberOfRows)
                if x != nil { print(x!) }
            })
        }.aspectRatio(1, contentMode: .fit)
    }
    
    func maxBoardSize(boardSquaresSize: CGFloat, size: CGSize) -> CGFloat {
        let maxSquareSpace = (size.width <= size.height ? size.width : size.height)
        return (maxSquareSpace <= boardSquaresSize ? maxSquareSpace : boardSquaresSize)
    }
                
    func gridItemWidthThatFits(
        count: Int,
        size: CGSize
    ) -> CGFloat {
        let rowCount = CGFloat(count)
        let width = (size.width / rowCount)
        let height = (size.height / rowCount)
        return (width <= height ? width : height).rounded(.down)
    }
    
    var dragGesture: some Gesture {
        DragGesture()
            .onChanged { value in
                translation = value.translation
            }
            .onEnded { value in
                lastTranslation.width += value.translation.width
                lastTranslation.height += value.translation.height
                translation = .zero
            }
    }

    func squareTapped(clickedAt: CGPoint, itemSize: CGFloat, rowCount: Int) -> Int? {
        let col = Int((clickedAt.x / itemSize).rounded(.down))
        let row = Int((clickedAt.y / itemSize).rounded(.down))
        let sqr = col + (rowCount * row)
        let inBoundsSquare: Int? = (sqr > 0 && sqr <= (rowCount * rowCount) - 1 ) ? sqr : nil
        return inBoundsSquare
    }

}
swiftui drag-and-drop gesture
1个回答
0
投票

FixedSquareGrid
可以使用额外的
(Item) -> Void
函数,以便超级视图可以检测到某个项目被点击。

let items: [Item]
@ViewBuilder var content: (Item) -> ItemView
let itemTapped: (Item) -> Void // extra property here

init(
    _ items: [Item],
    lightSquare: String,
    darkSquare: String,
    @ViewBuilder content: @escaping (Item) -> ItemView,
    itemTapped: @escaping (Item) -> Void // extra argument here
) {
    self.items = items
    self.content = content
    self.itemTapped = itemTapped
    self.lightSquare = lightSquare
    self.darkSquare = darkSquare
}

然后你可以在

onTapGesture
中调用它:

.onTapGesture() { location in
    if let index = squareTapped(clickedAt: location, itemSize: gridItemSize, rowCount: numberOfRows) {
        itemTapped(items[index])
    }
}

用法看起来像这样:

struct ContentView: View {
    var body: some View {
        FixedSquareGrid(
            [ /* insert your items here */ ],
            lightSquare: "...", darkSquare: "..."
        ) { item in
            // ...
        } itemTapped: { item in
            // handle tap here...
        }
    }
}

还可以考虑将点击手势放在每个

ZStack
上,这样您就不必使用
squareTapped
方法来计算点击了哪个方块。


对于拖动手势,您只为所有网格项声明了一组状态(

translation
lastTranslation
),所以当然它们都会一起移动。

每个可拖动的东西都应该有自己的一对

translation
lastTranslation
状态。您可以创建一个包含这些状态的新
View
ViewModifier

// the way you implemented the drag gesture is kind of weird
// I changed it to an implementation similar to the one here:
// https://sarunw.com/posts/move-view-around-with-drag-gesture-in-swiftui/
struct DraggableModifier: ViewModifier {
    
    @State private var offset = CGSize.zero
    @GestureState private var startOffset: CGSize? = nil
    
    var drag: some Gesture {
        DragGesture()
            .onChanged { value in
                var newOffset = startOffset ?? offset
                newOffset.width += value.translation.width
                newOffset.height += value.translation.height
                self.offset = newOffset
            }.updating($startOffset) { (value, startOffset, transaction) in
                startOffset = startOffset ?? offset
            }
    }
    func body(content: Content) -> some View {
        content
            .offset(offset)
            .gesture(drag)
    }
}
itemImage
    .padding(.bottom, 1)
    .frame(width: gridItemSize, height: gridItemSize)
    .clipped()
    .modifier(DraggableModifier())
© www.soinside.com 2019 - 2024. All rights reserved.