[iOS学习笔记]自学过程中积累的知识点(四)

31. UINavigationController

31.1 UINavigationController的简单使用

UINavigationController的使用步骤

  1. 初始化UINavigationController
  2. 设置UIWindow的rootViewController为UINavigationController
  3. 根据具体情况,通过push方法添加对应个数的子控制器

31.2 UINavigationController的子控制器

// UINavigationController以栈的形式保存子控制器
@property(nonatomic,copy) NSArray *viewControllers;
@property(nonatomic,readonly) NSArray *childViewControllers;

// 使用push方法能将某个控制器压入栈
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated;

// 使用pop方法可以移除控制器
// 将栈顶的控制器移除
- (UIViewController *)popViewControllerAnimated:(BOOL)animated;
// 回到指定的子控制器
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated;
// 回到根控制器(栈底控制器)
- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated;

31.3 如何修改导航栏的内容

// 导航栏的内容由栈顶控制器的navigationItem属性决定

// UINavigationItem有以下属性影响着导航栏的内容
// 左上角的返回按钮
@property(nonatomic,retain) UIBarButtonItem *backBarButtonItem;
// 中间的标题视图
@property(nonatomic,retain) UIView          *titleView;
// 中间的标题文字
@property(nonatomic,copy)   NSString        *title;
// 左上角的视图
@property(nonatomic,retain) UIBarButtonItem *leftBarButtonItem;
// 右上角的视图
@property(nonatomic,retain) UIBarButtonItem *rightBarButtonItem;

32. 内存警告处理

与之相关的三个方法:

  • -(void)didReceiveMemoryWarning;
  • -(void)viewWillUnload;
  • -(void)viewDidUnload;

33. UITabBarController

33.1 UITabBarController的简单使用

UITabBarController的使用步骤

  1. 初始化UITabBarController
  2. 设置UIWindow的rootViewController为UITabBarController
  3. 根据具体情况,通过addChildViewController方法添加对应个数的子控制器

33.2 UITabBarController的子控制器

UITabBarController添加控制器的方式有2种

  • 添加单个子控制器
    – (void)addChildViewController:(UIViewController *)childController;
  • 设置子控制器数组
    @property(nonatomic,copy) NSArray *viewControllers;

33.3 UITabBar && UITabBarButton

  • UITabBar与UITabBarButton的关系

    [iOS学习笔记]自学过程中积累的知识点(四)_第1张图片

  • UITabBarButton

34. 数据存取

34.1 iOS应用数据存储的常用方式

  • XML属性列表(plist)归档
  • Preference(偏好设置)
  • NSKeyedArchiver归档(NSCoding)
  • SQLite3
  • Core Data(基于SQLite3的可存取对象的方式)

34.2 iOS程序沙盒文件系统

  • 应用程序包:(上图中的Layer)包含了所有的资源文件和可执行文件
  • Documents:保存应用运行时生成的需要持久化的数据,iTunes同步设备时会备份该目录。例如,游戏应用可将游戏存档保存在该目录
  • tmp:保存应用运行时所需的临时数据,使用完毕后再将相应的文件从该目录删除。应用没有运行时,系统也可能会清除该目录下的文件。iTunes同步设备时不会备份该目录
  • Library/Caches:保存应用运行时生成的需要持久化的数据,iTunes同步设备时不会备份该目录。一般存储体积大、不需要备份的非重要数据
  • Library/Preference:保存应用的所有偏好设置,iOS的Settings(设置)应用会在该目录中查找应用的设置信息。iTunes同步设备时会备份该目录

34.3 应用沙盒目录的获取


1. 沙盒根目录

NSString *home = NSHomeDirectory();

2. 获取Documents文件夹

// *********方式1.利用沙盒根目录拼接”Documents”字符串*************
// 不建议采用,因为新版本的操作系统可能会修改目录名
NSString *home = NSHomeDirectory();
NSString *documents = [home stringByAppendingPathComponent:@"Documents"];

// **********方式2. 利用NSSearchPathForDirectoriesInDomains函数**********
// NSUserDomainMask 代表从用户文件夹下找
// YES 代表展开路径中的波浪字符“~”
NSArray *array =  NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, NO);
// 在iOS中,只有一个目录跟传入的参数匹配,所以这个集合里面只有一个元素
NSString *documents = [array objectAtIndex:0];

3. tmp

NSString *tmp = NSTemporaryDirectory();

4. Library/Caches (跟Documents类似的2种方法) 利用沙盒根目录拼接”Caches”字符串 利用NSSearchPathForDirectoriesInDomains函数(将函数的第2个参数改为:NSCachesDirectory即可)

5. Library/Preference 通过NSUserDefaults类存取该目录下的设置信息

34.4 plist归档

如果对象是NSString、NSDictionary、NSArray、NSData、NSNumber等类型,就可以使用writeToFile:atomically:方法直接将对象写到属性列表文件中


