爬爬爬之路:UI(五) 全局引用 解耦的两种方法 手势识别器

全局引用

第一步 新建文件

在Xcode中新建文件
other-> PCH File
勾选Targets的第一个选项
注意是新建在工程里

第二步

设置文件的绝对路径
PCH不同于普通文件. 它识别的是绝对路径. 当工程放置于其他电脑上, 会导致本文件无法被正常识别
解决方法:
工程设置Build Settings -> All -> 查找到Prefix Header
未设置路径的情况下
在未设置路径时, 该选项后是空白的
双击右侧空白处
步骤2
弹出输入框如图所示.

输入路径:$(SRCROOT)/你的工程名/你的prefix文件名.pch
$(SRCROOT)/myProject/PrefixHeader.pch
小博的工程名为myProject, prefix文件名为PerfixHeader.pch(默认生成的PCH文件名)
输入完毕后按下回车即可
此时空白处会被一串路径代替.

此时再在pch文件中写入需要全局引入的头文件名 或者 宏定义
如图
此时就可以在全局默认使用了


解耦的两种方法

  1. target/action模式
  2. delegate代理模式

以一个例题来说明这两种模式的具体实现
在RootViewController中, 添入一个自定义视图LoginView, LoginView中填入一个自定义视图MyImageView
实现点击MyImageView, 可以变化MyImageView的颜色

// LoginView.h
@property (nonatomic, retain) MyImageView *imageView;
// LoginView.m
// 省略dealloc方法
// 重写初始化方法
#define kImageViewWidth  100
#define kImageViewHeight 100
- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // 将子试图添加步骤放置addSubviews方法中实现.
        [self addSubviews];
    }
    return self;
}
- (void)addSubviews {
    self.imageView = [[MyImageView alloc] initWithFrame:CGRectMake((kScreenWidth - kImageViewWidth) / 2, 100, kImageViewWidth, kImageViewHeight)];
    self.imageView.backgroundColor = [UIColor redColor];
    [self addSubview:self.imageView];
    [_imageView release];
}

为了实现点击MyImageView变化背景色的功能, 必需在MyImageView中响应touchBegan或者touchEnded方法.
但是为了体现解耦的目的, 将具体更换背景图片的逻辑放在RootViewController中实现

这两种模式 主要就是介绍如何将逻辑放在RootViewController中实现

target/action模式

将RootViewController中的两个对象: RootViewController的对象(target)和执行方法名(action)当成参数穿给MyImageView.

// MyImageView.h
@property (nonatomic, retain) id target;
@property (nonatomic, assign) SEL action; 

// 自定义初始化方法
- (instancetype)initWithFrame:(CGRect)frame target:(id)target action:(SEL)action;
// MyImageView.m
// 实现自定义初始化方法
- (instancetype)initWithFrame:(CGRect)frame target:(id)target action:(SEL)action {
    self = [super initWithFrame:frame];
    if (self) {
        // UIImageView的交互开关默认是关闭的, 手动打开
        self.userInteractionEnabled = YES;
        self.target = target;
        self.action = action;
    }
    return self;
}
// 响应点击MyImageView事件
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    // 让传入的对象 调用起自身的方法名为clickMyImageView:的方法, 并将自身当成参数传给self.target对象
    [self.target performSelector:self.action withObject:self]; 
}
// 改写LoginView
// LoginView.h
// LoginView中也定义两个相同的属性作为过渡传导
@property (nonatomic, retain) id target;
@property (nonatomic, assign) SEL action; 
@property (nonatomic, retain) MyImageView *imageView;

// 自定义初始化方法
- (instancetype)initWithFrame:(CGRect)frame target:(id)target action:(SEL)action;
//LoginView.m
// 实现自定义初始化方法
- (instancetype)initWithFrame:(CGRect)frame target:(id)target action:(SEL)action {
    self = [super initWithFrame:frame];
    if (self) {
        self.target = target;
        self.action = action;
        // 调用MyImageView的自定义初始化方法
        self.imageView = [[MyImageView alloc] initWithFrame:CGRectMake((kScreenWidth - kImageViewWidth) / 2, 100, kImageViewWidth, kImageViewHeight) target:self action:@selector(clickMyImageView:)];
        self.imageView.backgroundColor = [UIColor redColor];
        [self addSubview:self.imageView];
        [_imageView release];
    }
    return self;
}

