SwiftUI案例分析《DragGesture手势》
SwiftUI案例分析《DragGesture手势》

SwiftUI案例分析《DragGesture手势》

案例分析

本文主要讲解一个DragGesture手势的代码样例,了解DragGesture是如何运作的。

示例代码

struct ContentView: View {
    @State private var offset = CGSize.zero // 偏移量

    var body: some View {
        Circle()
            .fill(Color.blue)
            .frame(width: 100, height: 100)
            .offset(offset)
            .gesture(
                DragGesture()
                    .onChanged { value in
                        // 限制偏移量在 -100 到 100 之间
                        offset = CGSize(
                            width: min(max(value.translation.width, -100), 100),
                            height: min(max(value.translation.height, -100), 100)
                        )
                    }
                    .onEnded { _ in
                        // 松手后复位
                        withAnimation {
                            offset = .zero
                        }
                    }
            )
    }
}

这段代码的效果为:使用DragGesture手势拖动圆圈,会被限制到200 * 200的区间,当松手时,会恢复原位。

代码分析

1、@State变量

@State private var offset = CGSize.zero // 偏移量

定义一个 @State 状态变量 offset,表示视图的偏移量。

CGSize是一个结构体:

struct CGSize {
    var width: CGFloat
    var height: CGFloat
}

默认值是 .zero,表示初始位置没有偏移,等价于 CGSize(width: 0, height: 0)。

2、创建Circle()

Circle()
    .fill(Color.blue)
    .frame(width: 100, height: 100)

创建一个蓝色的圆形,大小为 100×100。

3、offset(offset)

Circle()
    .offset(offset)

设置视图的偏移量。

offset 是一个 CGSize 类型,包含 width 和 height 两个值,分别表示视图在水平方向(X 轴)和竖直方向(Y 轴)的偏移量。

当@State变量offset的width和height变更时,Circle的位置对应的会发生偏移。

4、DragGesture手势和回调方法

.gesture(
    DragGesture()
        .onChanged { value in
            // 限制偏移量在 -100 到 100 之间
            offset = CGSize(
                width: min(max(value.translation.width, -100), 100),
                height: min(max(value.translation.height, -100), 100)
            )
        }
        .onEnded { _ in
            // 松手后复位
            withAnimation {
                offset = .zero
            }
        }
)

这段代码定义了一个DragGesture拖动手势

onChanged 回调
.onChanged { value in
    offset = CGSize(
        width: min(max(value.translation.width, -100), 100),
        height: min(max(value.translation.height, -100), 100)
    )
}

1、触发时机

当用户拖动视图时,onChanged 会被连续调用,并实时更新拖动的偏移量。

2、参数:value

value.translation 是一个 CGSize,表示从手势起始点到当前位置的偏移量。

3、逻辑解析

value.translation.width 和 value.translation.height 表示拖动手势的水平和垂直偏移量。

偏移量的正负取决于拖动方向,与屏幕的坐标系一致:

水平轴(X 轴)

向右为正,向左为负。

垂直轴(Y 轴)

向下为正,向上为负。

通过 min(max(…)) 对偏移量进行限制,确保它不会超出 [-100, 100] 的范围

max(value.translation.width, -100):限制最小值为 -100。

min(…, 100):限制最大值为 100。

offset 会被实时更新,从而控制视图的偏移位置。

当将圆圈向左拖动时,width和height会自动计算:

width: min(max(value.translation.width, -100), 100),
height: min(max(value.translation.height, -100), 100)

当value.translation.width为-200时:

max(value.translation.width, -100)

这里的max会计算最大值,因为-100大于-200,因此,这里返回的是-100。

min(max(value.translation.width, -100), 100)

上面的max返回-100,外层的min计算的是最小值,-100比100小,所以最后的width就是-100。

因此,圆圈会被限制在-100区间内,当手势向左超过-100时,width经过min(max( _, _))的计算后,得到的是-100。

.onChanged { value in
    offset = CGSize(
        width: -100,    // 伪代码
    )
}

因此,onChanged会将offset的width设置为-100,即使手势向左到-200、-300,但是offset的width只会在-100以内,height也同理。

onEnded 回调
.onEnded { _ in
    // 松手后复位
    withAnimation {
        offset = .zero
    }
}

触发时机

当用户停止拖动(手势结束)时调用。

逻辑解析

使用 withAnimation 包裹,将 offset 的值重置为 .zero。

这会通过动画效果使视图平滑地回到初始位置。

当松开手势时,offset会被设置为.zero,表示视图的初始位置。

.zero 的含义是指视图的初始位置,即相对于视图布局的 (0, 0) 坐标位置。

在 SwiftUI 中,视图的 .offset 修改器是以其初始位置为参考点的。所以:

offset = .zero 将会把视图的偏移量复位到视图的初始位置(即未偏移时的位置)。

