跳到主要内容

24 篇博文 含有标签「iOS」

查看所有标签

iOS噪音计

· 阅读需 4 分钟
BY

最近在办公室觉得有点吵,然后忽然想做一个噪音计测试一下噪音,在App Store下载了几款测噪音软件,使用原来都大同小异。于是决定自己实现测噪音的原理。

分贝dB

首先要测量噪音,必须知道噪音的大小的参考的单位为分贝(dB),分贝的定义如下:

SPL = 20lg[p(e)/p(ref)]

p(e)为待测的有效声压,p(ref)为参考声压,一般取2*10E-5帕,这是人耳能分辨的最小声压(1KHz)。

就是说噪音每增加20dB,声压增强了10倍。

iOS测噪音原理

iOS设备测量噪音原理非常简单:调用系统麦克风,根据麦克风输入强度计算转化为对应的dB值。但是,实现的过程可是坑满满。

找到了一篇博客介绍iOS硬件的调用:iOS开发系列--音频播放、录音、视频播放、拍照、视频录制

iOS的AVFoundation框架中有一个AVAudioRecorder类专门处理录音操作,详见Apple文档

AVAudioRecorder.h中找到下列方法

- (void)updateMeters; /* call to refresh meter values */ 更新麦克风测量值
- (float)peakPowerForChannel:(NSUInteger)channelNumber; /* returns peak power in decibels for a given channel */ 获取峰值
- (float)averagePowerForChannel:(NSUInteger)channelNumber; /* returns average power in decibels for a given channel */ 获取平局值

- (float)averagePowerForChannel:(NSUInteger)channelNumber;文档中描述:

Return Value

The current average power, in decibels, for the sound being recorded. A return value of 0 dB indicates full scale, or maximum power; a return value of -160 dB indicates minimum power (that is, near silence).

If the signal provided to the audio recorder exceeds ±full scale, then the return value may exceed 0 (that is, it may enter the positive range).

Discussion

To obtain a current average power value, you must call the updateMeters method before calling this method.

也就是说获取的麦克风测量值返回值范围为 -160dB ~ 0dB,并且注意最后那句话返回值可能超过0。

转化公式

获取的的测量值为 -160 ~ 0dB ,如何转化为我们所要的噪音值呢?在网上找了很多资料都没有结果,于是就自己摸索转化公式。

刚开始想到的是利用分贝计算公式SPL = 20lg[p(e)/p(ref)]进行计算,后来直接放弃这个方案,因为这是一个对数运算,获取到的值非常稳定,几乎不会波动,与其他的测噪软件所得的分贝值出入太大。

然后发现有个App在麦克风没有输入时显示-55dB

于是思路就有了。

其他测噪音软件的量程均为0~110dB,而我们获取的的测量值为 -160 ~ 0dB,两者之间差了50dB,也就是说以麦克风的测量值的-160dB+50dB = -110dB作为起点,0dB作为Max值,恰好量程为0~110dB.

问题看似结束,但是直接以50dB作为补偿测量结果会偏大。最后选择了分段进行处理,代码如下


-(void)audioPowerChange{

[self.audioRecorder updateMeters];//更新测量值
float power = [self.audioRecorder averagePowerForChannel:0];// 均值
float powerMax = [self.audioRecorder peakPowerForChannel:0];// 峰值
NSLog(@"power = %f, powerMax = %f",power, powerMax);

CGFloat progress = (1.0 / 160.0) * (power + 160.0);

// 关键代码
power = power + 160 - 50;

int dB = 0;
if (power < 0.f) {
dB = 0;
} else if (power < 40.f) {
dB = (int)(power * 0.875);
} else if (power < 100.f) {
dB = (int)(power - 15);
} else if (power < 110.f) {
dB = (int)(power * 2.5 - 165);
} else {
dB = 110;
}

NSLog(@"progress = %f, dB = %d", progress, dB);
self.powerLabel.text = [NSString stringWithFormat:@"%ddB", dB];
[self.audioPowerProgress setProgress:progress];

}

