簡體   English   中英

如何讓 SwiftUI 視圖在被拖動時出現在其他視圖前面?

[英]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的地方。 somethingsBeingDraggedPieceView中被設置為確定拖拽到棋盤上的點(與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所有字母同時排在前面。

按原樣嘗試:

  1. 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)
        }
    }
}
  1. 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
                    }
                
            )
    }
}
  1. 以下存根是最小可重現示例的一部分,以使其正常工作。
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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM