SwiftUI极简教程17:Gestures手势的使用

简介: SwiftUI极简教程17:Gestures手势的使用

在本章中,你将学会如何使用Gestures手势构建基本的交互动作。

在我们常见的业务中会用到很多的交互手势,例如:点击按钮打开页面、长按弹出窗口、拖拽移动等等。这些交互手势结合SwiftUI的动画效果,也就呈现出了iOS应用独具匠心的交互动画。


本章节将分成4个部分讲解。


1、onTapGesture点击手势

2、LongPressGesture长按手势

3、DragGesture拖拽手势

4、多种手势组合使用


那我们开始吧。


第一部分:onTapGesture点击手势


首先创建一个新项目,命名为SwiftUIGestures


image.png

我们尝试完成下下面的UI稿。

image.png 

首先,我们在ContentView.swift中完成基础样式的绘制。

代码如下:

//绘制
ZStack {
    Circle()
        .frame(width: 200, height: 200)
        .foregroundColor(.red)
    Image(systemName: "heart.fill")
        .foregroundColor(.white)
        .font(.system(size: 80))
    }


image.png

我们来分析下UI稿,我们看到这个交互有几种状态:点击时,背景颜色变成灰色,这是第一种状态;点击时,里面的心形变成红色,这是第二种状态;点击时,心形变大了1倍,这是第三种状态;

首先,我们需要定义三种状态,且它们的初始状态都是false,那么点击的时候,三个状态由false转变成true


//定义状态
@State private var circleColorChanged = false
@State private var heartColorChanged = false
@State private var heartSizeChanged = false

接下来,当我们点击的时候,三种状态切换的同时,UI稿会对应切换到第二个效果。我们可以看到我们做了4步:


1、circleColorChanged背景颜色变化时,背景颜色会在灰色、红色之间切换;


.foregroundColor(circleColorChanged ? Color(.systemGray5) : .red)


2、heartColorChanged心形颜色变化时,背景颜色会在灰色、红色之间切换;


.foregroundColor(heartColorChanged ? .red : .white)


3、heartSizeChanged心形大小变化时,大小在初始的1倍变成0.5倍;


.scaleEffect(heartSizeChanged ? 1.0 : 0.5)
复制代码


4、点击这个ZStack整个视图时,三个状态同时切换。


//点击手势
.onTapGesture {
    self.circleColorChanged.toggle()
    self.heartColorChanged.toggle()
    self.heartSizeChanged.toggle()
}
复制代码


好了,我们画完了,预览运行下看下效果:


image.png

至此,我们完成了基础的.onTapGesture点击手势的学习。

科普一个知识点。

如果我们需要实现更加复杂的手势,我们需要引用.gesture修饰符,在.gesture修饰符下我们需要实现.onTapGesture点击手势,就只需要使用TapGesture()方法。


//手势modifier
.gesture(
    //点击手势
    TapGesture()
        .onEnded({
            self.circleColorChanged.toggle()
            self.heartColorChanged.toggle()
            self.heartSizeChanged.toggle()
        })
)


我们给视图启用了.gesture修饰符手势,里面是一个TapGesture()点击手势,当我们点击结束时 .onEnded({})时,三个状态进行切换。

运行下模拟器,我们可以看到点击的效果,点击效果和.onTapGesture点击手势效果一致。

image.png

第二部分:LongPressGesture长按手势


接下来,我们学习下LongPressGesture长按手势。

LongPressGesture长按手势比较好理解,也就是按住操作至少1秒才会识别手势操作。

使用的方式也是使用.gesture修饰符,在里面引用LongPressGesture长按手势。


//手势modifier
.gesture(
    //长按手势
    LongPressGesture(minimumDuration: 1.0)
        .onEnded({ _ in
            self.circleColorChanged.toggle()
            self.heartColorChanged.toggle()
            self.heartSizeChanged.toggle()
        })
)


image.png

运行模拟器,我们长按住图像视图,发现长按1秒钟后,视图就切换成第二个状态了。