.zero 与手势的开始位置(DragGesture 的起点)无关,而是绝对的初始偏移量。

进阶代码

struct ContentView: View {
    @State private var position = CGSize.zero
    @State private var dragOffset = CGSize.zero

    var body: some View {
        Circle()
            .fill(Color.blue)
            .frame(width: 100, height: 100)
            .offset(x: position.width + dragOffset.width, y: position.height + dragOffset.height)
            .gesture(
                DragGesture()
                    .onChanged { value in
                        dragOffset = value.translation // 实时更新
                    }
                    .onEnded { value in
                        position.width += value.translation.width
                        position.height += value.translation.height
                        dragOffset = .zero
                    }
            )
    }
}

这段代码与前面的代码相比,圆形会随着手指拖动,并在释放手指后保持最终位置。

这里主要在于两个变量的计算:

@State private var position = CGSize.zero
@State private var dragOffset = CGSize.zero

其中position表示圆圈的定位,dragOffset表示偏移的位置,每次拖动时,都将圆圈的偏移位置加到postion上,这样,圆圈就会保留在偏移后的位置。

关键代码

Circle()
    .offset(x: position.width + dragOffset.width, y: position.height + dragOffset.height)
        .gesture(
            DragGesture()
                .onChanged { value in
                    dragOffset = value.translation // 实时更新
                }
                .onEnded { value in
                    position.width += value.translation.width
                    position.height += value.translation.height
                    dragOffset = .zero
                }
        )

圆圈的偏移量是通过positon和dragOffset相加计算的:

.offset(x: position.width + dragOffset.width, y: position.height + dragOffset.height)

DragGesture使用onChanged和onEnded两个回调方法:

DragGesture()
    .onChanged { value in
        dragOffset = value.translation // 实时更新
    }
    .onEnded { value in
        position.width += value.translation.width
        position.height += value.translation.height
        dragOffset = .zero
    }

当移动圆圈时,dragOffset被赋值为手势移动的偏移量,当向左上角移动时,假设移动的偏移量为(-100,100)。

这是dragOffset就是偏移量的值:

dragOffset = CGSize(width: -100, height: 100)

这也意味着,圆圈的位置会随着dragOffset的变化而变化。

假设在这个偏移量(-100,100)上松开手势,DragGesture的偏移量会被加到叠加到position上。

position.width += value.translation.width
position.height += value.translation.height

这时的position就从0变成了(-100,100),然后dragOffset为赋值为.zero,表示偏移量为0。

因为圆圈的位置是根据positon和dragOffset相加计算的,所以,当position为(-100,100),dragOffset为重置为0时,圆圈的位置就变成(-100,0)。

因此,圆圈就是在手势松开的位置停止。

可以总结为:position为圆圈的每次初始位置,dragOffset为每次的偏移量。第一次小球偏移了(-100,100),onChanged会实时计算偏移量的位置,并将偏移量赋值给dragOffset,这样小球就会跟着偏移量移动。

当松开小球时,onEnded会将偏移量叠加到position位置上,这样,小球的初始位置就从(0,0)变成了(-100,100),而dragOffset偏移量置为0,以便小球进行下一次的偏移计算。

再延伸一点,小球当前的位置为(-100,100),当手势向右拖动(200,0)时。

position: (-100,100)
dragOffset:随手势变化(这里的dragOffset为200,0)

这时,小球的偏移量由positon和dragOffset相加计算:

.offset(x: position.width + dragOffset.width, y: position.height + dragOffset.height)

小球位置的伪代码可以写作

x:-100 + 200 = 100,y:100 + 0 = 100

因此当前的小球位置经过positon和dragOffset变成了(100,100)。

这里松开小球后,手势的偏移量(200,0)会被叠加到position上:

position.width += value.translation.width
position.height += value.translation.height
dragOffset = .zero

小球位置的position伪代码可以写作

x:-100 + 200 = 100,y:100 + 0 = 100

与前面的计算是一样的,然后dragOffset置为0。

.offset(x: position.width + dragOffset.width, y: position.height + dragOffset.height)

就变成了:

x: 100 + 0, y: 100 + 0

最后小球的位置就变成了100,100,停止在手势松开的位置上。

相关文章

1、Swift二维空间尺寸CGSizehttps://fangjunyu.com/2024/12/12/swift%e4%ba%8c%e7%bb%b4%e7%a9%ba%e9%97%b4%e5%b0%ba%e5%af%b8cgsize/

2、SwiftUI偏移修饰符offsethttps://fangjunyu.com/2024/12/13/swiftui%e5%81%8f%e7%a7%bb%e4%bf%ae%e9%a5%b0%e7%ac%a6offset/

3、SwiftUI拖动操作DragGesturehttps://fangjunyu.com/2024/12/13/swiftui%e6%8b%96%e5%8a%a8%e6%93%8d%e4%bd%9cdraggesture/

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注