繁体   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