UI-2
2
多控制器处理
为了方便管理控制器,ios提供了2种特殊的控制器来管理多控制器
- UINavigationController
- UITabBarController
导航控制器UINavigationController
-
凡是有导航条的,都是这个控制器
-
导航控制器有自己的view,还有一个导航条(y = 20),还有一个栈顶控制器view
-
添加就是
pushViewController
-
凡是导航条下面的
scrollView
默认都会有一个64的contentInset
偏移量,如果不想默认,就需要设置self.automaticallyAdjustsScrollViewInset = NO
表示不要自动设置偏移量。这样下面的滚动表格就从0,0开始了。 -

-
当一个控制器被添加到了导航控制器,它的
navgationController
属性就有值了 -
导航控制器是以栈的形式添加控制器的,先进后出,意味着被压住的控制器是不会被销毁的,只是被
移走了
,只有pop
才会删除控制器,当pop
跳跃移动的时候,那么这中间的控制器都会被销毁 -
导航条内容是由当前栈顶控制器的
UINavigationiItem
模型属性决定的。就是说你这个页面想展示什么,就去这个页面自己设置backBarButtonItem
:返回按钮titleView
:中间标题视图title
:中间文字leftBarButtonItem
:左上角视图(可以是文字,图片,view)- 自定义View的时候不需要设置位置,直接
sizeToFit
即可
- 自定义View的时候不需要设置位置,直接
rightBarButtonItem
:右上角视图
-
设置Item内容的时候会遇到渲染问题,可以单独渲染图片Origion,可以全局渲染
/**
全局设置状态栏白色
全局设置导航栏背景mainColor
全局设置导航栏标题文字颜色白色,微软雅黑
全局设置导航栏Item颜色 白色
*/
- (void)systemSetting {
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
[[UINavigationBar appearance] setBarTintColor:mainColor];
[[UINavigationBar appearance] setTintColor:[UIColor whiteColor]];
[[UINavigationBar appearance] setTitleTextAttributes: [NSDictionary dictionaryWithObjectsAndKeys: [UIColor whiteColor], NSForegroundColorAttributeName, [UIFont fontWithName:@"MicrosoftYaHei" size:18.0], NSFontAttributeName, nil]];
}
控制器(view)的生命周期
控制器View的生命周期方法:只要是控制器的生命周期方法,都是以view开头.
控制器View加载完成时调用
- (void)viewDidLoad {
[super viewDidLoad];
}
控制器的View显示完成时调用
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
}
控制器的View即将显示的时候调用
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
}
控制器的View完全消失的时候调用
-(void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
}
控制器的View即将消失的时候调用.
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
}
布局控制器View的子控件完成时调用
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
}
将要布局控制器的View里面子控件的时候就会调用.
-(void)viewWillLayoutSubviews{
[super viewWillLayoutSubviews];
}
ARC的生命周期
viewDidLoad->viewWillAppear->viewDidLayoutSubviews->viewDidLayoutSubviews->viewDidAppear->
viewWillDisappear->viewDidDisappear
在非ARC当中.
当前控制器的View即将被销毁的时候会调用
-(void)viewWillUnload{
[super viewWillUnload];
}
当前控制器的View被销毁的时候会调用
-(void)viewDidUnload{
[super viewDidUnload];
清空界面上的数据.
self.dataList = nil;
}
viewDidLoad->viewWillAppear->viewDidLayoutSubviews->viewDidLayoutSubviews->viewDidAppear->
viewWillDisappear->viewDidDisappear->接收到内存警告->viewWillUnload->释放View->viewDidUnload
通讯录
- 监听 账号密码都有的时候才能登陆
- switch记住密码连调
- 从ios8后,UIAlertView和UIActionSheet合并成了UIAlertController(用block回调,要创建AlertController,再创建AlertAction按钮,然后在presentVC,使用简单)
- SVProgress
- 控制器反向传值(block 代理),通知
数据存储
ios常用的存储
1. plist归档
2. Prference偏好设置
3. NSKeyedArchiver归档(NSCoding)--> 保存自定义对象
4. SQLite3
5. Core Date(对SQLite3的封装,有一套库来操作数据库)
每一个ios应用都有自己的沙盒,并且每一个app都是文件隔离,独立的沙盒,沙盒目录如下