1. 保存数据

// 将数据封装成字典
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:@"母鸡" forKey:@"name"];
[dict setObject:@"15013141314" forKey:@"phone"];
[dict setObject:@"27" forKey:@"age"];
// 将字典持久化到Documents/stu.plist文件中
[dict writeToFile:path atomically:YES];

2. 读取数据

// 读取Documents/stu.plist的内容,实例化NSDictionary
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];
// 打印信息
NSLog(@"name:%@", [dict objectForKey:@"name"]);
NSLog(@"phone:%@", [dict objectForKey:@"phone"]);
NSLog(@"age:%@", [dict objectForKey:@"age"]);

34.5 NSKeyedArchiver & NSKeyedUnarchiver

使用NSKeyedArchiver可以持久化一个对象(包括自定义的对象)
注意:要持久化的对象必须实现NSCoding协议,并实现该协议中的encodeWithCoder:(NSCoder *)encoder和initWithCoder:(NSCoder *)decoder方法

e.g.

/*******************Person.h******************/
#import <Foundation/Foundation.h>

// 如果想将一个自定义对象保存到文件中必须实现NSCoding协议
@interface Person : NSObject <NSCoding>

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) double height;
@end


/*******************Person.m******************/
#import "Person.h"

@implementation Person

// 当将一个自定义对象保存到文件的时候就会调用该方法
// 在该方法中说明如何存储自定义对象的属性
// 也就说在该方法中说清楚存储自定义对象的哪些属性
- (void)encodeWithCoder:(NSCoder *)encoder
{
    [encoder encodeObject:self.name forKey:@"name"];
    [encoder encodeInteger:self.age forKey:@"age"];
    [encoder encodeFloat:self.height forKey:@"heigth"];
}

// 当从文件中读取一个对象的时候就会调用该方法
// 在该方法中说明如何读取保存在文件中的对象
// 也就是说在该方法中说清楚怎么读取文件中的对象
- (id)initWithCoder:(NSCoder *)decoder
{
    if (self = [super init]) {
        self.name = [decoder decodeObjectForKey:@"name"];
        self.age = [decoder decodeIntegerForKey:@"age"];
        self.height = [decoder decodeFloatForKey:@"heigth"];
    }
    return self;
}

@end


/*******************在控制器中使用时******************/
/**************************************************/
/**************************************************/

// 将一个Penson对象持久化至本地,path为要保存至的文件路径
[NSKeyedArchiver archiveRootObject:person toFile:path];

// 将文件中持久化的数据实例化至Person对象p中
Person *p = [NSKeyedUnarchiver unarchiveObjectWithFile:path];

34.6 偏好设置

很多iOS应用都支持偏好设置,比如保存用户名、密码、字体大小等设置,iOS提供了一套标准的解决方案来为应用加入偏好设置功能。每个应用都有个NSUserDefaults实例,通过它来存取偏好设置。

1. 保存偏好设置

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:@"itcast" forKey:@"username"];
[defaults setFloat:18.0f forKey:@"text_size"];
[defaults setBool:YES forKey:@"auto_login"];

2. 读取偏好设置

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *username = [defaults stringForKey:@"username"];
float textSize = [defaults floatForKey:@"text_size"];
BOOL autoLogin = [defaults boolForKey:@"auto_login"];

注意:UserDefaults设置数据时,不是立即写入,而是根据时间戳定时地把缓存中的数据写入本地磁盘。所以调用了set方法之后数据有可能还没有写入磁盘应用程序就终止了。出现以上问题,可以通过调用synchornize方法强制写入
[defaults synchornize];

35. Quartz2D绘图

35.1 Quartz2D绘图的基本过程

// 1.获得图形上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();

// 2. 拼接路径(下面代码是一条线段)
CGContextMoveToPoint(ctx, 10, 10);
CGContextAddLineToPoint(ctx, 100, 100);

// 3. 绘制路径
CGContextStrokePath(ctx);       // 绘制路径
// CGContextFillPath(ctx); // 绘制封闭的填充图形

35.2 Quartz2D的上下文

  1. Bitmap Graphics Context
    UIGraphicsBeginImageContext(CGSize size)
    UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale) opaque : YES:不透明 NO:透明

  2. PDF Graphics Context

  3. Window Graphics Context

  4. Layer Graphics Context
    可在UIView的drawRect:方法里面通过UIGraphicsGetCurrentContext()方法获得

  5. Printer Graphics Context

e.g. 画图并保存至本地文件(bitmap的创建以及使用)

// 0. 创建bitmap上下文
UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 200), NO, 0);
// 1.获取bitmap上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 2.绘图
CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, 100, 100));
// 3.渲染
CGContextStrokePath(ctx);
// 4.获取生成的图片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// 5.显示生成的图片到imageview
self.iv.image = image;
// 6.保存绘制好的图片到文件中
// 先将图片转换为二进制数据, 然后再将图片写到文件中
NSData *data = UIImagePNGRepresentation(image);
[data writeToFile:@"/Users/apple/Desktop/abc.png" atomically:YES];