// 实现clickMyImageView:方法
- (void)clickMyImageView:(MyImageView *)imageView {
    [self.target performSelector:self.action withObject:imageView];
}
// 然后在RootViewController.m的viewDidLoad方法中实现
LoginView *loginView = [[LoginView alloc] initWithFrame:self.view.bounds target:self action:@selector(click:)];
[self.view addSubview:loginView];
// 并实现click:方法
- (void)click:(MyImageView:)imageView {
    imageView.backgroundColor = [UIColor colorWithRed:(arc4random() % (255 - 0 + 1) + 0) / 255.0 green:(arc4random() % (255 - 1 + 1) + 1) / 255.0 blue:(arc4random() % (255 - 1 + 1) + 1) / 255.0 alpha:1];
}

或者不需要通过LoginView作为传递

// MyImageView.h
@property (nonatomic, retain) id target;
@property (nonatomic, assign) SEL action; 

// 使用继承于UIImageView的初始化方法

// MyImageView.m中
// 重写初始化方法
- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.userInteractionEnabled = YES;
    }
    return self;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [self.target performSelector:self.action withObject:self];
}
// LoginView.h中
@property (nonatomic, retain) MyImageView *imageView;

// loginView.m中 正常重写自定义初始化方法
- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.imageView = [[MyImageView alloc] initWithFrame:CGRectMake((kScreenWidth - kImageViewWidth) / 2, 100, kImageViewWidth, kImageViewHeight)];
        self.imageView.backgroundColor = [UIColor redColor];
        [self addSubview:self.imageView];
        [_imageView release];
    }
    return self;
}
// RootViewController.m中 在viewDidLoad方法中实现

LoginView *loginView = [[LoginView alloc] initWithFrame:self.view.bounds];
loginView.imageView.target = self;
loginView.imageView.action = @selector(click:);
[self.view addSubview:loginView];

// click:方法不变

两种写法各有优劣. 看个人的习惯而定.

delegate代理模式

// 在MyImageView.h中声明代理如下
@class MyImageView; // 因为此时MyImageView还未声明, 需要先声明MyImageView是一个类
@protocol MyImageViewDelegate <NSObject>
@optional
- (void)changeColor:(MyImageView *)imageView;
@end


@interface MyImageView : UIImageView
// 注意代理虽然也是id对象类型, 但是用assign来修饰 在文后会进行解释
@property (nonatomic, assign) id<MyImageViewDelegate> delegate;
@end
// MyImageView.m文件中实现
- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.userInteractionEnabled = YES;
    }
    return self;
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    // 安全判断, 若无, 在当代理人未实现本方法的时候 会崩溃.
    if ([_delegate respondsToSelector:@selector(changeColor:)]) {
        [self.delegate changeColor:self];
    }
}

跳过LoginView(需要将MyImageView对象声明成属性)

// 在RootViewController.m的viewDidLoad方法中实现:
LoginView *loginView = [[LoginView alloc] initWithFrame:self.view.bounds];
loginView.imageView.delegate = self;
[self.view addSubview:loginView];

// 实现代理方法
- (void)changeColor:(MyImageView *)imageView {
    imageView.backgroundColor = [UIColor colorWithRed:(arc4random() % (255 - 0 + 1) + 0) / 255.0 green:(arc4random() % (255 - 1 + 1) + 1) / 255.0 blue:(arc4random() % (255 - 1 + 1) + 1) / 255.0 alpha:1];
}

相比于target/action模式, 代理模式显得更加简明.
解耦的目的: 为了实现代码的复用.

关于为什么属性delegate明明是id类型却要声明成assign

