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即可
    • 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

通讯录

  1. 监听 账号密码都有的时候才能登陆
  2. switch记住密码连调
  3. 从ios8后,UIAlertView和UIActionSheet合并成了UIAlertController(用block回调,要创建AlertController,再创建AlertAction按钮,然后在presentVC,使用简单)
  4. SVProgress
  5. 控制器反向传值(block 代理),通知

数据存储

ios常用的存储

1. plist归档	
2. Prference偏好设置
3. NSKeyedArchiver归档(NSCoding)--> 保存自定义对象
4. SQLite3
5. Core Date(对SQLite3的封装,有一套库来操作数据库)

每一个ios应用都有自己的沙盒,并且每一个app都是文件隔离,独立的沙盒,沙盒目录如下
-w200

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的高度是49selecyedIndex属性控制当前显示控制器,并且也有一个数组childViewControllers,但是添加的时候不是栈的形式了,当选中(显示)别的控制器的时候,不显示的控制器还是一直在的,因为都在数组里,没释放的。
  • UITabBar内容:
    • 如果UITabBarController有N个子控制器,那么UITabBar就有N个UITabBarButton。UITabBarButton里面的内容由UITabBarItem 模型控制,其中有title,image,selectedImage,badgeValue等属性。

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];    
}





原文地址:https://www.cnblogs.com/sgxx/p/7120092.html