[英]How do I get a SwiftUI view to appear in front of other views while it's being dragged?
我正在用新的和更完整的代码更新这个问题,以展示我是如何尝试实施@HunterLion 下面的答案中的建议的。 这是问题的原始陈述:
我正在使用 SwiftUI 实现一个版本的 Pentominos。当我将一块(视图)拖到板上时,我希望它在被拖动时出现在其他块(视图)的前面,但它出现在其他渲染的块后面稍后在布局中。 当我拖动第一块(U)时,它会拖到其他块和棋盘后面:
放下时,棋子会根据需要将自己定位在前面:
根据@HunterLion 的建议,我尝试使用@Published
变量来设置GameView
中的zIndex
来实现这一点,但它仍然不起作用。
关于下面的代码,我还没有尝试创建一个最小的可重现示例——不确定这是否可能,所以这段代码不完整且不可执行,但我认为它充分显示了结构和关系。
GameView
布置了包含HomeView
和棋盘(图像和BoardView
)的游戏空间。 每个HomeView
都包含一个PieceView
,它在各自的起始位置显示各个棋子。 当PieceView
被拖放到板上时,它会在BoardView
中重新绘制(未显示)。
Pieces
class 包含一个 pieces 字典,这是我放置@Published var somethingsBeingDragged: Bool = false
的地方。 somethingsBeingDragged
在PieceView
中被设置为确定拖拽到棋盘上的点(与PieceView
中较短的拖拽相反,该拖拽指示棋子的水平或垂直翻转)。
// GameView places the pieces and the board in the game space.
//
struct GameView: View {
var dropTarget = Target()
var map = Map(rows: constants.boardRows, cols: constants.boardCols)
@ObservedObject var homes: Homes
@ObservedObject var pieces: Pieces
var body: some View {
HStack
{
VStack {
homes.home["U"].modifier(smallPieceFrame())
homes.home["W"].modifier(smallPieceFrame())
homes.home["X"].modifier(smallPieceFrame())
homes.home["Y"].modifier(bigPieceFrame())
homes.home["I"].modifier(bigPieceFrame())
}
VStack {
homes.home["Z"].modifier(smallPieceFrame())
ZStack {
Image("board")
BoardView(rows: constants.boardRows, cols: constants.boardCols)
}
.zIndex(pieces.somethingsBeingDragged ? -1 : 1)
homes.home["V"].modifier(bigPieceFrame())
}
VStack {
homes.home["F"].modifier(smallPieceFrame())
homes.home["P"].modifier(smallPieceFrame())
homes.home["T"].modifier(smallPieceFrame())
homes.home["L"].modifier(bigPieceFrame())
homes.home["N"].modifier(bigPieceFrame())
}
}
...
----------------------------
// HomeView is the starting location of each piece, the location
// to which it returns if dropped illegally or removed from the board,
// and the location of the anchor image that remains after a
// piece is placed on the board.
//
struct HomeView: View {
var id: String // piece being displayed
var onBoard: Bool
@EnvironmentObject var pieces: Pieces
var body: some View {
ZStack {
PieceView(id: id, orientation: 8) // 8 => anchor image
if !onBoard {
PieceView(id: id, orientation: pieces.piece[id]!.orientation)
}
}
}
}
----------------------------
// PieceView tracks the individual game pieces, enables their
// reorientation by rotation (right and left) and reflection
// (horizontal and vertical) by gestures, enables their placement
// on the board by dragging.
//
struct PieceView: View {
var id: String // Identifies the piece
@State var dragOffset = CGSize.zero // Offset of piece while dragging
@State var dragging = false // T => piece is being dragged
@State var orientation: Int // orientation of image
@EnvironmentObject var dropTarget: Target
@EnvironmentObject var map: Map
@EnvironmentObject var pieces: Pieces
...
var body: some View {
Image(id + "\(orientation)")
.padding(0)
// .border(Color.gray)
.gesture(tapSingle)
.highPriorityGesture(tapDouble)
.offset(dragOffset)
.gesture(
DragGesture(coordinateSpace: .named("gameSpace"))
.onChanged { gesture in
dragging = false
pieces.somethingsBeingDragged = false
// Currently checking for drag by distance, but intend to change this.
//
if abs(Int(gesture.translation.width)) > Int(constants.dragTolerance) ||
abs(Int(gesture.translation.height)) > Int(constants.dragTolerance) {
dragOffset = gesture.translation
dragging = true
pieces.somethingsBeingDragged = true
}
}
.onEnded { gesture in
if dragging {
if onBoard(location: gesture.location) {
// piece has been legally dropped on board
//
dropTarget.pieceId = id
orientation = pieces.piece[id]!.orientation
} else {
// piece was dropped but not in a legal position, so goes home
//
dragOffset = CGSize(width: 0.0, height: 0.0)
}
} else {
// If not dragging, check for reflection.
//
...
}
}
}
)
.zIndex(dragging ? 1 : 0)
}
----------------------------
// Piece contains the state information about each piece: its size (in squares)
// and its current orientation.
//
class Piece: ObservableObject {
var orientation: Int = 0
let size: Int
init(size: Int) {
self.size = size
}
}
// Pieces contains the dictionary of Pieces.
//
class Pieces: ObservableObject {
@Published var somethingsBeingDragged: Bool = false
var piece: [String: Piece] = [:]
init() {
for name in smallPieceNames {
piece[name] = Piece(size: constants.smallPieceSquares)
}
for name in bigPieceNames {
piece[name] = Piece(size: constants.bigPieceSquares)
}
}
}
我会很感激这方面的任何帮助。
PS @HunterLion,为了回答您的“顺便说一句”评论,我在if
语句中将dragging
设置为true
,因为只有某个最小距离的拖动才被解释为向游戏板移动。 较短的拖动被解释为垂直或水平翻转一块。 我打算改变不同阻力的识别方式,但目前仅此而已。
我有几乎完全相同的代码,它与.zIndex()
完美配合(我假设dragging
是您视图中的@State
变量)。
但这还不够:您需要在拖动棋子时将棋盘移至背景。
因此,解决方案是在您的视图 model 中添加一个@Published
变量,该变量与(或代替) dragging
一起更改。 如果我们调用该变量isSomethingBeingDragged
,您可以向面板添加另一个.zIndex()
,如下所示:
ZStack {
Image("board")
BoardView(rows: constants.boardRows, cols: constants.boardCols)
}
.zIndex(viewModel.isSomethingBeingDragged ? -1 : 1)
如果您愿意,也可以在两个视图之间使用@Binding
而不是视图 model 中的变量。
顺便说一句:你为什么不直接将dragging = true
移出if{}
条件? 它应该是.onChanged
中的第一行。
编辑
更改问题后,我在下面创建了最小的可重现示例。
它在您的情况下不起作用,因为这些片段仍然嵌入在它们的VStack
中:虽然片段的.zIndex()
为 1,但 VStack 的VStack
.zIndex()
仍然为 0。因此,片段位于最前面在栈里面,但是栈还在后面。
我刚刚添加了更多.zIndex()
修饰符,正如您从下面的代码中看到的那样,它起作用了:移动时绿色字母在前面,否则网格在前面。 缺点: VStack
的所有字母同时排在前面。
按原样尝试:
GameView
中,将.zIndex()
放在堆栈上:struct GameView: View {
@StateObject private var pieces = Pieces()
var body: some View {
HStack {
VStack {
PieceView(id: "A", orientation: 0, pieces: pieces)
PieceView(id: "B", orientation: 0, pieces: pieces)
PieceView(id: "C", orientation: 0, pieces: pieces)
}
.zIndex(pieces.somethingsBeingDragged ? 1 : 0)
VStack {
ZStack {
Image(systemName: "square.grid.3x3")
.font(.system(size: 200))
}
}
.zIndex(pieces.somethingsBeingDragged ? -1 : 1)
VStack {
PieceView(id: "X", orientation: 0, pieces: pieces)
PieceView(id: "Y", orientation: 0, pieces: pieces)
PieceView(id: "Z", orientation: 0, pieces: pieces)
}
.zIndex(pieces.somethingsBeingDragged ? 1 : 0)
}
}
}
PieceView
中,记得在手势结束时dragging
变量恢复为 false。struct PieceView: View {
var id: String // Identifies the piece
@State var dragOffset = CGSize.zero // Offset of piece while dragging
@State var dragging = false { // T => piece is being dragged
didSet {
pieces.somethingsBeingDragged = dragging
}
}
@State var orientation: Int // orientation of image
@ObservedObject var pieces: Pieces
var body: some View {
Text(id)
.font(.system(size: 100))
.fontWeight(.black)
.foregroundColor(.green)
.zIndex(dragging ? 1 : 0)
.padding(0)
.gesture(TapGesture())
.highPriorityGesture(TapGesture(count: 2))
.offset(dragOffset)
.gesture(
DragGesture(coordinateSpace: .named("gameSpace"))
.onChanged { gesture in
dragging = false
// Currently checking for drag by distance, but intend to change this.
//
if abs(Int(gesture.translation.width)) > Int(10) ||
abs(Int(gesture.translation.height)) > Int(10) {
dragOffset = gesture.translation
dragging = true
}
}
.onEnded { gesture in
if dragging {
if gesture.location.y < 300.0 {
// piece has been legally dropped on board
//
orientation = pieces.piece[id]!.orientation
} else {
// piece was dropped but not in a legal position, so goes home
//
dragOffset = CGSize(width: 0.0, height: 0.0)
}
}
// On ended: bring the variables back to false
dragging = false
}
)
}
}
struct Piece {
var orientation: Int = 0
let size: Int
init(size: Int) {
self.size = size
}
}
class Pieces: ObservableObject {
@Published var somethingsBeingDragged: Bool = false
var piece: [String: Piece] = [:]
let smallPieceNames = ["A", "B", "C"]
let bigPieceNames = ["X", "Y", "Z"]
init() {
for name in smallPieceNames {
piece[name] = Piece(size: 20)
}
for name in bigPieceNames {
piece[name] = Piece(size: 20)
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.