但这不够完美,如果我们需要设置1秒钟以上的长按,比如长按2秒钟才唤起什么操作时,我们发现体验没那么好。这是因为UI没法体现出我们长按的动作。

这时候,我们需要引用一个新的知识点,叫做@GestureState属性包装器。和之前的章节学习的@State一样,它可以监测长按手势的状态,也就是点击的时候它能“知道”。

我们首先先定义一个longPressTap长按手势,初始值为false,当它被点击时,状态就切换。

@GestureState private var longPressTap = false

为了达到明显的演示效果,我们给UI稿中的心形加个透明度的效果,当我们长按时,即longPressTap被点击时,透明度变化。

.opacity(longPressTap ? 0.4 : 1.0)

再然后,我们需要在LongPressGesture长按手势内增加一个.updating更新方法,当视图被LongPressGesture长按时,调用这个方法。

//更新方法
.updating($longPressTap, body: { ( 
    currentState, state, transaction
    ) in 
    state = currentState
})

.updating更新方法有三个参数,value、statetransaction

value参数可以自定义,我们这里用的是手势的当前状态currentStatecurrentState当前状态表示检测到点击。

state参数实际上是一个in-out参数,它允许您更新longPressTap属性的值。

在上面的代码中,我们将state的值设置为currentState当前状态,也就是longPressTap属性需要一直跟踪长按手势的最新状态。

一句话概括就是:state参数存储currentState当前状态来处理updating更新的上下文。

运行下模拟器,我们可以看到我们点击视图的时候,心形透明度降低从而变暗淡了,保持了1秒钟,视图就切换到了第二个状态。


image.png

使用@GestureState的好处是,当手势结束时,它会自动将手势状态属性的值设置为初始值。

也就实现了我们只在LongPressGesture长按那1秒钟内,有一个交互效果,代表了用户正在长按这件事。


第三部分:DragGesture拖拽手势


经过上面学习的TapGesture点击手势、LongPressGesture长按手势,相信你对.gesture修饰符学习有了更深的了解。

接下来,我们再学习一个手势,DragGesture拖拽手势。


//手势modifier
.gesture(
    //拖拽手势
    DragGesture()
)


使用DragGesture拖拽手势前,我们也需要使用@GestureState属性包装器定义一个拖拽位置参数dragOffset,用来记录我们的拖拽前的初始位置CGSize.zero,也用来监听和更新UI。


@GestureState private var dragOffset = CGSize.zero


然后我们给整个ZSkcak视图加一个可以拖动的偏移位置。


//移动位置
.offset(x: dragOffset.width, y: dragOffset.height)


它的横轴x为我们定义的拖拽位置参数dragOffset的宽度,纵轴y为拖拽位置参数dragOffset的高度,也就是我们上下左右都可以拖动。

再然后和LongPressGesture长按手势一样,我们需要再DragGesture拖拽手势中添加更新方法。


//更新方法
.updating($dragOffset, body: { (
    currentPosition, state, transaction
    ) in
    state = currentPosition.translation
})


.updating更新方法有三个参数,value、statetransaction

value参数我们这里用的是手势的当前位置currentPositioncurrentPosition当前位置表示当前被拖动的位置。

然后也是state参数存储currentPosition当前位置来处理updating更新的上下文。

简单来说,就是拖动的时候,系统需要试试获取你拖动的位置来更新UI所展示的画面,你拖到那里,视图就移动到哪里。


image.png

科普一个知识点。

由于我们使用@GestureState属性包装器监听变化,因此拖动结束后,视图还会回到初始位置。

如果我们想要拖动之后,就把视图放在拖拽后的位置,还记得之前的章节么?没错,我们需要使用@State把拖动后的位置存储起来。

我们再定义一个位置:


@State private var position = CGSize.zero


然后拖拽视图的时候,x、y轴的位置需要再原先的基础上再加上我们position的位置。


//移动位置
.offset(x: position.width + dragOffset.width, y: position.height + dragOffset.height)


然后呢,我们在拖拽结束的时候,记录视图移动后的位置,这样就知道了拖动后视图应该放在哪里了。

