SwiftUI 在决定最终位置的视图上同时使用 .offset 和 .position 修饰符时究竟会发生什么?

SwiftUI What exactly happens when use .offset and .position Modifier simultaneously on a View, which decides the final location?

当我尝试给View一个初始位置时,我有这个问题,然后用户可以使用拖动手势将View的位置更改到任何地方。虽然我已经通过仅在视图上使用 .position(x:y:) 解决了这个问题,但一开始我在考虑使用 .position(x:y:) 来给出初始位置,并使用 .offset(offset:) 来同时使视图随手势移动。现在,我真的只想更详细地了解,当我同时使用它们(下面的代码)时到底发生了什么,所以我可以解释下面的视图中发生了什么。

我无法在下面的视图中解释的是:当我简单地在 VStack 框上拖动手势时,它按预期工作并且 VStack 随手指手势移动,但是,一旦手势结束并尝试开始新的在 VStack 上拖动手势,VStack 框突然回到原来的位置(就像代码加载时跳转到原来的位置),然后随着手势开始移动。请注意,手势像常规手势一样移动,但 VStack 已经跳到不同的位置,因此它从不同的位置开始移动。这导致指尖不再位于 VStack 框的顶部,而是离开了一段距离,尽管 VStack 以与拖动手势相同的轨迹移动。

我的问题是:为什么 .position(x:y:) 修饰符似乎只在检测到的每个新拖动手势的最开始时生效,但在拖动手势动作期间似乎 .offset(offset:) 支配主要运动VStack 停在它被拖到的地方。但是一旦打开新的拖动手势,VStack 就会突然跳到原来的位置。我只是无法理解这种行为是如何通过时间线发生的。有人可以提供一些见解吗?

请注意,我已经解决了这个问题来实现我所需要的,现在只是为了了解当 .position(x:y:).offset(offset:) 同时使用时到底发生了什么,所以请避免一些建议,例如。不要同时使用它们,谢谢。下面的代码假设在复制和粘贴后可以运行,如果不是,请原谅我的错误,因为我删除了几行以使其更清晰地重现问题。

import SwiftUI

struct ContentView: View {

    var body: some View {

        ButtonsViewOffset()
    }
}

struct ButtonsViewOffset: View {

    let location: CGPoint = CGPoint(x: 50, y: 50)
    @State private var offset = CGSize.zero
    @State private var color = Color.purple

    var dragGesture: some Gesture {
        DragGesture()
            .onChanged{ value in
                self.offset = value.translation
                print("offset onChange: \(offset)")
            }
            .onEnded{ _ in
                if self.color == Color.purple{
                    self.color = Color.blue
                }
                else{
                    self.color = Color.purple
                }

            }
    }
    
    var body: some View {
        VStack {
            Text("Watch 3-1")
            Text("x: \(self.location.x), y: \(self.location.y)")
        }
        .background(Color.gray)
        
        .foregroundColor(self.color)
        .offset(self.offset)
        .position(x: self.location.x, y: self.location.y)
        .gesture(dragGesture)
        
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            ContentView()
        }
            
    }
}

您的问题与position和offset的使用无关。他们实际上同时工作。 Position 设置视图的绝对位置,其中 offset 相对于绝对位置移动它。因此,您会注意到您的视图从屏幕上的位置 (50, 50) 开始,然后您可以将它拖动到各处。一旦放手,它就停在原处。到目前为止,一切都很好。然后你想再次移动它,它会弹回原来的位置。这样做的原因是您将 location 设置为 let 常量的方式。它需要状态。

问题源于您在没有意识到的情况下将偏移值添加到位置。完成拖动后,offset 会保留最后的值。但是,当您开始下一次拖动时,这些值再次从 (0,0) 开始,因此偏移量将重置为 (0,0) 并且视图将移回原始位置。关键是你需要只使用位置或更新 .onEnded 中的偏移量。不要同时使用两者。这里你有一个设定的位置,并没有保存偏移量。您如何处理它取决于您移动视图的目的。

首先,使用.position():

struct OffsetAndPositionView: View {
    
    @State private var position = CGPoint(x: 50, y: 50)
    @State private var color = Color.purple
    
    var dragGesture: some Gesture {
        DragGesture()
            .onChanged{ value in
                position = value.location
                print("position onChange: \(position)")
            }
            .onEnded{ value in
                if color == Color.purple{
                    color = Color.blue
                }
                else{
                    color = Color.purple
                }
            }
    }
    
    var body: some View {
        Rectangle()
            .fill(color)
            .frame(width: 30, height: 30)
            .position(position)
            .gesture(dragGesture)
    }
}

其次,直接使用.offset():

struct ButtonsViewOffset: View {
    @State private var savedOffset = CGSize.zero
    @State private var dragValue = CGSize.zero
    @State private var color = Color.purple
    
    var offset: CGSize {
        savedOffset + dragValue
    }

    var dragGesture: some Gesture {
        DragGesture()
            .onChanged{ value in
                dragValue = value.translation
                print("dragValue onChange: \(dragValue)")
            }
            .onEnded{ value in
                savedOffset = savedOffset + value.translation
                dragValue = CGSize.zero
                if color == Color.purple{
                    color = Color.blue
                }
                else{
                    color = Color.purple
                }
            }
    }

    var body: some View {
            Rectangle()
                .fill(color)
                .frame(width: 30, height: 30)
                .offset(offset)
                .gesture(dragGesture)
    }
}

// Convenience operator overload
func + (lhs: CGSize, rhs: CGSize) -> CGSize {
    return CGSize(width: lhs.width + rhs.width, height: lhs.height + rhs.height)
}