35.3 常用的函数

  1. 添加一个矩形
    void CGContextAddRect(CGContextRef c, CGRect rect)
    CGContextStrokeRect(CGContextRef c, CGRect rect) 直接绘制一个空心矩形(所有图形都有这个方法)
    CGContextFillRect(CGContextRef c, CGRect rect) 直接绘制一个实心矩形(所有图形都有这个方法)

  2. 添加一个椭圆(rect为椭圆的外接圆)
    void CGContextAddEllipseInRect(CGContextRef context, CGRect rect)

  3. 添加一个圆弧
    void CGContextAddArc(CGContextRef c, CGFloat x, CGFloat y, CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise)
    其中:
    x/y 圆心
    radius 半径
    startAngle 开始的弧度 eg: M_PI
    endAngle 结束的弧度
    clockwise 画圆弧的方向 (0 顺时针, 1 逆时针)

  4. 关闭路径
    void CGContextClosePath ( CGContextRef c )

  5. 获得上下文
    CGContextRef UIGraphicsGetCurrentContext()

  6. 设置颜色
    [[UIColor purpleColor] setFill]; 设置实心颜色
    [[UIColor blueColor] setStroke]; 设置空心颜色
    [[UIColor greenColor] set]; 同时设置空心/实心颜色
    [[UIColor colorWithRed:1.0 green:0 blue:0 alpha:1.0] set]; 同时设置空心/实心颜色

  7. 画曲线(一条直线通过几个控制点的扭曲实现)
    void CGContextAddCurveToPoint(CGContextRef c, CGFloat cp1x, CGFloat cp1y, CGFloat cp2x, CGFloat cp2y, CGFloat x, CGFloat y )
    CGContextAddQuadCurveToPoint(CGContextRef c, CGFloat cpx, CGFloat cpy, CGFloat x, CGFloat y)
    其中:
    cpx/cpy 是指控制点的坐标
    x/y 终点的坐标

  8. Path的一些方法
    CGPathCreateMutable 类似于 CGContextBeginPath
    CGPathMoveToPoint 类似于 CGContextMoveToPoint
    CGPathAddLineToPoint 类似于 CGContextAddLineToPoint
    CGPathAddCurveToPoint 类似于 CGContextAddCurveToPoint
    CGPathAddEllipseInRect 类似于 CGContextAddEllipseInRect
    CGPathAddArc 类似于 CGContextAddArc
    CGPathAddRect 类似于 CGContextAddRect
    CGPathCloseSubpath 类似于 CGContextClosePath
    CGPathRef
    CGMutablePathRef
    注意:但凡通过quarzt2d中的带有create/ copy / retain 方法创建出来的值都必须手动的释放
    在此创建路径时,记得释放创建的路径,释放路径的两种方法
    CGPathRelease(path)
    CFRelease(path)

  9. 将Path添加至上下文
    void CGContextAddPath(CGContextRef context, CGPathRef path);

  10. 绘制图形
    CGContextStrokePath(ctx); // 绘制路径
    CGContextFillPath(ctx); // 绘制封闭的填充图形

  11. 设置渲染时的参数
    void CGContextSetLineWidth(CGContextRef c, CGFloat width); 设置线条的宽度
    void CGContextSetLineJoin(CGContextRef c, CGLineJoin join) 设置线条转角样式
    void CGContextSetMiterLimit(CGContextRef c, CGFloat limit) 当转角样式为Miter时,limit参数对其有影响
    void CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat lengths[], size_t count) 虚线相关

  12. 保存/恢复图形上下文
    CGContextSaveGState(ctx) 保存上下文至堆栈
    CGContextRestoreGState(ctx) 恢复已保存的上下文至ctx

  13. 清空上下文内容
    CGContextClearRect(CGContextRef c, CGRect rect)

  14. 对layer进行矩阵操作
    CGContextRotateCTM(CGContextRef c, CGFloat angle)
    CGContextScaleCTM(CGContextRef c, CGFloat sx, CGFloat sy)
    CGContextTranslateCTM(CGContextRef c, CGFloat tx, CGFloat ty)

  15. 指定可以显示的范围(可用来剪切图片)
    CGContextClip(ctx);

  16. 通知UIView刷新视图
    - (void)setNeedsDisplay;
    - (void)setNeedsDisplayInRect:(CGRect)rect;

  17. NSTimer & CADisplayLink刷帧变成动画
    具体用法见系列第十四点:NSTimer & CADisplayLink

  18. 获取上下文中的图片
    UIImage* UIGraphicsGetImageFromCurrentImageContext(void)

  19. 将图片转为NSData类型,使其可以保存至文件中
    NSData *UIImageJPEGRepresentation(UIImage *image, CGFloat compressionQuality);
    NSData *UIImagePNGRepresentation(UIImage *image); png图片效果更好