举个例子:
有两个类A和B, 他们互相将对方作为各自的代理
// A.h
@property (nonatomic, retain) id delegate;
// B.h
@property (nonatomic, retain) id delegate;
各自的.m文件中均按要求书写了dealloc方法:

- (void)dealloc {
    [_delegate release];
    [super dealloc];
}

// 在main.m或者其他类中实现以下语句

    A *a = [[A alloc] init];    // 此时a.retainCount = 1
    B *b = [[B alloc] init];    // 此时b.retainCount = 1
    a.delegate = b;    // 由于类A中的delegate为retain 此时 b.retainCount = 2;
    b.delegate = a;    // 理由同上, a.retainCount = 2;
    // 按照书写要求, 一个alloc对应一个release
    [a release];       // a.retainCount = 1
    [b release];       // b.retainCount = 1
    // 此时就会发现, 按照规范的内存管理释放原则, 此时对象a和b均没有被释放. 他们的retainCount均不等于0, 于是他们的dealloc都不会被调用.这导致了内存泄漏

这也是为什么代理需要声明成assign

// 若A, B中delegate均是assign修饰的
A *a = [[A alloc] init];    // a.retainCount = 1
B *b = [[B alloc] init];    // b.retainCount = 1
a.delegate = b;             // 由于是assign b.retainCount = 1不增
b.delegate = a;             // 同上 a.retainCount = 1
[a release];                // a.retainCount = 0
[b release];                // b.retainCount = 0 两个对象都被完美释放

手势识别器

⼿势识别器是UIGestureRecognizer 是iOS中的⼀个抽象类,⽤于识别⼀个⼿势
所谓⼿势:有规律的触摸。

分别识别轻拍⼿势、平移⼿势、轻扫⼿势、缩放⼿势、旋转⼿势、 ⻓按⼿势以及屏幕边界平移⼿势 一共七种手势.
其中缩放手势也叫捏合手势

UIGestureRecognizer 类中
属性
view 手势添加所在的view.
state 手势状态
typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
UIGestureRecognizerStatePossible // 未知状态
UIGestureRecognizerStateBegan // 手势开启状态
UIGestureRecognizerStateChanged // 手势发生改变状态
UIGestureRecognizerStateEnded // 手势结束状态
UIGestureRecognizerStateCancelled // 手势取消状态
UIGestureRecognizerStateFailed // 手势失败状态
UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded // 手势结束状态
};
方法:
- (instancetype)initWithTarget:(id)target action:(SEL)action;
七种手势

*1. 轻拍手势 能识别轻拍操作 (也就是点击)

UITapGestureRecognizer

属性:
  1. numberOfTapsRequired
    NSUInteger 默认为1 表示响应轻拍事件的拍击次数

  2. numberOfTouchesRequired
    NSUInteger 默认为1 表示响应轻拍事件的手指操作数

实现步骤
// 新建手势类 设置响应方法名 和响应方法所在对象
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction:)];

// 新建一个UIView的子类, 用于添加手势操作
UIImageView *view = [[UIImageView alloc] initWithFrame:self.view.bounds];
// 打开交互
view.userInteractionEnabled = YES;
// 添加手势操作
[view addGestureRecognizer:tapGestureRecognizer];
[tapGestureRecognizer release];
[self.view addSubview:view];
[view release];

// 实现响应方法
- (void)tapAction:(UITapGestureRecognizer *)tap {
    NSLog(@"我是轻拍");
}

*2. 长按手势 能识别⻓按操作。

UILongPressGestureRecognizer

属性
  1. minimumPressDuration
    CFTimeInterval(Double) 响应长按事件的最少按键时长 默认0.5秒
  2. allowableMovement
    CGFloat 响应长按事件过程允许滑动的最大范围 默认10
实现方法参照拍击手势

不同的是, 响应状态方法中可以加入一句

if (longPress.state == UIGestureRecognizerStateBegan) {

}
// 由于长按事件是基于touchMoved, 会多次响应, 利用本句判断让响应只执行一次