而我们使用的@State属性包装器定义的position位置,也就记录并保存了我们的位置,这样就就不会回到初始位置了。


//拖拽结束后的位置
.onEnded({ (currentPosition) in
    self.position.height += currentPosition.translation.height
    self.position.width += currentPosition.translation.width
})


image.png

前方高能!

前方高能!

前方高能!

第四部分:多种手势组合使用


上面我们都只讨论了单个手势的使用方法,如果一个视图中有多个手势同时操作时,我们该怎么处理?

比方说,如果我们希望用户在开始拖拽之前按住图像,我们必须结合长按和拖拽手势。

SwiftUI提供了三种手势组合类型,包括同步的、顺序的和排他的。比如,当需要同时检测多个手势时,可以使用同步合成类型。而且SwiftUI可以识别指定的手势,但当检测到其中一种手势时,它会忽略其余的手势。当我们使用序列组合类型组合多个手势,SwiftUI将按照特定的顺序识别这些手势。

以下面的UI稿为例:

image.png


我们想要实现的组合交互是:初始状态不变,长按时切换状态,并且长按1秒后可以拖拽,拖拽后停留到拖拽后的位置,且切换样式状态。

相当于将第二部分、第三部分的内容结合一下。

首先,我们在.gesture修饰符先完成长按手势及其更新方法,同时长按的时候,切换UI样式状态。

// 长按手势
LongPressGesture(minimumDuration: 1.0)
    // 长按手势更新方法
    .updating($longPressTap, body: {
        (currentState, state, transaction) in
            state = currentState
            self.circleColorChanged.toggle()
            self.heartColorChanged.toggle()
            self.heartSizeChanged.toggle()
        })

然后,LongPressGesture长按手势后承接的是DragGesture拖拽手势,承接的手势组合顺序的用的修饰符是.sequenced序列。

.sequenced(before: DragGesture())

紧接着,实现拖拽手势的更新方法。

// 拖拽手势更新方法
.updating($dragOffset, body: {
    (currentPosition, state, transaction) in
        //顺序执行
        switch currentPosition {
            case .first(true):
                print("正在点击")
            case .second(true, let drag):
                state = drag?.translation ?? .zero
            default:
                break
            }
        })

这里我们用switch语句来区分手势,使用.first.second case来找出要处理的手势,首先识别的是LongPressGesture长按手势,再识别DragGesture拖拽手势。

因为LongPressGesture长按手势之前已经被触发了,所以这里就print打印信息供我们参考吧。

第二个被识别DragGesture拖拽手势,我们选取拖动数据并使用相应的转换更新dragOffset

识别完成后break退出switch区分判断。

最后,我们只需要当拖动结束时,调用onEnded函数更新样式就行了。

// 拖拽结束后的位置
.onEnded({ currentPosition in
    guard case .second(true, let drag?) = currentPosition else {
        return
    }
    self.position.height += drag.translation.height
    self.position.width += drag.translation.width
    self.circleColorChanged.toggle()
    self.heartColorChanged.toggle()
    self.heartSizeChanged.toggle()
})

运行下模拟器,我们尝试下完成的交互效果。

完整代码如下:

import SwiftUI
struct ContentView: View {
    // 定义状态
    @State private var circleColorChanged = false
    @State private var heartColorChanged = false
    @State private var heartSizeChanged = false
    @GestureState private var longPressTap = false
    @GestureState private var dragOffset = CGSize.zero
    @State private var position = CGSize.zero
    var body: some View {
        // 绘制
        ZStack {
            Circle()
                .frame(width: 200, height: 200)
                .foregroundColor(circleColorChanged ? Color(.systemGray5) : .red)
            Image(systemName: "heart.fill")
                .foregroundColor(heartColorChanged ? .red : .white)
                .font(.system(size: 80))
                .scaleEffect(heartSizeChanged ? 1.0 : 0.5)
        }
        // 移动位置
        .offset(x: position.width + dragOffset.width, y: position.height + dragOffset.height)
        // 手势modifier
        .gesture(
            // 长按手势
            LongPressGesture(minimumDuration: 1.0)
                // 长按手势更新方法
                .updating($longPressTap, body: {
                    currentState, state, _ in
                    state = currentState
                    self.circleColorChanged.toggle()
                    self.heartColorChanged.toggle()
                    self.heartSizeChanged.toggle()
                })
                // 拖拽手势
                .sequenced(before: DragGesture())
                // 拖拽手势更新方法
                .updating($dragOffset, body: {
                    currentPosition, state, _ in
                    // 顺序执行
                    switch currentPosition {
                    case .first(true):
                        print("正在点击")
                    case .second(true, let drag):
                        state = drag?.translation ?? .zero
                    default:
                        break
                    }
                })
                // 拖拽结束后的位置
                .onEnded({ currentPosition in
                    guard case .second(true, let drag?) = currentPosition else {
                        return
                    }
                    self.position.height += drag.translation.height
                    self.position.width += drag.translation.width
                    self.circleColorChanged.toggle()
                    self.heartColorChanged.toggle()
                    self.heartSizeChanged.toggle()
                })
        )
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


image.png

快来动手试试吧!

相关文章
|
7月前
|
存储 监控 算法
大师学SwiftUI第12章 - 手势 Part 1
手势是用户在屏幕上执行的动作,如点击、滑动或捏合。这些手势很难识别,因为屏幕上只能返回手指的位置。为此,Apple提供了手势识别器。手势识别器完成所有识别手势所需的计算。所以我们不用处理众多的事件和值,只需在等待系统监测到复杂手势时发送通知并进行相应处理即可。
81 0
|
7月前
|
存储 编解码 vr&ar
大师学SwiftUI第12章 - 手势 Part 2
放大手势常被称为捏合手势,因为常常在用户张开或捏合两个手指时进行识别。通常这个手势实现用于让用户放大或缩小图片。
71 0
|
程序员 索引
SwiftUI极简教程18:SwipeCard卡片滑动效果的使用(上)
SwiftUI极简教程18:SwipeCard卡片滑动效果的使用(上)
1082 0
SwiftUI极简教程18:SwipeCard卡片滑动效果的使用(上)
|
存储 索引
SwiftUI极简教程19:SwipeCard卡片滑动效果的使用(下)
SwiftUI极简教程19:SwipeCard卡片滑动效果的使用(下)
677 0
SwiftUI极简教程19:SwipeCard卡片滑动效果的使用(下)
|
存储 索引
SwiftUI极简教程42:使用MatchedGeometryEffect构建一个导航菜单
在本章中,你将学会使用MatchedGeometryEffect构建一个导航菜单。 在构建SwiftUI应用过程中,我们常常会使用TabView构建底部菜单,但更多的时候会由于我们定制化的需求,需要我们自己绘制底部菜单。 那么本章中,我们就来试试构建一个底部导航菜单。
463 0
SwiftUI极简教程42:使用MatchedGeometryEffect构建一个导航菜单
|
存储 Swift
SwiftUI极简教程38:ScrollViewReader滚动视图锚点的使用
在本章中,你将学会ScrollViewReader滚动视图锚点的使用。
841 0
SwiftUI极简教程38:ScrollViewReader滚动视图锚点的使用
|
UED iOS开发
SwiftUI极简教程14:ModalView模态弹窗使用
SwiftUI极简教程14:ModalView模态弹窗使用
1428 0
SwiftUI极简教程14:ModalView模态弹窗使用
SwiftUI极简教程07:ScrollView滚动视图的使用
SwiftUI极简教程07:ScrollView滚动视图的使用
1482 0
SwiftUI极简教程07:ScrollView滚动视图的使用
SwiftUI—如何使一个视图同时支持多种的手势
SwiftUI—如何使一个视图同时支持多种的手势
274 0
SwiftUI—如何快速打开一个模态窗口
SwiftUI—如何快速打开一个模态窗口
455 0
SwiftUI—如何快速打开一个模态窗口