效果

效果如下:

下载地址

Demo下载地址:Noise-meter-Demo

JSON转模型 For YYModel

· 阅读需 2 分钟
BY

JSON转模型是我们做iOS开发的基础技能,本文将通过YYModel这个框架安全快速的完成JSON到模型的转换,其中还会介绍到一款好用的插件ESJsonFormat

创建模型类我们可以通过ESJsonFormat这款插件快速完成。

使用方法:

将光标移动到代码行中 如下图的13行

然后点击Window->ESJsonFormat->Input JSON Window调出窗口

在窗口中输入你要解析的JSON文本,如下图:

Enter继续,然后神奇的一幕发生了

看到在.h中 所有的属性自动为你填上,而且帮你选好了类型

.m 也为你声明了list中成员的类型,不过这里需要稍作修改,因为我们需要用到YYModel进行解析,所以方法名改成modelContainerPropertyGenericClass

+ (NSDictionary *)modelContainerPropertyGenericClass {
return @{@"list" : [List class]};
}

还有问题就是属性中出现关键字id,我们需要将id改为teacherId

然后在.m的implementation中声明,将字典的的id

+ (NSDictionary *)modelCustomPropertyMapper {
return @{@"teacherId" : @"id"};
}

这样,模型的创建就完成了,剩下的就是用YYModel进行解析了

2、使用YYModel进行解析

解析很简单,就只需要一句话

// 将 JSON (NSData,NSString,NSDictionary) 转换为 Model:
Model *model = [Model yy_modelWithJSON:json];

// 或者
Model *model = [[Model alloc] init];
[model yy_modelSetWithDictionary:json];

到此,简便快速的完成了JSON到模型的转换。

最后,这里附上一篇YYModel的使用

Xcode Debug 大全

· 阅读需 7 分钟
BY

BUG,简单来说就是程序运行结果与预期的不同,下面来说说Xcode中的DEBUG方法

参考博文

断点调试

  • 普通断点
  • 全局断点
  • 条件断点

1.普通断点

看图

当程序运行到断点处时会停下,然后进行单步调试

2.全局断点

当程序运行出现崩溃时,就会自动断点到出现crash的代码行

3.条件断点

我们如果在一个循环里面使用了断点,如果这个循环执行了100万次,那你的断点要执行那么多次,你不觉得蛋蛋都凉了的忧伤么?所以我们这么做:

编辑断点

添加条件Condition

还可以Action中在条件断点触发时执行事件

如:输出信息

4.方法断点

打印调试(NSLog)

尽管ARC已经让内存管理变得简单、省时和高效,但是在object的life-cycles中跟踪一些重要事件依然十分重要。毕竟ARC并没有完全排除内存泄露的可能性,或者试图访问一个被release的对象。

  • NSLog

强化NSLog

//A better version of NSLog
#define NSLog(format, ...) do { \
fprintf(stderr, "<%s : %d> %s\n", \
[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], \
__LINE__, __func__); \
(NSLog)((format), ##__VA_ARGS__); \
fprintf(stderr, "-------\n"); \
} while (0)

控制台输出

<ViewController.m : 32> -[ViewController viewDidLoad]
2016-10-14 17:33:31.022 DEUBG[12852:1238167] Hello World!
-------

利用NSString输出多种类型

  • 开启僵尸对象

Xcode可以把那些已经release掉得对象,变成“僵尸”,当我们访问一个Zombie对象时,Xcode可以告诉我们正在访问的对象是一个不应该存在的对象了。因为Xcode知道这个对象是什么,所以可以让我们知道这个对象在哪里,以及这是什么时候发生的。 所以Zombies是你的好基友!他可以让你输出的信息更具体!

具体这样做:(僵尸只能用在模拟器和OC语言)

控制台(lldb 命令)