35.4 数字水印

基本思想:创建一个bitmap,首先绘制原图,然后在原图的某个位置绘制水印,最后保存。

// 1.调用分类方法生成水印图片
UIImage *newImage =  [UIImage imageWithBackgroundImageName:@"psb" log:@"logo1"];

// 2.将图片写到文件中
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"aaa.png"];

NSData *data = UIImagePNGRepresentation(newImage);
[data writeToFile:path atomically:YES];

生成水印的方法

+ (instancetype)imageWithBackgroundImageName:(NSString *)bgName log:(NSString *)logNmae
{
    // 0. 加载背景图片
    UIImage *image = [UIImage imageNamed:bgName];

    // 1.创建bitmap上下文
    // 执行完这一行在内存中就相遇创建了一个UIImage
    UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);

    // 2.绘图图片
    // 绘制背景图片
    [image drawAtPoint:CGPointMake(0, 0)];

    // 绘制水印
    UIImage *logImage = [UIImage imageNamed:logNmae];

     CGFloat margin = 10;
     CGFloat logY = margin;
     CGFloat logX = image.size.width - margin - logImage.size.width;
     [logImage drawAtPoint:CGPointMake(logX, logY)];

// NSString *str = @"如果仅有文字,可以这样";
// [str drawAtPoint:CGPointMake(150, 50) withAttributes:nil];

    // 3.获得图片
    UIImage *newImage =  UIGraphicsGetImageFromCurrentImageContext();

    return newImage;
}

35.5 条纹背景

// 1.生成一张以后用于平铺的小图片
CGSize size = CGSizeMake(self.view.frame.size.width, 44);
UIGraphicsBeginImageContextWithOptions(size , NO, 0);

// 2.画矩形
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFloat height = 44;
CGContextAddRect(ctx, CGRectMake(0, 0, self.view.frame.size.width, height));
[[UIColor redColor] set];
CGContextFillPath(ctx);

// 3.画线条
CGFloat lineWidth = 2;
CGFloat lineY = height - lineWidth;
CGFloat lineX = 0;
CGContextMoveToPoint(ctx, lineX, lineY);
CGContextAddLineToPoint(ctx, 320, lineY);
[[UIColor blackColor] set];
CGContextStrokePath(ctx);

// Key Point : 调用UIColor里面的colorWithPatternImage函数获取条纹背景,
// 然后设置为背景颜色即可
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIColor *myColor = [UIColor colorWithPatternImage:image];
self.contentView.backgroundColor = myColor;

35.6 书页翻页效果

CATransition的其他用法详见39.4.5:CATransition

CATransition *ca = [[CATransition alloc] init];
ca.type = @"pageCurl";
[self.contentView.layer addAnimation:ca forKey:nil];

35.7 截屏并保存至相册

// 1.创建一个bitmap的上下文
UIGraphicsBeginImageContext(self.view.frame.size);

// 2.将屏幕绘制到上下文中
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();

// 3.保存图片到相册
UIImageWriteToSavedPhotosAlbum(newImage, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);

注意:访问相册时iOS会提示是否允许app访问相册。如果用户禁止访问相册,然后又保存了图片,则最好在image:didFinishSavingWithError:contextInfo:函数里面提示用户允许app访问

 - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{   
    if (error) {
        NSLog(@"保存失败,app应提示用户在Settings -> Privacy -> Camera 中将对应app的UISwitch置为On");
    }else
    {
        NSLog(@"提示保存成功");
    }
}

35.8 用UIBezierPath画一条线

// 1. 创建路径
UIBezierPath *path = [UIBezierPath bezierPath];

// 2.设置路径的相关属性 (options)
[path setLineJoinStyle:kCGLineJoinRound];
[path setLineCapStyle:kCGLineCapRound];
[path setLineWidth:10];

// 3. 设置当前路径的起点
[path moveToPoint:startPoint];

// 4. 设置当前路径的终点
[path addLineToPoint:movePoint];

// 5. 绘制path
[path stroke];

36. 触摸事件

iOS中的事件可分为三类:
1. 触摸事件
2. 加速计事件
3. 远程控制事件
在iOS中不是任何对象都能处理事件,只有继承了UIResponder的对象才能接收并处理事件。我们称之为“响应者对象”。UIApplication、UIViewController、UIView都继承自UIResponder,因此它们都是响应者对象,都能够接收并处理事件。

36.1 响应者对象UIResponder