Documents
存储着应用运行时需要持久化的数据,iTunes同步设备会备份该目录,例如游戏文档(网络下载的存这里直接苹果退回)
tmp
是保存临时数据,不备份,系统可能会清除
library/Caches
存储持久化数据,iTunes不会备份,一般存储体积大不用备份的非重要数据
library/Preference
保存应用程序所有偏好设置,iTunes会备份,
这些数据如果存储错了,那么打包可能会被苹果退回来的
- plist
- (IBAction)save:(id)sender {
数据存储是保存在手机里面的
plist文件存储一般都是存取字典和数组,直接写成plist文件,把它存到应用沙盒当中.
只有在ios当中才有plist存储,它是ios特有的存储方式.
获取沙盒根根路径,每一个应用在手机当中都有一个文件夹,这个方法就是获取当前应用在手机里安装的文件.
NSString *homeDir = NSHomeDirectory();
NSLog(@"homeDir = %@",homeDir);
在某个范围内搜索文件夹的路径.
directory:获取哪个文件夹
domainMask:在哪个路径下搜索
expandTilde:是否展开路径.
这个方法获取出的结果是一个数组.因为有可以搜索到多个路径.
NSArray *array = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
在这里,我们指定搜索的是Cache目录,所以结果只有一个,取出Cache目录
NSString *cachePath = array[0];
拼接文件路径
NSString *filePathName = [cachePath stringByAppendingPathComponent:@"agePlist.plist"];
想要把这个字典存储为plist文件.
直接把字典写入到沙盒当中
用字典写, plist文件当中保存的是字典.
NSDictionary *dict = @{@"age" : @18,@"name" : @"gaowei"};
获取沙盒路径
ToFile:要写入的沙盒路径
[dict writeToFile:filePathName atomically:YES];
用数组写,plist文件当中保存的类型是数组.
NSArray *dataArray = @[@56,@"asdfa"];
获取沙盒路径
ToFile:要写入的沙盒路径
[dataArray writeToFile:filePathName atomically:YES];
}
读取数据
- (IBAction)reader:(id)sender {
这个方法获取出的结果是一个数组.因为有可以搜索到多个路径.
NSArray *array = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
在这里,我们指定搜索的是Cache目录,所以结果只有一个,取出Cache目录
NSString *cachePath = array[0];
拼接文件路径
NSString *filePathName = [cachePath stringByAppendingPathComponent:@"agePlist.plist"];
从文件当中读取字典, 保存的plist文件就是一个字典,这里直接填写plist文件所存的路径
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePathName];
如果保存的是一个数组.那就通过数组从文件当中加载.
NSArray *dataArray = [NSArray arrayWithContentsOfFile:filePathName];
NSLog(@"%@",dataArray);
}
- 偏好设置:一般用来保存用户名,密码,字体大小等设置。
- (IBAction)save:(id)sender {
偏好设置NSUserDefaults
底层就是封闭了一个字典,利用字典的方式生成plist文件
好处:不需要关心文件名(它会自动生成)快速进行键值对存储.
NSUserDefaults *defautls = [NSUserDefaults standardUserDefaults];
[defautls setObject:@"gaowei" forKey:@"name"];
[defautls setBool:YES forKey:@"isBool"];
[defautls setInteger:5 forKey:@"num"];
//同步,立即写入文件.
[defautls synchronize];
}
- (IBAction)reader:(id)sender {
存是用什么key存的, 读的时候就要用什么key值取
存的时候使用的什么类型,取的时候也要用什么类型.
NSString *str = [[NSUserDefaults standardUserDefaults] objectForKey:@"name"];
BOOL isBool = [[NSUserDefaults standardUserDefaults] boolForKey:@"isBool"];
NSInteger num = [[NSUserDefaults standardUserDefaults] integerForKey:@"num"];
NSLog(@"name =%@-isBool=%d-num=%ld",str,isBool,num);
}
- 归档:偏好设置和pilsh都是字典数组,存储对象需要归档
保存数据
- (IBAction)save:(id)sender {
归档一般都是保存自定义对象的时候,使用归档.因为plist文件不能够保存自定义对象.
如果一个字典当中保存有自定义对象,如果把这个字典写入到文件当中,它是不会生成plist文件的.
Persion *persion = [[Persion alloc] init];
persion.name = @"gaowei";
persion.age = 18;
获取沙盒临时目录
NSString *tempPath = NSTemporaryDirectory();
NSString *filePath = [tempPath stringByAppendingPathComponent:@"persion.data"];
archiveRootObject这个方法底层会去调用保存对象的encodeWithCoder方法,去询问要保存这个对象的哪些属性.
所以要实现encodeWithCoder方法, 告诉要保存这个对象的哪些属性.
[NSKeyedArchiver archiveRootObject:persion toFile:filePath];
}
读取数据
- (IBAction)reader:(id)sender {
获取沙盒临时目录
NSString *tempPath = NSTemporaryDirectory();
NSString *filePath = [tempPath stringByAppendingPathComponent:@"persion.data"];
NSKeyedUnarchiver会调用initWithCoder这个方法,来让你告诉它去获取这个对象的哪些属性.
所以我们在保存的对象当中实现initWithCoder方法.
Persion *persion = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(@"name=%@---age=%d",persion.name,persion.age);
}
要保存的对象
#import <Foundation/Foundation.h>
要遵守<NSCoding>协议
@interface Persion : NSObject<NSCoding>
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) int age;
@end
archiveRootObject这个方法底层会去调用保存对象的encodeWithCoder方法,去询问要保存这个对象的哪些属性.
只有遵守了NSCoding协议之后才能够实现这个方法.
-(void)encodeWithCoder:(NSCoder *)encode{
[encode encodeObject:self.name forKey:@"name"];
[encode encodeInt32:self.age forKey:@"age"];
}
NSKeyedUnarchiver会调用initWithCoder这个方法,来让你告诉它去获取这个对象的哪些属性.
initWithCoder什么时候调用:解析一个文件的时候就会调用.
-(instancetype)initWithCoder:(NSCoder *)decoder{
这个地方为什么没有[super initWithCoder]
是因为它的父类没有遵守NSCoding协议
if (self = [super init]) {
要给它里面的属性进行赋值,外界取得对象时访问该属性,里面才会用值.
self.age = [decoder decodeInt32ForKey:@"age"];
self.name = [decoder decodeObjectForKey:@"name"];
}
return self;
}
UITabBarController
- 也用于管理多控制器,也有一个子控制器view,底部有一个UITabBar。UITabBar的高度是
49
,selecyedIndex
属性控制当前显示控制器,并且也有一个数组childViewControllers
,但是添加的时候不是栈的形式了,当选中(显示)别的控制器的时候,不显示的控制器还是一直在的,因为都在数组里,没释放的。 - UITabBar内容:
- 如果UITabBarController有N个子控制器,那么UITabBar就有N个UITabBarButton。UITabBarButton里面的内容由
UITabBarItem
模型控制,其中有title,image,selectedImage,badgeValue
等属性。
- 如果UITabBarController有N个子控制器,那么UITabBar就有N个UITabBarButton。UITabBarButton里面的内容由
MODAL跳转
- 从地下钻出来的控制器,就是modal。任何控制器都能modal出来,用
present 和 dismiss
来完成。push
一般是有关系的两个页面,modal一般注册啊这个那个都可以用。
手势&绘图
对UIView的形变:transform
在做动画的时候,一般会对View做平移,旋转,缩放
等操作,就用到transform
- CGAffineTransformTranslate
- CGAffineTransformRotate
- CGAffineTransformScale
IOS的事件
1.ios的事件分为三大类,触摸,加速计,远程控制事件
,分别是触摸,摇动手机,控制耳机按钮等。
触摸
:ios中不是任何对象都能处理事件,只有继承了UIResponder的对象才能响应,称为响应者对象。UIApplication,UIViewController,UIView
都继承自UIResponder,都能接收并且处理事件。
//当开始触摸屏幕的时候调用
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"%s",__func__);
}
//触摸时开始移动时调用(移动时会持续调用)
//NSSet:无序
//NSArray:有序
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//NSLog(@"%s",__func__);
//做UIView拖拽
UITouch *touch = [touches anyObject];
//求偏移量 = 手指当前点的X - 手指上一个点的X
CGPoint curP = [touch locationInView:self];
CGPoint preP = [touch previousLocationInView:self];
NSLog(@"curP====%@",NSStringFromCGPoint(curP));
NSLog(@"preP====%@",NSStringFromCGPoint(preP));
CGFloat offsetX = curP.x - preP.x;
CGFloat offsetY = curP.y - preP.y;
//平移
self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);
}
//当手指离开屏幕时调用
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"%s",__func__);
}
//当发生系统事件时就会调用该方法(电话打入,自动关机)
-(void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"%s",__func__);
}
2.事件的传递:touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
方法中的touches
记录了你当前触摸的点,上一次触摸的点,你触摸的当前控制器或者view,而event
就保存着事件,里面保存着当前发生的事件的时间,类型等。
当发生一个触摸事件,系统会将该事件加入一个由UIApplication
管理事件的队列(先进先出),然后拿出最前面的事件(也就是先触摸的),然后交给主窗口keyWindow
,主窗口会根据视图层次结构找到一个最合适的视图来处理触摸事件,找到了就调用touch方法了。
UIAppcation
-> UIWindow
-> 父控件
-> 一层层到子控件
。如果父控件无法接受触摸,那么子控件也无法接收触摸。如果父控件隐藏,子控件也隐藏。如果父控件aplha = 0,子控件也是0.
3.UIView不接收触摸的三种情况
- userIntercationEnable = NO
- hidden = YES
- alpha = 0.0 ~ 0.01
- UIImageView默认用户交互是No
4.如何寻找最适合View?
先看自己能否接收事件,再看触摸点在不在自己身上,然后从后往前遍历子控件,如果没有合适的就是自己最合适处理。如果用代码来寻找,就要调用hitTest
方法。(开发中没用过 )
//作用:去寻找最适合的View
//什么时候调用:当一个事件传递给当前View,就会调用.
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
NSLog(@"%s",__func__);
return [super hitTest:point withEvent:event];
}
5 事件触摸
在产生一个事件时,系统会将该事件加入到一个由UIApplication管理的事件队列中,
UIApplication会从事件队列中取出最前面的事件,将它传递给先发送事件给应用程序的主窗口.
主窗口会调用hitTest方法寻找最适合的视图控件,找到后就会调用视图控件的touches方法来做具体的事情.
当调用touches方法,它的默认做法, 就会将事件顺着响应者链条往上传递,
传递给上一个响应者,接着就会调用上一个响应者的touches方法
如何去寻找上一个响应者?
1.如果当前的View是控制器的View,那么控制器就是上一个响应者.
2.如果当前的View不是控制器的View,那么它的父控件就是上一个响应者.
3.在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理
4.如果window对象也不处理,则其将事件或消息传递给UIApplication对象
5.如果UIApplication也不能处理该事件或消息,则将其丢弃
6.手势识别
手势识别器UIGrestreRecognizer
是一个抽象类,使用它的子类才能处理手势,有点按,长按,轻扫,拖动,旋转,捏合
六种。
添加点按手势
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap)];
手势也可以设置代理
tap.delegate = self;
添加手势
[self.imageV addGestureRecognizer:tap];
代理方法:
是否允许接收手指
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
让图片的左边不可以点击,
获取当前手指所在的点.是在图片的左边还是在图片的右边.
CGPoint curP = [touch locationInView:self.imageV];
if (curP.x > self.imageV.bounds.size.width * 0.5) {
在图片的右侧
return YES;
}else{
在图片的左侧
return NO;
}
return YES;
}
添加长按手势
UILongPressGestureRecognizer *longP = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longP:)];
[self.imageV addGestureRecognizer:longP];
当长按时调用.
这个方法会调用很多次, 当手指长按在上面不松,来回移动时,会持续调用.
所以要判断它的状态.
- (void)longP:(UILongPressGestureRecognizer *)longP{
if(longP.state == UIGestureRecognizerStateBegan){
NSLog(@"开始长按");
}else if(longP.state == UIGestureRecognizerStateChanged){
NSLog(@"长按时手指移动");
}else if(longP.state == UIGestureRecognizerStateEnded){
NSLog(@"手指离开屏幕");
}
}
添加轻扫手势
UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe:)];
轻扫手势默认是向右边称轻扫
可以设置轻扫的方法.
一个轻扫手势只能设置一个方法的轻扫.想要让它有多个方向的手势,必须得要设置的
swipe.direction = UISwipeGestureRecognizerDirectionLeft;
[self.imageV addGestureRecognizer:swipe];
添加轻扫手势
UISwipeGestureRecognizer *swipe2 = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe:)];
轻扫手势默认是向右边称轻扫
可以设置轻扫的方法.
一个轻扫手势只能设置一个方法的轻扫.想要让它有多个方向的手势,必须得要设置的
swipe2.direction = UISwipeGestureRecognizerDirectionUp;
[self.imageV addGestureRecognizer:swipe2];
- (void)swipe:(UISwipeGestureRecognizer *)swipe{
判断的轻扫的方向
if (swipe.direction == UISwipeGestureRecognizerDirectionLeft) {
NSLog(@"向左轻扫");
}else if(swipe.direction == UISwipeGestureRecognizerDirectionUp){
NSLog(@"向上轻扫");
}
}
添加平移手势
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
[self.imageV addGestureRecognizer:pan];
当手指拖动时调用
- (void)pan:(UIPanGestureRecognizer *)pan{
拖动手势也有状态
if(pan.state == UIGestureRecognizerStateBegan){
开始拖动
}else if(pan.state == UIGestureRecognizerStateChanged){
可能获取不当前手指移动的举例
是相对于最原始的点
CGPoint transP = [pan translationInView:self.imageV];
会清空上一次的形变
self.imageV.transform = CGAffineTransformMakeTranslation(transP.x,transP.y);
self.imageV.transform = CGAffineTransformTranslate(self.imageV.transform, transP.x, transP.y);
复位,让它相对于上一次.
[pan setTranslation:CGPointZero inView:self.imageV];
}else if(pan.state == UIGestureRecognizerStateEnded){
结束拖动
}
}
添加捏合手势
UIPinchGestureRecognizer *pinGes = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinGes:)];
设置代理使其能够同时支持多个手势
pinGes.delegate = self;
[self.imageV addGestureRecognizer:pinGes];
旋转时调用
- (void)pinGes:(UIPinchGestureRecognizer *)pin{
self.imageV.transform = CGAffineTransformScale(self.imageV.transform, pin.scale, pin.scale);
复位
[pin setScale:1];
}
添加旋转手势
UIRotationGestureRecognizer *rotation = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(rotation:)];
设置代理使其能够同时支持多个手势
rotation.delegate = self;
[self.imageV addGestureRecognizer:rotation];
当手指开始旋转时调用.
- (void)rotation:(UIRotationGestureRecognizer *)rotation{
self.imageV.transform = CGAffineTransformRotate(self.imageV.transform, rotation.rotation);
复位.
[rotation setRotation:0];
}
可以添加多个手势,例如又旋转又放大又捏合(支持代理,实现方法就行,要找一找,方法比较多,是一个允许支持多手势的代理方法),
手势案例 -- 抽屉效果
Make by:弓_虽_子
第一步:搭建界面
- (void)viewDidLoad {
[super viewDidLoad];
搭建界面
[self setUpView];
}
- (void)setUpView{
添加左边的View
UIView *leftV = [[UIView alloc] initWithFrame:self.view.bounds];
左边蓝色
leftV.backgroundColor = [UIColor blueColor];
[self.view addSubview:leftV];
添加右边的View
UIView *rightV = [[UIView alloc] initWithFrame:self.view.bounds];
右边縁色
rightV.backgroundColor = [UIColor greenColor];
self.rightV = rightV;
[self.view addSubview:rightV];
添加中间的View(中间一个最后添加,显示到最外面.)
UIView *mainV = [[UIView alloc] initWithFrame:self.view.bounds];
中间红色
mainV.backgroundColor = [UIColor redColor];
self.mainV = mainV;
[self.view addSubview:mainV];
}
第二步.添加手势.能够让中间的红色View左右移动,要在控制器View加载完成时就要添加View
- (void)viewDidLoad {
[super viewDidLoad];
搭建界面
[self setUpView];
拖动手势
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
添加手势
[self.mainV addGestureRecognizer:pan];
}
实现手势方法:
当手指拖动时调用.
-(void)pan:(UIPanGestureRecognizer *)pan{
获取手指在屏幕上面的偏移量
CGPoint transP = [pan translationInView:self.mainV];
在这里为什么不用Transform,是因为我们移动时,要改变的尺寸大小.用Transform只能改变它的位置.
self.mainV.transform = CGAffineTransformTranslate(self.mainV.transform, transP.x, 0);
计算mainV的Frame
单独抽出一个方法来计算mainV的frame.因为要计算它的Y值和高度.代码会很多.所以单独抽出一个方法
self.mainV.frame = [self frameWithOffsetX:transP.x];
每次移动时判断当前MainV的x值是大于0还是小于0.如果是大于0 , 显示左边,小于0 显示右边
if (self.mainV.frame.origin.x > 0) {
self.rightV.hidden = YES;
}else if(self.mainV.frame.origin.x < 0){
self.rightV.hidden = NO;
}
注意要做复位
[pan setTranslation:CGPointZero inView:self.mainV];
}
最大Y值为100
#define maxY 100
根据偏移量计算mainV的frame.
- (CGRect)frameWithOffsetX:(CGFloat)offsetX{
取出最原始的Frame
CGRect frame = self.mainV.frame;
frame.origin.x += offsetX;
获取屏幕的宽度
(计算Y值如果下图:找最大值.当Main.x拖动最大的时候,Main.y值也最大.main.x最大为屏幕的宽度)
设定一个最大Y值MaxY为100,正好.当max.x为屏幕的宽度时,最大Y等于100
所以Y值等于 main.y = main.x * maxY / ScreenW;
100 = 375 * 100 / 375;)
有可能frame.origin.x有可能是小于0,小于0的话, 得出的Y值就会小于0,小于0就会出现, 红色的View向上走.
对结果取绝对值.
frame.origin.y = fabs(frame.origin.x * maxY / screenW);
计算frame的高度
(当前Main的高度等于屏幕的高度减去两倍的Y值.)
frame.size.height = screenH - 2 * frame.origin.y;
返回计算好的frame.
return frame;
}
第三步:当手指松开时做到自动定位.
MainV定位到右侧的X值
#define targetR 275
MainV定位到右侧的X值
#define targetL -275
当手指拖动时调用.
-(void)pan:(UIPanGestureRecognizer *)pan{
获取手指在屏幕上面的偏移量
CGPoint transP = [pan translationInView:self.mainV];
在这里为什么不用Transform,是因为我们移动时,要改变的尺寸大小.用Transform只能改变它的位置.
self.mainV.transform = CGAffineTransformTranslate(self.mainV.transform, transP.x, 0);
计算mainV的Frame
单独抽出一个方法来计算mainV的frame.因为要计算它的Y值和高度.代码会很多.所以单独抽出一个方法
self.mainV.frame = [self frameWithOffsetX:transP.x];
每次移动时判断当前MainV的x值是大于0还是小于0.如果是大于0 , 显示左边,小于0 显示右边
if (self.mainV.frame.origin.x > 0) {
self.rightV.hidden = YES;
}else if(self.mainV.frame.origin.x < 0){
self.rightV.hidden = NO;
}
判断手指的状态
if(pan.state == UIGestureRecognizerStateEnded){
当手指松开时进入执行
记录最终判断结果后.定位的值.
CGFloat target = 0;
当手指松开,要判断MainV的x值是否大于屏幕的一半.如果大于屏幕一半时, 自动定位到右边一个位置.
if (self.mainV.frame.origin.x > screenW * 0.5) {
target = targetR;
}else if(CGRectGetMaxX(self.mainV.frame) < screenW * 0.5){
当手指松开,要判断MainV的最大的X值是否小于屏幕的一半.如果小于屏幕的一半时, 自动定位到左边的位置.
target = targetL;
}
最终定位的x值 - 当前的main.x的值. 求出便宜量.使其定位
CGFloat offsetX = target - self.mainV.frame.origin.x;
根据便宜量设置mainV的frame值
CGRect frame = [self frameWithOffsetX:offsetX];
[UIView animateWithDuration:0.25 animations:^{
伴随动画设置frame
self.mainV.frame = frame;
}];
}
注意要做复位
[pan setTranslation:CGPointZero inView:self.mainV];
}