LLDB 是一个有着 REPL 的特性和 C++ ,Python 插件的开源调试器。LLDB 绑定在 Xcode 内部,存在于主窗口底部的控制台中。调试器允许你在程序运行的特定时暂停它,你可以查看变量的值,执行自定的指令,并且按照你所认为合适的步骤来操作程序的进展。(这里有一个关于调试器如何工作的总体的解释。)

你以前有可能已经使用过调试器,即使只是在 Xcode 的界面上加一些断点。但是通过一些小的技巧,你就可以做一些非常酷的事情。GDB to LLDB 参考是一个非常好的调试器可用命令的总览。你也可以安装 Chisel,它是一个开源的 LLDB 插件合辑,这会使调试变得更加有趣。

参考:

与调试器共舞 - LLDB 的华尔兹

LLDB调试命令初探

About LLDB and Xcode

The LLDB Debugger

基础

help

在控制台输入help,显示控制台支持的lldb命令

print

打印值

缩写p

print是 expression -- 的缩写

printk可以指定格式打印 如 默认 p

十六进制 p/x

二进制 p/t

(lldb) p 16
16

(lldb) p/x 16
0x10

(lldb) p/t 16
0b00000000000000000000000000010000

(lldb) p/t (char)16
0b00010000

你也可以使用 p/c 打印字符,或者 p/s 打印以空终止的字符串 p/d打印ACRSII(译者注:以 '\0' 结尾的字符串)。

完整清单点击查看

po

打印对象,是 e -o --的缩写

expression

流程控制

当你通过 Xcode 的源码编辑器的侧边槽 (或者通过下面的方法) 插入一个断点,程序到达断点时会就会停止运行。

调试条上会出现四个你可以用来控制程序的执行流程的按钮。

从左到右,四个按钮分别是:continue,step over,step into,step out。

第一个,continue 按钮,会取消程序的暂停,允许程序正常执行 (要么一直执行下去,要么到达下一个断点)。在 LLDB 中,你可以使用 process continue 命令来达到同样的效果,它的别名为 continue,或者也可以缩写为 c。

第二个,step over 按钮,会以黑盒的方式执行一行代码。如果所在这行代码是一个函数调用,那么就不会跳进这个函数,而是会执行这个函数,然后继续。LLDB 则可以使用 thread step-over,next,或者 n 命令。

如果你确实想跳进一个函数调用来调试或者检查程序的执行情况,那就用第三个按钮,step in,或者在LLDB中使用 thread step in,step,或者 s 命令。注意,当前行不是函数调用时,next 和 step 效果是一样的。

大多数人知道 c,n 和 s,但是其实还有第四个按钮,step out。如果你曾经不小心跳进一个函数,但实际上你想跳过它,常见的反应是重复的运行 n 直到函数返回。其实这种情况,step out 按钮是你的救世主。它会继续执行到下一个返回语句 (直到一个堆栈帧结束) 然后再次停止。

frame info

会告诉你当前的行数和源码文件

(lldb) frame info
frame #0: 0x000000010a53bcd4 DebuggerDance`main + 68 at main.m:17

Thread Return

调试时,还有一个很棒的函数可以用来控制程序流程:thread return 。它有一个可选参数,在执行时它会把可选参数加载进返回寄存器里,然后立刻执行返回命令,跳出当前栈帧。这意味这函数剩余的部分不会被执行。这会给 ARC 的引用计数造成一些问题,或者会使函数内的清理部分失效。但是在函数的开头执行这个命令,是个非常好的隔离这个函数,伪造返回值的方式 。

(lldb) thread return NO

不用断点调试

在程序运行时,点击暂停按钮,即可进入调试状态,能对全局变量做操作

工具调试(instruments)

instruments Xcode自带许多工具供大家使用,打开方式如下图:

leaks内存泄漏检查工具

运行后查看

视图调试