UIResponder内部提供了以下方法来处理事件

  • 触摸事件
    -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
    -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
    -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
    -(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
    touches里面存放的都是UITouch对象

  • 加速计事件
    -(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
    -(void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
    -(void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;

  • 远程控制事件
    -(void)remoteControlReceivedWithEvent:(UIEvent *)event;

36.2 UITouch对象

当用户用一根触摸屏幕时,会创建一个与手指相关联的UITouch对象
一根手指对应一个UITouch对象

36.2.1 UITouch的作用

UITouch保存着跟手指相关的信息,比如触摸的位置、时间、阶段
当手指移动时,系统会更新同一个UITouch对象,使之能够一直保存该手指在的触摸位置
当手指离开屏幕时,系统会销毁相应的UITouch对象
提示:iPhone开发中,要避免使用双击事件!!!

36.2.2 UITouch的属性

  • 触摸产生时所处的窗口
    @property(nonatomic,readonly,retain) UIWindow *window;

  • 触摸产生时所处的视图
    @property(nonatomic,readonly,retain) UIView *view;

  • 短时间内点按屏幕的次数,可以根据tapCount判断单击、双击或更多的点击
    @property(nonatomic,readonly) NSUInteger tapCount;

  • 记录了触摸事件产生或变化时的时间,单位是秒
    @property(nonatomic,readonly) NSTimeInterval timestamp;

  • 当前触摸事件所处的状态
    @property(nonatomic,readonly) UITouchPhase phase;

36.2.3 UITouch的函数

  • -(CGPoint)locationInView:(UIView *)view;
    返回值表示触摸在view上的位置
    这里返回的位置是针对view的坐标系的(以view的左上角为原点(0, 0))
    调用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置

  • -(CGPoint)previousLocationInView:(UIView *)view;
    该方法记录了前一个触摸点的位置

36.3 UIEvent对象

每产生一个事件,就会产生一个UIEvent对象
UIEvent:称为事件对象,记录事件产生的时刻和类型

36.3.1 常见属性

  • 事件类型
    @property(nonatomic,readonly) UIEventType type;
    @property(nonatomic,readonly) UIEventSubtype subtype;

  • 事件产生的时间
    @property(nonatomic,readonly) NSTimeInterval timestamp;

36.4 touches和event

  • 一次完整的触摸过程,会经历3个状态:
    触摸开始:- (void)touchesBegan:(NSSet )touches withEvent:(UIEvent )event
    触摸移动:- (void)touchesMoved:(NSSet )touches withEvent:(UIEvent )event
    触摸结束:- (void)touchesEnded:(NSSet )touches withEvent:(UIEvent )event
    触摸取消(可能会经历):- (void)touchesCancelled:(NSSet )touches withEvent:(UIEvent )event

  • 4个触摸事件处理方法中,都有NSSet *touches和UIEvent *event两个参数
    一次完整的触摸过程中,只会产生一个事件对象,4个触摸方法都是同一个event参数
    如果两根手指同时触摸一个view,那么view只会调用一次touchesBegan:withEvent:方法,touches参数中装着2个UITouch对象
    如果这两根手指一前一后分开触摸同一个view,那么view会分别调用2次touchesBegan:withEvent:方法,并且每次调用时的touches参数中只包含一个UITouch对象
    根据touches中UITouch的个数可以判断出是单点触摸还是多点触摸

36.5 事件的产生和传递

  1. 发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中
  2. UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)
  3. 主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件,这也是整个事件处理过程的第一步
  4. 找到合适的视图控件后,就会调用视图控件的touches方法来作具体的事件处理
    touchesBegan…
    touchesMoved…
    touchedEnded…
    触摸事件的传递是从父控件传递到子控件.如果父控件不能接收触摸事件,那么子控件就不可能接收到触摸事件
    UIView不能接收触摸事件的三种情况:1.不接受用户交互(userInteractionEnabled = NO) 2.隐藏了(hidden = YES) 3.是透明的(alpha = 0.0 ~0.01)。
    Tips:UIImageView的userInteractionEnabled默认就是NO,因此UIImageView以及它的子控件默认是不能接收触摸事件的

响应者链条的事件传递过程:
1. 如果view的控制器存在,就传递给控制器;如果控制器不存在,则将其传递给它的父视图
2. 在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理
3. 如果window对象也不处理,则其将事件或消息传递给UIApplication对象
4. 如果UIApplication也不能处理该事件或消息,则将其丢弃

37 手势识别(Gesture Recognizer)

为了完成手势识别,必须借助于手势识别器—-UIGestureRecognizer
利用UIGestureRecognizer,能轻松识别用户在某个view上面做的一些常见手势

UIGestureRecognizer是一个抽象类,定义了所有手势的基本行为,使用它的子类才能处理具体的手势

  • UITapGestureRecognizer(敲击)
  • UIPinchGestureRecognizer(捏合,用于缩放)
  • UIPanGestureRecognizer(拖拽)
  • UISwipeGestureRecognizer(轻扫)
  • UIRotationGestureRecognizer(旋转)
  • UILongPressGestureRecognizer(长按)

37.1 手势识别器的使用

// 1. 创建手势识别器对象
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] init];

// 2. 设置手势识别器对象的具体属性
// 连续敲击2次
tap.numberOfTapsRequired = 2;
// 需要2根手指一起敲击
tap.numberOfTouchesRequired = 2;

// 3. 添加手势识别器到对应的view上
[self.iconView addGestureRecognizer:tap];

// 4. 监听手势的触发
[tap addTarget:self action:@selector(tapIconView:)];

37.2 手势识别的状态

typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
    UIGestureRecognizerStatePossible,           // 没有触摸事件发生,所有手势识别的默认状态
    UIGestureRecognizerStateBegan,              // 一个手势已经开始但尚未改变或者完成时
    UIGestureRecognizerStateChanged,            // 手势状态改变
    UIGestureRecognizerStateEnded,              // 手势完成
    UIGestureRecognizerStateCancelled,      // 手势取消,恢复至Possible状态
    UIGestureRecognizerStateFailed,         // 手势失败,恢复至Possible状态
    UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded    // 识别到手势识别
};

