[英]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.