启用视图调试:运行app过程中,按下底部的Debug View Hierarchy 按钮,或者从菜单中选择Debug > View Debugging > Capture View Hierarchy 来启动视图调试。

启动视图调试后,Xcode会对应用程序的视图层次拍一个快照并展示三维原型视图来探究用户界面的层级。该三维视图除了展示app的视图层次外,还展示每个视图的位置、顺序和视图尺寸,以及视图间的交互方式。

模拟器调试

编译并运行应用程序,选中模拟器,从 Debug菜单中选择Color Blended Layers选项。

然后会看到app的用户界面被红色和绿色覆盖,显示了哪些图层可以被叠加覆盖,以及哪些图层是透明的。混合层属于计算密集型视图,所以推荐尽可能地使用不透明的图层。

结语

目前所知道的调试方法大概就是上面这几种了,若有什么有趣的方法,请和我分享哈!

iOS手势与变形

· 阅读需 9 分钟
BY

手势在用户交互中有着举足轻重的作用,这篇文字简单的介绍了iOS中的手势,并通过手势对控件进行变形处理。

iOS手势分为下面这几种:

  • UITapGestureRecognizer(点按)
  • UIPanGestureRecognizer(拖动)
  • UIScreenEdgePanGestureRecognizer (边缘拖动)
  • UIPinchGestureRecognizer(捏合)
  • UIRotationGestureRecognizer(旋转)
  • UILongPressGestureRecognizer(长按)
  • ​UISwipeGestureRecognizer(轻扫)

这些手势大都继承于UIGestureRecognizer类,(UIScreenEdgePanGestureRecognizer继承于UIPanGestureRecognizer类),

需要说明的是这些手势只有一个是离散型手势,那就是UITapGestureRecognizer,一旦识别就无法取消,而且只会调用一次手势操作事件。

换句话说其他手势是连续型手势,而连续型手势的特点就是:会多次调用手势操作事件,而且在连续手势识别后可以取消手势。

从下图可以看出两者调用操作事件的次数是不同的:

这些手势类有着以下共同的方法:

创建方法:

- (instancetype)initWithTarget:(nullable id)target action:(nullable SEL)action;

移除方法:

- (void)removeTarget:(nullable id)target action:(nullable SEL)action;

添加事件:

- (void)addTarget:(id)target action:(SEL)action;

还有下面这些属性等:

@property(nonatomic,readonly) UIGestureRecognizerState state;// 手势状态

typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
UIGestureRecognizerStatePossible, // 尚未识别是何种手势操作(但可能已经触发了触摸事件),默认状态
UIGestureRecognizerStateBegan, // 手势已经开始,此时已经被识别,但是这个过程中可能发生变化,手势操作尚未完成
UIGestureRecognizerStateChanged, // 手势状态发生转变
UIGestureRecognizerStateEnded, // 手势识别操作完成(此时已经松开手指)
UIGestureRecognizerStateCancelled, // 手势被取消,恢复到默认状态
UIGestureRecognizerStateFailed, // 手势识别失败,恢复到默认状态
UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded // 手势识别完成,同UIGestureRecognizerStateEnded
};

@property(nullable,nonatomic,weak) id <UIGestureRecognizerDelegate> delegate; // 代理

@property(nonatomic, getter=isEnabled) BOOL enabled;

当然我们也可以自定义手势来实现特殊的需求,关于自定义手势可以看这篇博客.

接下来我们来看看这些常用手势的用法.

UITapGestureRecognizer(点按)

Tap手势有两个属性,

  • numberOfTapsRequired
  • numberOfTouchesRequired:

numberOfTapsRequired为触发事件需要点击的次数,默认是1;

numberOfTouchesRequired为触发事件需要的几个手指点按,默认是1;

若都设置为2,就需要两个手指同时点按2次才会触发事件。

Tap手势也是我们最常用的手势之一, 比如点击ImageView跳转到其他界面,或者双击图片放大缩小等。

