我只是一个尝试学习 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
}
}
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())