37.3 各个手势识别器的属性

  • UITapGestureRecognizer
    numberOfTapsRequired 点击的次数
    numberOfTouchesRequired 需要的手指数

  • UIPinchGestureRecognizer
    scale 相对于屏幕触摸点的比例
    velocity 速度

  • UIPanGestureRecognizer
    minimumNumberOfTouches
    maximumNumberOfTouches

  • UISwipeGestureRecognizer
    numberOfTouchesRequired
    direction 方向

  • UIRotationGestureRecognizer
    rotation 旋转角度
    velocity

  • UILongPressGestureRecognizer
    numberOfTapsRequired
    numberOfTouchesRequired
    minimumPressDuration 按下的最小持续时间
    allowableMovement 手指按下后事件响应之前允许手指移动的偏移位

37.4 UIGestureRecognizerDelegate

  • (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
    该方法返回的BOOL值决定了view是否能够同时响应多个手势,返回YES表示允许

38. CALayer

38.1 CALayer的属性

  • 宽度和高度
    @property CGRect bounds;

  • 位置(默认指中点,具体由anchorPoint决定)
    @property CGPoint position;

  • 锚点(x,y的范围都是0-1),决定了position的含义
    @property CGPoint anchorPoint;

  • 背景颜色(CGColorRef类型)
    @property CGColorRef backgroundColor;

  • 形变属性
    @property CATransform3D transform;

  • 边框颜色(CGColorRef类型)
    @property CGColorRef borderColor;

  • 边框宽度
    @property CGFloat borderWidth;

  • 圆角半径
    @property CGColorRef borderColor;

  • 内容(比如设置为图片CGImageRef)
    @property(retain) id contents;

38.2 position和anchorPoint

CALayer有2个非常重要的属性:position和anchorPoint,他们共同决定View显示在什么位置

  • position
    用来设置CALayer在父层中的位置
    以父层的左上角为原点(0, 0)

  • anchorPoint
    称为“定位点”、“锚点”
    决定着CALayer身上的哪个点会在position属性所指的位置
    以自己的左上角为原点(0, 0)
    它的x、y取值范围都是0~1,默认值为(0.5, 0.5)

38.3 CALayer的隐式动画

  • 每一个UIView内部都默认关联着一个CALayer,我们可用称这个Layer为Root Layer(根层)

  • 所有的非Root Layer,也就是手动创建的CALayer对象,都存在着隐式动画

  • 什么是隐式动画?
    当对非Root Layer的部分属性进行修改时,默认会自动产生一些动画效果
    而这些属性称为Animatable Properties(可动画属性)

  • 列举几个常见的Animatable Properties:
    bounds:用于设置CALayer的宽度和高度。修改这个属性会产生缩放动画
    backgroundColor:用于设置CALayer的背景色。修改这个属性会产生背景色的渐变动画
    position:用于设置CALayer的位置。修改这个属性会产生平移动画

  • 可以通过动画事务(CATransaction)关闭默认的隐式动画效果

[CATransaction begin];
[CATransaction setDisableActions:YES];
self.myview.layer.position = CGPointMake(10, 10);
[CATransaction commit];

39. Core Animation核心动画

Core Animation的动画执行过程都是在后台操作的,不会阻塞主线程。另外Core Animation是直接作用在CALayer上的,并非UIView。

39.1 Core Animation的使用步骤

  1. 使用它需要先添加QuartzCore.framework框架和引入主头文件<QuartzCore/QuartzCore.h/>(iOS7及以后不需要)
  2. 初始化一个CAAnimation对象,并设置一些动画相关属性
  3. 通过调用CALayer的addAnimation:forKey:方法增加CAAnimation对象到CALayer中,这样就能开始执行动画了
  4. 通过调用CALayer的removeAnimationForKey:方法可以停止CALayer中的动画

39.2 CAAnimation继承结构

CAAnimation is an abstract animation class. It provides the basic support for the CAMediaTiming and CAAction protocols. To animate Core Animation layers or Scene Kit objects, create instances of the concrete subclasses CABasicAnimation, CAKeyframeAnimation, CAAnimationGroup, or CATransition.

CAAnimation是一个抽象的动画类。它为CAMediaTiming和CAAtion协议提供了基础支持。为了使用核心动画,需要创建具体的子类:CABasicAnimation, CAKeyframeAnimation, CAAnimationGroupCATransition.

39.3 CAAnimation的属性解析

  • duration:动画的持续时间
  • repeatCount:动画的重复次数
  • repeatDuration:动画的重复时间
  • removedOnCompletion:默认为YES,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。如果想让图层保持显示动画执行后的状态,那就设置为NO,不过还要设置fillMode为kCAFillModeForwards
  • fillMode:决定当前对象在非active时间段的行为.比如动画开始之前,动画结束之后
  • beginTime:可以用来设置动画延迟执行时间,若想延迟2s,就设置为CACurrentMediaTime()+2
  • CACurrentMediaTime()为图层的当前时间
  • timingFunction:速度控制函数,控制动画运行的节奏
  • delegate:动画代理

39.4 CAAnimation子类

39.4.1 CAPropertyAnimation

是CAAnimation的子类,也是个抽象类,要想创建动画对象,应该使用它的两个子类:CABasicAnimation和CAKeyframeAnimation
属性解析:

  • keyPath:通过指定CALayer的一个属性名称为keyPath(NSString类型),并且对CALayer的这个属性的值进行修改,达到相应的动画效果。比如,指定@”position”为keyPath,就修改CALayer的position属性的值,以达到平移的动画效果

39.4.2 CABasicAnimation

  • fromValue:keyPath相应属性的初始值
  • toValue:keyPath相应属性的结束值
  • byValue: 在当前基础上增加的值
  • 随着动画的进行,在长度为duration的持续时间内,keyPath相应属性的值从fromValue渐渐地变为toValue
  • 如果果fillMode=kCAFillModeForwards和removedOnComletion=NO,那么在动画执行完毕后,图层会保持显示动画执行后的状态。但在实质上,图层的属性值还是动画执行前的初始值,并没有真正被改变。比如,CALayer的position初始值为(0,0),CABasicAnimation的fromValue为(10,10),toValue为(100,100),虽然动画执行完毕后图层保持在(100,100)这个位置,实质上图层的position还是为(0,0)

39.4.3 CAKeyframeAnimation

  • values:就是上述的NSArray对象。里面的元素称为”关键帧”(keyframe)。动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧
  • path:可以设置一个CGPathRef\CGMutablePathRef,让层跟着路径移动。path只对CALayer的anchorPoint和position起作用。如果你设置了path,那么values将被忽略
  • keyTimes:可以为对应的关键帧指定对应的时间点,其取值范围为0到1.0,keyTimes中的每一个时间值都对应values中的每一帧.当keyTimes没有设置的时候,各个关键帧的时间是平分的
  • CABasicAnimation可看做是最多只有2个关键帧的CAKeyframeAnimation

39.4.4 CAAnimationGroup

CAAnimation的子类,可以保存一组动画对象,将CAAnimationGroup对象加入层后,组中所有动画对象可以同时并发运行

  • animations:用来保存一组动画对象的NSArray
  • 默认情况下,一组动画对象是同时运行的,也可以通过设置动画对象的beginTime属性来更改动画的开始时间

39.4.5 CATransition

CAAnimation的子类,用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果。iOS比Mac OS X的转场动画效果少一点

  • type:动画过渡类型(type取值见下)
  • subtype:动画过渡方向
  • startProgress:动画起点(在整体动画的百分比)
  • endProgress:动画终点(在整体动画的百分比)

过渡效果

  • fade //交叉淡化过渡(不支持过渡方向)
  • kCATransitionFade
  • push //新视图把旧视图推出去 kCATransitionPush
  • moveIn //新视图移到旧视图上面 kCATransitionMoveIn
  • reveal //将旧视图移开,显示下面的新视图 kCATransitionReveal
  • cube //立方体翻滚效果
  • oglFlip //上下左右翻转效果
  • suckEffect //收缩效果,如一块布被抽走(不支持过渡方向)
  • rippleEffect //滴水效果(不支持过渡方向)
  • pageCurl //向上翻页效果
  • pageUnCurl //向下翻页效果
  • cameraIrisHollowOpen //相机镜头打开效果(不支持过渡方向)
  • cameraIrisHollowClose //相机镜头关上效果(不支持过渡方向)

40. 常用小功能

40.1 打电话

  • 播完不能自动回到原应用,会停留在拨号页面
NSURL *url = [NSURL URLWithString:@"tel://10010"];
[[UIApplication sharedApplication] openURL:url];

缺点:电话打完后,不会自动回到原应用,直接停留在通话记录界面

  • 拨完后能自动回到原应用
NSURL *url = [NSURL URLWithString:@"telprompt://10010"];
[[UIApplication sharedApplication] openURL:url];

缺点:因为是私有API,所以可能不会被审核通过

  • 推荐 创建一个UIWebView来加载URL,拨完后能自动回到原应用
if (_webView == nil) {
    _webView = [[UIWebView alloc] initWithFrame:CGRectZero]; } [_webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"tel://10010"]]];