3. 旋转手势 能识别旋转操作

UIRotationGestureRecognizer

属性
  1. rotation
    CGFloat 旋转角度
  2. velocity
    CGFloat 旋转速度
    利用 .view.transform 属性进行设置图片的旋转形变方式
    transform
    struct CGAffineTransform {
    CGFloat a, b, c, d;
    CGFloat tx, ty;

用到的方法:
CGAffineTransform transform = CGAffineTransformRotate(<#CGAffineTransform t#>, <#CGFloat angle#>);
参数1: 要发生形变属性的视图
参数2: 根据弧度创建

使用方法:

在响应事件中:

CGAffineTransform transform = CGAffineTransformRotate(rotation.view.transform, rotation.rotation);
rotation.view.transform = transform;
// 每次转的时候 需要把旋转的角度重置为0
// 因为要接替上一次旋转的角度开始旋转
rotation.rotation = 0; // 本句很重要, 否则旋转会调整会非常鬼畜

4. 捏合手势

UIPinchGestureRecognizer

属性
  1. scale
    CGFloat 捏合的比例
  2. velocity
    CGFloat 捏合的速度

pinch.view.transform = CGAffineTransformScale(<#CGAffineTransform t#>, <#CGFloat sx#>, <#CGFloat sy#>)
参数1: 要发生形变属性的视图
参数2, 3: 捏合的比例

使用方法:
// 根据缩放的刻度改变形变的属性
pinch.view.transform = CGAffineTransformScale(pinch.view.transform, pinch.scale, pinch.scale);
// 重置捏合比例
pinch.scale = 1;
// 然而相册的捏合和这个一点关系没有

5. 平移手势

UIPanGestureRecognizer

属性
  1. minimumNumberOfTouches
    NSUInteger 默认是1, 表示触发平移手势所需的手指数
  2. maximumNumberOfTouches
    NSUInteger 默认是最大值, 表示触发平移手势所需最大操作手指头数
使用方法:
// 获取平移的点(相对于要平移的试图)
CGPoint p = [pan translationInView:pan.view];

// 根据这个点 改变形变属性
pan.view.transform = CGAffineTransformTranslate(pan.view.transform, p.x, p.y);
// 重置这个点
[pan setTranslation:CGPointMake(0, 0) inView:pan.view];

*6. 轻扫手势

UISwipeGestureRecognizer

属性
  1. numberOfTouchesRequired
    NSUInteger 默认是1 识别轻扫手势的最小手指数
  2. direction
    UISwipeGestureRecognizerDirection 默认是右, 识别轻扫手势的扫动放向
typedef NS_OPTIONS(NSUInteger, UISwipeGestureRecognizerDirection) {
    UISwipeGestureRecognizerDirectionRight = 1 << 0,     // 右扫
    UISwipeGestureRecognizerDirectionLeft  = 1 << 1,     // 左扫
    UISwipeGestureRecognizerDirectionUp    = 1 << 2,     // 上扫
    UISwipeGestureRecognizerDirectionDown  = 1 << 3      // 下扫 
}
使用方法:

参照以上

7. 屏幕边缘平移手势 是iOS7中新增的⼿势。

UIScreenEdgePanGestureRecognizer

属性
  1. edges
    UIRectEdge 触发边缘平移手势的边缘方位
 typedef NS_OPTIONS(NSUInteger, UIRectEdge) {
    UIRectEdgeNone   = 0,          // 无
    UIRectEdgeTop    = 1 << 0,     // 上
    UIRectEdgeLeft   = 1 << 1,     // 左
    UIRectEdgeBottom = 1 << 2,     // 下
    UIRectEdgeRight  = 1 << 3,     // 右
    UIRectEdgeAll    = UIRectEdgeTop | UIRectEdgeLeft | UIRectEdgeBottom | UIRectEdgeRight                    // 所有方向
};
使用方法

参照以上


tips:通常需要换图片的控件, 一般使用button 不用ImageView

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值