创建:

	UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap:)];
tap.numberOfTapsRequired = 2;
tap.numberOfTouchesRequired = 1;
[self.imageView addGestureRecognizer:tap];

UIPanGestureRecognizer(拖动)

Pan手势的属性和方法:

  • @property (nonatomic) NSUInteger minimumNumberOfTouches __TVOS_PROHIBITED;
  • @property (nonatomic) NSUInteger minimumNumberOfTouches __TVOS_PROHIBITED;
  • -(CGPoint)translationInView:(nullable UIView *)view;
  • -(void)setTranslation:(CGPoint)translation inView:(nullable UIView *)view;
  • -(CGPoint)velocityInView:(nullable UIView *)view;

translationInView:方法获取View的偏移量;

setTranslation:方法设置手势的偏移量;

velocityInView:方法获取速度;

所以手势的创建方法都类似,这里就不在一一列举了。

UIScreenEdgePanGestureRecognizer (边缘拖动)

ScreenEdgePan继承于UIPanGestureRecognizer,在屏幕边缘滑动才会触发

  • @property (readwrite, nonatomic, assign) UIRectEdge edges;

edges为指定边缘拖动触发的边,是一个枚举:

typedef NS_OPTIONS(NSUInteger, UIRectEdge) {
UIRectEdgeNone = 0,
UIRectEdgeTop = 1 << 0,
UIRectEdgeLeft = 1 << 1,
UIRectEdgeBottom = 1 << 2,
UIRectEdgeRight = 1 << 3,
UIRectEdgeAll = UIRectEdgeTop | UIRectEdgeLeft | UIRectEdgeBottom | UIRectEdgeRight
} NS_ENUM_AVAILABLE_IOS(7_0);

其他方法和Tap手势一致,主要用于像左右抽屉视图的变换等处理。

UIPinchGestureRecognizer(捏合)

Pinch手势有两个属性:

  • @property (nonatomic) CGFloat scale;
  • @property (nonatomic,readonly) CGFloat velocity;

scale:捏合比例

velocity:捏合速度 = scale/second

UIRotationGestureRecognizer(旋转)

Rotation手势和Pinch手势类似,同样有两个手势:

  • @property (nonatomic) CGFloat rotation;
  • @property (nonatomic,readonly) CGFloat velocity;

rotation:旋转弧度,注意,这里的单位是弧度

velocity:旋转速度

UILongPressGestureRecognizer(长按)

LongPress的属性:

  • @property (nonatomic) NSUInteger numberOfTapsRequired; // Default is 0.
  • @property (nonatomic) NSUInteger numberOfTouchesRequired __TVOS_PROHIBITED; // Default is 1.
  • @property (nonatomic) CFTimeInterval minimumPressDuration;
  • @property (nonatomic) CGFloat allowableMovement;

numberOfTapsRequirednumberOfTouchesRequired和Tap手势类似,都是指定触发需要的点击次数和手指数量,但是LongPress手势的numberOfTapsRequired是指定长按前需要点击的次数。

minimumPressDuration:触发时间

allowableMovement:允许长按时间触发前允许手指滑动的范围。若是你在长按时手指移动,该长按手势将会失败,allowableMovement设置你能容忍的滑动范围,默认是10.

变形


iOS的变形指的是图片的旋转、平移和缩放。这些变形可以和上面介绍的手势结合,完成许多变形操作。

说变形前我们来看看CGAffineTransformCGAffineTransform为一个结构体:

struct CGAffineTransform {
CGFloat a, b, c, d;
CGFloat tx, ty;
};

我们输出一个控件的transform看看

NSLog(@"%@", NSStringFromCGAffineTransform(self.label.transform));

输出

2016-12-22 17:01:19.211 手势[6489:1481987] [1, 0, 0, 1, 0, 0]