注意:这个webView千万不要添加到界面上来,不然会挡住其他界面

40.2 发短信

  • 直接跳到发短信界面,但是不能指定短信内容,而且不能自动回到原应用
NSURL *url = [NSURL URLWithString:@"sms://10010"];
[[UIApplication sharedApplication] openURL:url];
  • 可以指定短信内容,可以回到原界面
#import <MessageUI/MessageUI.h>

// 显示发短信的控制器
MFMessageComposeViewController *vc = [[MFMessageComposeViewController alloc] init];
// 设置短信内容
vc.body = @"吃饭了没?";
// 设置收件人列表
vc.recipients = @[@"10010", @"02010010"];
// 设置代理
vc.messageComposeDelegate = self;

// 显示控制器
[self presentViewController:vc animated:YES completion:nil];

// 代理方法,当短信界面关闭的时候调用,发完后会自动回到原应用
- (void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult)result
{
    // 关闭短信界面
    [controller dismissViewControllerAnimated:YES completion:nil];

    if (result == MessageComposeResultCancelled) {
        NSLog(@"取消发送");
    } else if (result == MessageComposeResultSent) {
        NSLog(@"已经发出");
    } else {
        NSLog(@"发送失败");
    }
}

40.3 发邮件(模拟器可以发邮件)

  • 用自带的邮件客户端,发完邮件后不会自动回到原应用