我们可以看到输出了一个长度为6的数组:[1, 0, 0, 1, 0, 0],并且我们可以猜测对应结构体中的[a, b, c, d, tx, ty],并且默认的transform值就是[1, 0, 0, 1, 0, 0]

想进一步了解可以看这篇《iOS CGAffineTransform详解》

对iOS控件进行变形实际就是对控件transform属性进行操作。

但是我们使用中,使用已经封装好的的API对控件进行变形处理。分别是:

  • CGAffineTransformScale()
  • CGAffineTransformTranslate()
  • CGAffineTransformRotate()

和:

  • CGAffineTransformMakeScale()
  • CGAffineTransformMakeTranslate()
  • CGAffineTransformMakeRotate()

这些API都是对设置CGAffineTransform的一个封装,针对[a, b, c, d, tx, ty]中不同的位置进行操作。

下面我们在ViewController创建一个UILabel控件。然后对它进行变形操作。

缩放

首先来看一个缩放操作

// 缩放到90%(相对)
self.label.transform = CGAffineTransformScale(self.label.transform, 0.9, 0.9);

NSLog(@"%@", NSStringFromCGAffineTransform( self.label.transform));

输出:

2016-12-22 17:26:25.074 手势[6526:1564064] [0.90000000000000002, 0, 0, 0.90000000000000002, 0, 0]
2016-12-22 17:26:26.096 手势[6526:1564064] [0.81000000000000005, 0, 0, 0.81000000000000005, 0, 0]
2016-12-22 17:26:26.963 手势[6526:1564064] [0.72900000000000009, 0, 0, 0.72900000000000009, 0, 0]
2016-12-22 17:26:28.830 手势[6526:1564064] [0.65610000000000013, 0, 0, 0.65610000000000013, 0, 0]

我们再看看另一个缩放:

self.label.transform = CGAffineTransformMakeScale(0.9, 0.9);

NSLog(@"%@", NSStringFromCGAffineTransform( self.label.transform));

输出

2016-12-22 17:32:32.972 手势[6581:1600236] [0.90000000000000002, 0, 0, 0.90000000000000002, 0, 0]
2016-12-22 17:32:34.164 手势[6581:1600236] [0.90000000000000002, 0, 0, 0.90000000000000002, 0, 0]
2016-12-22 17:32:35.246 手势[6581:1600236] [0.90000000000000002, 0, 0, 0.90000000000000002, 0, 0]

对比可以发现CGAffineTransformScale()CGAffineTransformMakeScale()的区别在于,CGAffineTransformScale()实在原理的基础上在进行缩放操作,而CGAffineTransformMakeScale()直接将缩放值设定为0.9不变了。

缩放操作变动的是构体中[a, b, c, d, tx, ty]ad,值和变形系数Scale是相对应的,大于1是放大,小于1是缩小。。

a是横向缩放, d是纵向缩放。

平移

先来看一个平移操作:

self.label.transform = CGAffineTransformTranslate(self.label.transform, 10, 10);
NSLog(@"%@", NSStringFromCGAffineTransform( self.label.transform));

输出

2016-12-22 17:40:38.568 手势[6608:1631232] [1, 0, 0, 1, 10, 10]
2016-12-22 17:40:40.833 手势[6608:1631232] [1, 0, 0, 1, 20, 20]
2016-12-22 17:40:41.834 手势[6608:1631232] [1, 0, 0, 1, 30, 30]
2016-12-22 17:40:42.532 手势[6608:1631232] [1, 0, 0, 1, 40, 40]
2016-12-22 17:40:43.162 手势[6608:1631232] [1, 0, 0, 1, 50, 50]

我们可以看到label往右下角移动

对应xy的正向坐标为右下角。

旋转

self.label.transform = CGAffineTransformRotate(self.label.transform, M_PI_2);
NSLog(@"%@", NSStringFromCGAffineTransform( self.label.transform));

输出:

2016-12-22 17:59:43.680 手势[6667:1717130] [6.123233995736766e-17, 1, -1, 6.123233995736766e-17, 0, 0]

可以看到label顺时针旋转了π/2弧度(90°)。

手势结合变形


手势结合变形就是通过手势对控件变形处理。

上代码:

#import "ViewController.h"

@interface ViewController ()<UIGestureRecognizerDelegate>
@property (weak, nonatomic) IBOutlet UIImageView *imageView;

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

// CGAffineTransform *mytransform = self.imageView.transform;
self.imageView.userInteractionEnabled = YES;
//1双击 恢复
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap:)];
tap.numberOfTapsRequired = 2;
tap.numberOfTouchesRequired = 1;
[self.imageView addGestureRecognizer:tap];

//2拖拽
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];
// pan.delegate = self;
[self.imageView addGestureRecognizer:pan];

//3捏合
UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinch:)];
pinch.delegate = self;
[self.imageView addGestureRecognizer:pinch];

//4旋转
UIRotationGestureRecognizer *rotaion = [[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(rotaion:)];
// pinch.delegate = self;
[self.imageView addGestureRecognizer:rotaion];

// 长按
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
longPress.numberOfTapsRequired = 0;
longPress.minimumPressDuration = 1;
// longPress.allowableMovement = 3;
[self.imageView addGestureRecognizer:longPress];

}
- (void)tap:(UITapGestureRecognizer *)sender{

NSLog(@"tap!");

//恢复
self.imageView.transform = CGAffineTransformIdentity;

}
- (void)pan:(UIPanGestureRecognizer *)sender{
// CGPoint center = self.imageView.center;
// if (center.x < 0){
// center.x = 0;
// }else{
// center.x += [sender translationInView:self.view].x;
// }
//
// center.y += [sender translationInView:self.view].y;
// self.imageView.center = center;
//将相对偏移量清零
// [sender setTranslation:CGPointMake(0, 0) inView:self.view];

self.imageView.transform = CGAffineTransformTranslate(self.imageView.transform, [sender translationInView:self.imageView].x, [sender translationInView:self.imageView].y);
[sender setTranslation:CGPointZero inView:self.view];

}

- (void)pinch:(UIPinchGestureRecognizer *)sender{

CGFloat scale = sender.scale;
self.imageView.transform = CGAffineTransformScale(self.imageView.transform, scale, scale);
[sender setScale:1];

}

- (void)rotaion:(UIRotationGestureRecognizer *)sender{
//获取旋转弧度
CGFloat rotation = sender.rotation;
self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, rotation);
sender.rotation = 0;

// self.imageView.transform = CGAffineTransformMakeRotation(sender.rotation);

}

- (void)longPress:(UILongPressGestureRecognizer *)sender {

NSLog(@"longPress:%@", sender);

// 判断长按事件触发
if (sender.state == UIGestureRecognizerStateBegan) {
self.imageView.transform = CGAffineTransformMake(1, 0, 0, 1, 0, 0);
}

}


//希望两个手势共存
//遵守 UIGestureRecognizerDelegate 协议
//实现方法 -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
//将要同时实现的手势设置代理 pinch.delegate = self; pinch.delegate = self;

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}

有几点需要注意:

  • 给本身没有交互功能的控件()imagView, UIlabel, View等)添加手势,要设置userInteractionEnabledYES,否则识别不了手势
  • 想要手势共存需要:
    • 遵守 UIGestureRecognizerDelegate 协议
    • 实现-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer方法,返回YES
    • 将要同时实现的手势设置代理 pinch.delegate = self; pinch.delegate = self

在storyboard中添加手势

storyboard的控件栏中我们可以看到这些手势控件:

storyboard中的手势控件

使用方法:

  1. 直接将手势控件拖到要添加的视图上

  2. 关联手势事件

  3. 设置手势属性

注意:若想同时识别多个手势,方法和上面相同,遵循协议,实现方法,设置代理,不过代理可以手动关联。