NSURL *url = [NSURL URLWithString:@"mailto://[email protected]"];
[[UIApplication sharedApplication] openURL:url];
  • 可以指定邮件内容,可以回到原界面
// 不能发邮件
if (![MFMailComposeViewController canSendMail]) return;

MFMailComposeViewController *vc = [[MFMailComposeViewController alloc] init];

// 设置邮件主题
[vc setSubject:@"会议"];
// 设置邮件内容
[vc setMessageBody:@"今天下午开会吧" isHTML:NO];
// 设置收件人列表
[vc setToRecipients:@[@"[email protected]"]];
// 设置抄送人列表
[vc setCcRecipients:@[@"[email protected]"]];
// 设置密送人列表
[vc setBccRecipients:@[@"[email protected]"]];

// 添加附件(一张图片)
UIImage *image = [UIImage imageNamed:@"lufy.jpeg"];
NSData *data = UIImageJPEGRepresentation(image, 0.5);
[vc addAttachmentData:data mimeType:@"image/jepg" fileName:@"lufy.jpeg"];

// 设置代理
vc.mailComposeDelegate = self;
// 显示控制器
[self presentViewController:vc animated:YES completion:nil];

// 邮件发送后的代理方法回调,发完后会自动回到原应用
- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
    // 关闭邮件界面
    [controller dismissViewControllerAnimated:YES completion:nil];

    if (result == MFMailComposeResultCancelled) {
        NSLog(@"取消发送");
    } else if (result == MFMailComposeResultSent) {
        NSLog(@"已经发出");
    } else {
        NSLog(@"发送失败");
    }
}

40.4 打开其他文件

  • 如果想打开一些常见文件,比如html、txt、PDF、PPT等,都可以使用UIWebView打开,只需要告诉UIWebView文件的URL即可
  • 至于打开一个远程的共享资源,比如http协议的,也可以调用系统自带的Safari浏览器:
NSURL *url = [NSURL URLWithString:@”http://www.baidu.com"];
[[UIApplication sharedApplication] openURL:url];

40.5 应用间跳转

有时候,需要在本应用中打开其他应用,比如从A应用中跳转到B应用

  • 首先,B应用得有自己的URL地址(在Info.plist中配置)
  • 接着在A应用中使用UIApplication完成跳转
NSURL *url = [NSURL URLWithString:applicationURL];
[[UIApplication sharedApplication] openURL:url];

40.6 跳转至AppStore

  • 方法1
NSString *appid = @"444934666";
NSString *str = [NSString stringWithFormat:@"itms-apps://ax.itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=%@", appid];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:str]];
  • 方法2
NSString *str = [NSString stringWithFormat:@"itms-apps://itunes.apple.com/cn/app/id%@?mt=8", appid];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:str]];

40.7 关于Block循环引用

block循环应用会导致控制器无法释放,解决方法是创建一个weak的引用在block里面使用
__weak typeof(self) unsafeSelf = self;

__unsafe_unretained NJShareViewController *unsafeSelf = self;
__weak NJShareViewController *unsafeSelf = self;
__unsafe_unretained相当于__weak,两者只有一点区别:
__weak 当对象释放之后会自动设置为nil, 而__unsafe_unretained不会,还是会指向一个内存单元

你可能感兴趣的:(ios)