全部总结

UIView的常见属性

UIView的常见属性

- (void)addSubview:(UIView *)view;

添加一个子控件view


- (void)removeFromSuperview;

从父控件中移除


- (UIView *)viewWithTag:(NSInteger)tag;

根据一个tag标识找出对应的控件(一般都是子控件)

 

 

transform属性

 

利用transform属性可以修改控件的位移(位置)、缩放、旋转


创建一个transform属性

CGAffineTransform CGAffineTransformMakeTranslation(CGFloat tx,  CGFloat ty) ;

CGAffineTransform CGAffineTransformMakeScale(CGFloat sx, CGFloat sy);

CGAffineTransform CGAffineTransformMakeRotation(CGFloat angle)

(注意:angle是弧度制,并不是角度制)


在某个transform的基础上进行叠加

CGAffineTransform CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty);

CGAffineTransform CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy);

CGAffineTransform CGAffineTransformRotate(CGAffineTransform t, CGFloat angle);


清空之前设置的transform属性

view.transform = CGAffineTransformIdentity;

 

 

 

1笔记

 

1. IBAction的参数

========================================

- (IBAction)left:(UIButton *)button


1> 在OC中,绝大多数的控件监听方法的第一个参数就是控件本身

2> 默认连线时的参数类型是id

3> 如果要在监听方法中,方便控件的使用,可以在连线时或者连线后,修改监听方法的参数类型


2. 修改对象的结构体成员

========================================

在OC中,不允许直接修改“对象”的“结构体属性”的“成员”,但是允许修改“对象”的“结构体属性”


修改结构体属性的成员方法如下:


1> 使用临时变量记录对象的结构体属性

2> 修改临时变量的属性

3> 将临时变量重新设置给对象的结构体属性


3. 在程序开发中需要避免出现魔法数字(Magic Number)

========================================

使用枚举类型,可以避免在程序中出现魔法数字


1> 枚举类型实质上就是一个整数,其作用就是用来替代魔法数字

2> 枚举类型中,指定了第一个整数之后,后面的数字会递增


4. frame & bounds & center

========================================

1> frame可以修改对象的位置和尺寸

2> bounds可以修改对象的尺寸

3> center可以修改对象的位置


5. 首尾式动画

========================================

// beginAnimations表示此后的代码要“参与到”动画中

[UIView beginAnimations:nil context:nil];

// setAnimationDuration用来指定动画持续时间

[UIView setAnimationDuration:2.0];


self.headImageView.bounds = rect;

......


// commitAnimations,将beginAnimation之后的所有动画提交并生成动画

[UIView commitAnimations];


6. transform属性

========================================

在OC中,通过transform属性可以修改对象的平移、缩放比例和旋转角度


常用的创建transform结构体方法分两大类


1> 创建“基于控件初始位置”的形变

CGAffineTransformMakeTranslation

CGAffineTransformMakeScale

CGAffineTransformMakeRotation


2> 创建“基于transform参数”的形变

CGAffineTransformTranslate

CGAffineTransformScale

CGAffineTransformRotate


补充:

在OC中,所有跟角度相关的数值,都是弧度值,180° = M_PI

正数表示顺时针旋转

负数表示逆时针旋转


提示:由于transform属性可以基于控件的上一次的状态进行叠加形变,例如,先旋转再平移

因此在实际动画开发中,当涉及位置、尺寸形变效果时,大多修改控件的transform属性,

而不是frame、bounds、center


7. 使用代码创建控件

========================================

在OC开发中,Storyboard中的所有操作都可以通过代码实现,程序员一定要熟练掌握代码布局界面的能力!


使用代码创建控件的步骤如下:


1> 使用控件对应类创建对象

2> 设置对象属性:frame\color\text\image\backgroundImage……

3> [self.view addSubview:btn];将控件添加到视图


设置控件监听方法的示例代码如下:

[btn addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchUpInside];


提示:

1> addTarget方法定义在UIControl类中,这意味着可以给所有继承自UIControl类的对象添加监听方法

2> 监听方法的第一个参数就是对象本身

3> 监听方法的第二个参数是监听控件的事件


8. viewDidLoad

========================================

viewDidLoad是视图加载完成后调用的方法,通常在此方法中执行视图控制器的初始化工作


在viewDidLoad方法中,一定不要忘记调用父类的方法实现!

[super viewDidLoad];


图片浏览器

 

1. 界面分析

========================================

1> 需要读取或修改属性的控件需要设置属性

// 序号标签

// 图片

// 图片描述

// 左边按钮

// 右边按钮

2> 需要监听响应事件的对象,需要添加监听方法

// 左边按钮

// 右边按钮


2. 手码懒加载创建控件的步骤

========================================

1> 定义控件属性,注意:属性必须是strong的,示例代码如下:

@property (nonatomic, strong) UIImageView *icon;

2> 在属性的getter方法中实现懒加载,示例代码如下:

- (UIImageView *)icon

{

    if (!_icon) {

        // 计算位置参数

        CGFloat imageW = 200;

        CGFloat imageX = (320 - imageW) / 2;

        CGFloat imageH = 200;

        CGFloat imageY = 80;

        // 实例化图像视图

        _icon = [[UIImageView alloc] initWithFrame:CGRectMake(imageX, imageY, imageW, imageH)];

        // 将图像视图添加到主视图

        [self.view addSubview:_icon];

    }

    return _icon;

}


使用懒加载的好处:

1> 不必将创建对象的代码全部写在viewDidLoad方法中,代码的可读性更强

2> 每个控件的getter方法中分别负责各自的实例化处理,代码彼此之间的独立性强,松耦合


3. 使用Plist文件

========================================

使用Plist文件的目的:将数据与代码分离


加载方法:

NSString *path = [[NSBundle mainBundle] pathForResource:@"ImageData" ofType:@"plist"];

_imageList = [NSArray arrayWithContentsOfFile:path];


提示:通常在方法中出现File字眼,通常需要传递文件的全路径作为参数



Tomcat

 

 

 

1. Images.xcassets中的素材

========================================

1>  只支持png格式的图片

2>  图片只支持[UIImage imageNamed]的方式实例化,但是不能从Bundle中加载

3>  在编译时,Images.xcassets中的所有文件会被打包为Assets.car的文件


2. UIImageView的序列帧动画

========================================

// 0. 是否正在动画

[self.tom isAnimating];

// 1. 设置图片的数组

[self.tom setAnimationImages:arrayM];

// 2. 设置动画时长,默认每秒播放30张图片

[self.tom setAnimationDuration:arrayM.count * 0.075];

// 3. 设置动画重复次数,默认为0,无限循环

[self.tom setAnimationRepeatCount:1];

// 4. 开始动画

[self.tom startAnimating];

// 5. 动画播放完成后,清空动画数组

[self.tom performSelector:@selector(setAnimationImages:) withObject:nil afterDelay:self.tom.animationDuration];


3. UIImage imageNamed

========================================

在图片使用完成后,不会直接被释放掉,具体释放时间由系统决定,适用于图片小,常用的图像处理


如果要释放快速释放图片,可以使用[UIImage imageWithContentsOfFile:path]实例化图像


4. 方法重构的策略

========================================

1> 将具有共性的代码复制到一个新的方法

2> 根据不同的调用情况,增加方法的参数


提示:在写程序时不要着急重构,有时候把代码先写出来,更容易看清楚如何重构才会更好!


5. Bundle中的图片素材

========================================

往项目中拖拽素材时,通常选择


1> Destination: 勾选

2> Folders:

    选择第一项:黄色文件夹

        Xcode中分文件夹,Bundle中所有所在都在同一个文件夹下,因此,不能出现文件重名的情况

        特点:

        *** 可以直接使用[NSBundle mainBundle]作为资源路径,效率高!

        *** 可以使用[UIImage imageNamed:]加载图像


    选择第二项:蓝色文件夹

        Xcode中分文件夹,Bundle中同样分文件夹,因此,可以出现文件重名的情况

        特点:

        *** 需要在[NSBundle mainBundle]的基础上拼接实际的路径,效率较差!

        *** 不能使用[UIImage imageNamed:]加载图像


6. 文件管理

========================================

[NSFileManager defaultManager]


常用方法

1> 判断文件是否存在

- (BOOL)fileExistsAtPath:(NSString *)path;

2> 将文件从源路径复制到目标路径

- (BOOL)copyItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error;

3> 删除文件

- (BOOL)removeItemAtPath:(N

 

 

 

UIImageView帧动画相关属性和方法

@property(nonatomic,copy) NSArray *animationImages; 

需要播放的序列帧图片数组(里面都是UIImage对象,会按顺序显示里面的图片)

@property(nonatomic) NSTimeInterval animationDuration;

帧动画的持续时间

@property(nonatomic) NSInteger animationRepeatCount; 

帧动画的执行次数(默认是无限循环)

- (void)startAnimating;

开始执行帧动画

- (void)stopAnimating;

停止执行帧动画

- (BOOL)isAnimating;

是否正在执行帧动画



 

方式一:有缓存(图片所占用的内存会一直停留在程序中)

+ (UIImage *)imageNamed:(NSString *)name;

name是图片的文件名



方式二:无缓存(图片所占用的内存会在一些特定操作后被清除)

+ (UIImage *)imageWithContentsOfFile:(NSString *)path

- (id)initWithContentsOfFile:(NSString *)path;

path是图片的全路径


知识扩充

 

1. @property的参数说明

========================================

ARC是苹果为了简化程序员对内存的管理,推出的一套内存管理机制

使用ARC机制,对象的申请和释放工作会在运行时,由编译器自动在代码中添加retain和release


1> strong:强指针引用的对象,在生命周期内不会被系统释放

    在OC中,对象默认都是强指针

2> weak:弱指针引用的对象,系统会立即释放

    弱指针可以指向其他已经被强指针引用的对象


@property参数使用小结:


1> 控件用weak

2> 属性对象用strong

3> 非对象类型用assign

4> 字符串NSString用copy


提示:在纯手码实现界面布局时,如果通过懒加载处理界面控件,需要使用strong强指针


2. 运行循环

========================================

在iOS的应用程序中,应用程序启动之后,系统即会创建一个运行循环监听用户的交互。


以下代码其本质是在运行循环中注册一个监听事件

[button addTarget:self action:@selector(click:) forControlEvents:UIControlEventTouchUpInside];


当运行循环检测到button的UIControlEventTouchUpInside事件时,会给视图控制器(self)发送一个click消息。

 

 

 

应用管理的笔记

 

 

1. 开发前的思路

========================================

1> 从mainBundle中加载Plist

2> 按照plist中的数据数量先确定各个appView的大小和位置

3> 使用代码创建appView中的子控件,并显示内容


2. 关于九宫格布局的计算方法

========================================

关于如何计算界面的九宫格布局,其实可以有若干种方法,不必死记课堂的代码,

要能够顺利计算出每一个小格子准确的坐标,建议:

1>  先创建若干小的视图

2>  找到自己理解比较容易的计算方法

3>  编写循环创建九宫格布局


要求:能够公用的常量尽量给抽取出来,以便增加九宫格布局的灵活性,尽量保证做到:

1> 根据要显示的数据自动调整小格子的位置和数量

2> 一旦调整了要显示的列数,仅需要修改少量的数值即可做到


3. 关于UIButton的一些补充

========================================

3.1 按钮的类型

在iOS的控件中,只有UIButton提供了类方法,可以在实例化按钮时指定按钮的不同类型。


UIButtonTypeCustom和[[UIButton alloc] init]是等价的


3.2 修改按钮字体


在UIButton中定义有两个readonly的属性:

1> titleLabel

2> imageView

@property中readonly表示不允许修改这两个属性的指针地址,但是可以修改其属性


注意:由于按钮的字体大小是所有状态共用的,因此可以通过

button.titleLabel.font= [UIFont systemFontOfSize:14.0];

修改按钮标签文本的字体大小


但是不能使用以下代码设置按钮标签的文本内容

button.titleLabel.text = @"下载";


因为按钮标签的文本内容是跟按钮的状态向关的


4. 块动画

========================================

4.1 首尾式动画


如果只是修改控件的属性,使用首尾式动画还是比较方便的,但是如果需要在动画完成后做后续处理,就不是那么方便了


[UIView beginAnimations:nil context:nil];

[UIView setAnimationDuration:1.0];

// 修改属性的动画代码

// ......

[UIView commitAnimations];


4.2 块动画


块动画相对来说比较灵活,尤为重要的是能够将动画相关的代码编写在一起,便于代码的阅读和理解


[UIView animateWithDuration:2.0 animations:^{

    // 修改控件属性动画

    label.alpha = 0.0;

} completion:^(BOOL finished) {

    // 删除控件

    [label removeFromSuperview];

}];


5. 字典转模型

========================================

5.1 字典转模型的好处:

1> 降低代码的耦合度

2> 所有字典转模型部分的代码统一集中在一处处理,降低代码出错的几率

3> 在程序中直接使用模型的属性操作,提高编码效率


模型应该提供一个可以传入字典参数的构造方法

- (instancetype)initWithDict:(NSDictionary *)dict;

+ (instancetype)xxxWithDict:(NSDictionary *)dict;


5.2 instancetype & id

1> instancetype在类型表示上,跟id一样,可以表示任何对象类型

2> instancetype只能用在返回值类型上,不能像id一样用在参数类型上

3> instancetype比id多一个好处:编译器会检测instancetype的真实类型


5.3 在模型中添加readonly属性

// 定义属性时,会生成getter&setter方法,还会生成一个带下划线的成员变量

// 而如果是readonly属性,则只会生成getter方法,同时没有成员变量

@property (nonatomic, strong, readonly) UIImage *image;


@interface LFAppInfo()

{

    UIImage *_imageABC;

}

- (UIImage *)image

{

    if (!_imageABC) {

        _imageABC = [UIImage imageNamed:self.icon];

    }

    return _imageABC;

}

在模型中合理地使用只读属性,可以进一步降低代码的耦合度。


5.4 使用数据模型的好处:

*** 调用方不用关心模型内部的任何处理细节!


6. XIB

========================================

Xib文件可以用来描述某一块局部的UI界面


XIB & Storyboard

相同点:

1>  都用来描述软件界面

2>  都用Interface Builder工具来编辑

不同点

1>  Xib是轻量级的,用来描述局部的UI界面

2>  Storyboard是重量级的,用来描述整个软件的多个界面,并且能展示多个界面之间的跳转关系


7. View的封装思路

========================================

1>  如果一个view内部的子控件比较多,一般会考虑自定义一个view,把它内部子控件的创建屏蔽起来,不让外界关心

2>  外界可以传入对应的模型数据给view,view拿到模型数据后给内部的子控件设置对应的数据

 

 

快捷键

 

新建

shift + cmd + n     新建项目

cmd + n             新建文件


视图

option + cmd + 回车 打开助理编辑器

cmd + 回车           显示主窗口

cmd + 0             导航窗口

option + cmd + 0    工具窗口

 

在.m & .h之间切换           control + cmd + 上/下

按照浏览文件的前后顺序切换     control + cmd + 左右


查看头文件       control + cmd + j

切换到对应的函数 control + 6 支持智能输入,注意输入法


运行

cmd + r             运行

cmd + .             停止

cmd + b             编译

cmd + shift + b 静态内存分析编译,可以检查程序结构上是否存在内存泄露


排版

    control + i         将选中按钮重新缩进

    cmd + ]             向右增加缩进

    cmd + [             向左减少缩进

    cmd + /             注释/取消注释,提示:取消注释时,注释双斜线必须在行首

    cmd + 向上           到文件开始位置

    cmd + 向下           到文件末尾位置


        

UIButton的常见设置

 

- (void)setTitle:(NSString *)title forState:(UIControlState)state;

设置按钮的文字


- (void)setTitleColor:(UIColor *)color forState:(UIControlState)state;

设置按钮的文字颜色


- (void)setImage:(UIImage *)image forState:(UIControlState)state; 

设置按钮内部的小图片


- (void)setBackgroundImage:(UIImage *)image forState:(UIControlState)state;

设置按钮的背景图片


设置按钮的文字字体(需要拿到按钮内部的label来设置)

btn.titleLabel.font = [UIFont systemFontOfSize:13];

- (NSString *)titleForState:(UIControlState)state; 

获得按钮的文字


- (UIColor *)titleColorForState:(UIControlState)state;

获得按钮的文字颜色


- (UIImage *)imageForState:(UIControlState)state;

获得按钮内部的小图片


- (UIImage *)backgroundImageForState:(UIControlState)state;

获得按钮的背景图片

猜图笔记

 

1. 搭建界面

1> 上半部分,固定的,可以用Storyboard实现

2> 下半部分,根据题目变化,可以考虑用代码实现


2. 图片的放大缩小

1> 放大过程

(1) 增加蒙版(遮罩),蒙版默认alpha = 0

(2) 将图片移动到视图的顶层

(3) 计算图片的目标位置,将蒙版的alpha改为0.5,动画显示


2> 缩小过程

(1) 恢复图片位置,动画显示

(2) 隐藏蒙版,将蒙版的默认alpha改为0


提示:如果按钮的alpha = 0,则不能响应用户交互


3. 加载数据,字典转模型(KVC)

1> KVC (key value coding)键值编码


KVC允许间接修改对象的属性值,是cocoa的大招!


提示:使用setValuesForKeys要求类的属性必须在字典中存在。类中的属性可以比字典中的键值多,但是不能少!


2> 下一题按钮的交互

当到达最后一题时,禁用下一题按钮。


4. 游戏的交互部分实现

1> 增加答案视图和备选答案视图,简化按钮的布局

利用九宫格算法动态添加按钮


2> 点击备选按钮,文字到上面

3> 点击答案按钮,文字到下面

4> 答案的检测

(1) 错误,需要提示用户

(2) 正确,提示用户,自动进入下一题


5. 提示功能


6. 首尾工作

1> 图标

2> 启动画面


iTools 将手机中的应用程序导出到电脑上

搜索引擎: 疯狂猜图 ipa

KVC KVO

 

KVC key value Coding 键值编码

 KVO key value Observer  键值观察

 

介绍UIScrollView

 

1.介绍UIScrollView


2.大图展示(UIScrollView使用)

    1.给scrollView添加内容

    2.设置contentSize属性(只能在代码中设置)

3.UIScrollView重要属性

    1.contentOffset(通过按钮改变其位移)

    2.contentInset

        注意:通过stroryboard设置立即生效,通过代码设置需配合contentOffect手动位移

    3.其他属性

4.喜马拉雅

    1.storyboard界面布局(减少代码)

    2.通过最后按钮的frame获取contentSize的height(CGRectGetMaxY(frame))

    3.storyboard设置contentInset(使用代码设置还需设置contentOffset)


5.scrollView代理方法

    1.要想成为代理遵守协议(UIScrollViewDelegate)

        1.声明协议(一般协议名称:控件名称 + Delegate)

        2.实现协议定义的接口方法

        3.设置代理(UIViewController成为scrollView的代理)


6.scrollView实现缩放功能

    1.要想成为代理遵守协议(UIScrollViewDelegate)

        1.声明协议(一般协议名称:控件名称 + Delegate)

    2.实现协议定义的接口方法()

        2.设置代理(UIViewController成为scrollView的代理)

    3.设置最大、最小缩放倍数(注:倍数相等时,无法缩放)

7.图片轮播功能

    1.将内容添加到scrollView中(5张图片)

    2.设置scrollView的contentSize

    3.设置翻页属性

    4.添加UIPageControl控件

    5.设置UIPageControl的页码 (利用contentSize计算页码)

    6.增加自动滚动(NSTimer实现)


 

1.介绍UIScrollView的背景

2.介绍scrollView一些属性(主要有三个)

 1>.要想使用scrollView必须做两件事

    1.设置scrollView内容

    2.设置contentSize

 2>.其他重要属性

    1.contentOffset(滚动位置)

    2.contentInset(注意:在storyborad里面设置效果不同)


3.喜马拉雅项目

 1>.分析页面结构(scrollView的frame确定)

 2>.在storyboard拖控件

 3>.重点scrollView(设置内容并且设置contentSize)

 4>.调节scrollView的显示位置

4.代理

 1>代理思想两个思想

    1).监听思想:B监听A发生了什么事情

    2).通知思想:A发生了一些事情,要通知B去做

 2>scrollView的代理使用

    1).如何成为代理(三步)

        *声明协议

        *设置代理对象self.scrollView.delegate = self;

        *实现协议方法

    2).代理监听scrollView的拖拽事件

    3).用代理实现缩放

        *成为UIScrollView的代理()

        *设置缩放对象(通过viewForZoomingInScrollView方法)

        *设置缩放为范围(maximumZoomScale、minimumZoomScale)


1.图片轮播(可以采用两种维度去分解工作:功能、MVC)

    1.UI(分析UI如何实现storyboard、代码创建)

        1.scrollView(有两件事)

        2.图片(代码增加到scrollView)

        3.UIPageControl(需设置总页数、当前页码)

    2.业务

        1.拖动(整页切换,UIScrollView的宽度为一页)

        2.页码设置(当前是第几页)

        3.自动滚动

        4.优化(timer的机制:触摸式移除,放开时再加进来)



2.智能猜图扩展(代理)

    1.UIAlertView的使用以及常见代理使用 (介绍UIWindow的level模式,level最高是UIWindowLevelStatusBar,可以覆盖status bar)

    2.UIActionSheetView简单使用以及代理使用(强调了危险操作,标红显示按钮)



3.应用管理扩展(定义协议并使用)

    1.定义协议(三步)

        *定义protocol(两种optional[代理对象可不实现]、required[代理对象必须实现])

        *增加代理属性(weak) @property (weak, nonatomic) id<LFAppInfoViewDelegate> delegate;

        *给代理发消息,调用代理的方法(需要判断代理对象是否实现了该方法,不判断调用后(编译时不会)会报错)

注意:定义协议的名称命名[类名+Delegate]、协议方法的命名规范[方法名称需要去掉前缀,并且将自己作为参数]


    2.使用代理(三步)

        *声明协议

        *设置代理对象

        *实现协议方法(本例是在代理对象[控制器] 添加一个UILabel)

iOS通知

 

 

 一个完整的通知一般包含3个属性:

- (NSString *)name; // 通知的名称

- (id)object; // 通知发布者(是谁要发布通知)

- (NSDictionary *)userInfo; // 一些额外的信息(通知发布者传递给通知接收者的信息内容)


初始化一个通知(NSNotification)对象

+ (instancetype)notificationWithName:(NSString *)aName object:(id)anObject;

+ (instancetype)notificationWithName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo;

- (instancetype)initWithName:(NSString *)name object:(id)object userInfo:(NSDictionary *)userInfo;

发布通知

通知中心(NSNotificationCenter)提供了相应的方法来帮助发布通知

- (void)postNotification:(NSNotification *)notification;

发布一个notification通知,可在notification对象中设置通知的名称、通知发布者、额外信息等


- (void)postNotificationName:(NSString *)aName object:(id)anObject;

发布一个名称为aName的通知,anObject为这个通知的发布者


- (void)postNotificationName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo;

发布一个名称为aName的通知,anObject为这个通知的发布者,aUserInfo为额外信息


注册通知监听器

通知中心(NSNotificationCenter)提供了方法来注册一个监听通知的监听器(Observer)

- (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject;

observer:监听器,即谁要接收这个通知

aSelector:收到通知后,回调监听器的这个方法,并且把通知对象当做参数传入

aName:通知的名称。如果为nil,那么无论通知的名称是什么,监听器都能收到这个通知

anObject:通知发布者。如果为anObject和aName都为nil,监听器都收到所有的通知  

- (id)addObserverForName:(NSString *)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block;

name:通知的名称

obj:通知发布者

block:收到对应的通知时,会回调这个block

queue:决定了block在哪个操作队列中执行,如果传nil,默认在当前操作队列中同步执行

取消注册通知监听器

通知中心不会保留(retain)监听器对象,在通知中心注册过的对象,必须在该对象释放前取消注册。否则,当相应的通知再次出现时,通知中心仍然会向该监听器发送消息。因为相应的监听器对象已经被释放了,所以可能会导致应用崩溃


通知中心提供了相应的方法来取消注册监听器

- (void)removeObserver:(id)observer;

- (void)removeObserver:(id)observer name:(NSString *)aName object:(id)anObject;


一般在监听器销毁之前取消注册(如在监听器中加入下列代码):

- (void)dealloc {

//[super dealloc];  非ARC中需要调用此句

    [[NSNotificationCenter defaultCenter] removeObserver:self];

}


通知和代理的选择

共同点

利用通知和代理都能完成对象之间的通信

(比如A对象告诉D对象发生了什么事情, A对象传递数据给D对象)


不同点

代理 : 一对一关系(1个对象只能告诉另1个对象发生了什么事情)

通知 : 多对多关系(1个对象能告诉N个对象发生了什么事情, 1个对象能得知N个对象发生了什么事情)  

键盘通知

我们经常需要在键盘弹出或者隐藏的时候做一些特定的操作,因此需要监听键盘的状态


键盘状态改变的时候,系统会发出一些特定的通知

UIKeyboardWillShowNotification // 键盘即将显示

UIKeyboardDidShowNotification // 键盘显示完毕

UIKeyboardWillHideNotification // 键盘即将隐藏

UIKeyboardDidHideNotification // 键盘隐藏完毕

UIKeyboardWillChangeFrameNotification // 键盘的位置尺寸即将发生改变

UIKeyboardDidChangeFrameNotification // 键盘的位置尺寸改变完毕

系统发出键盘通知时,会附带一下跟键盘有关的额外信息(字典),字典常见的key如下:

UIKeyboardFrameBeginUserInfoKey // 键盘刚开始的frame

UIKeyboardFrameEndUserInfoKey // 键盘最终的frame(动画执行完毕后)

UIKeyboardAnimationDurationUserInfoKey // 键盘动画的时间

UIKeyboardAnimationCurveUserInfoKey // 键盘动画的执行节奏(快慢)


UIDevice通知

UIDevice类提供了一个单粒对象,它代表着设备,通过它可以获得一些设备相关的信息,比如电池电量值(batteryLevel)、电池状态(batteryState)、设备的类型(model,比如iPod、iPhone等)、设备的系统(systemVersion)


通过[UIDevice currentDevice]可以获取这个单粒对象


UIDevice对象会不间断地发布一些通知,下列是UIDevice对象所发布通知的名称常量:

UIDeviceOrientationDidChangeNotification // 设备旋转

UIDeviceBatteryStateDidChangeNotification // 电池状态改变

UIDeviceBatteryLevelDidChangeNotification // 电池电量改变

UIDeviceProximityStateDidChangeNotification // 近距离传感器(比如设备贴近了使用者的脸部)       

 

 

创建头部视图

 

//创建头部视图

+(instancetype)headerViewWithTableView:(UITableView *)tableView{

    return [[self alloc]initWithTableView:tableView];

}

-(instancetype)initWithTableView:(UITableView *)tableView{

    static NSString * indentifier = @"header";

    GYLHeaderView * headerView = [tableView dequeueReusableCellWithIdentifier:indentifier];

    if (headerView == nil) {

        headerView = [[GYLHeaderView alloc]initWithReuseIdentifier:indentifier];

    }

    return headerView;

}

//但凡在init中获得的frame都是 0

-(id)initWithReuseIdentifier:(NSString *)reuseIdentifier

{

    if(self =[super initWithReuseIdentifier:reuseIdentifier])

    {

        //1.添加子控件

        //1.1添加按钮

        UIButton * btn = [UIButton buttonWithType:UIButtonTypeCustom];

        //添加按钮的点击事件

        [btn addTarget:self action:@selector(btnOnClick:) forControlEvents:UIControlEventTouchDragInside];

        //设置按钮的背景图片

        [btn setBackgroundImage:[UIImage imageNamed:@"buddy_header_bg"] forState:UIControlStateNormal];

        [btn setBackgroundImage:[UIImage imageNamed:@"buddy_header_bg_highlighted"] forState:UIControlStateHighlighted];

        //设置按钮的上的尖尖的图片

        [btn setImage:[UIImage imageNamed:@"buddy_header_arrow"] forState:UIControlStateNormal];

        //设置按钮的内容左对齐

        

        btn.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;

        //2.设置按钮的内边距,这样按钮的内容距离左边就有一定的距离

        btn.contentEdgeInsets = UIEdgeInsetsMake(0, 20, 0, 0);

        //3.设置按钮色标题和图片之间的距离

        btn.titleEdgeInsets = UIEdgeInsetsMake(0, 20, 0, 0);

//        btn.imageEdgeInsets 这是设置图片的距离

        [btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];

        

        //设置btn中的图片不填充整个imageView

        btn.imageView.contentMode  = UIViewContentModeCenter;

        //超出的部分不要进行剪切

//        btn.imageView.clipsToBounds = NO;

        btn.imageView.layer.masksToBounds = NO;

        [self addSubview:btn];

        self.btn = btn ;

        //1.2添加label

        UILabel * label = [[UILabel alloc]init];

//        label.backgroundColor = [UIColor grayColor];

        //设置文本右对齐

        label.textAlignment = NSTextAlignmentRight;

        label.textColor = [UIColor grayColor];

        [self addSubview:label];

        self.label = label;


    }

    return self;

}






// 该方法在控件的frame被改变的时候就会调用

// 该方法一般用于调整子控件的位置

- (void)layoutSubviews

{

#warning 切记重写layoutSubviews方法一定要调用父类的layoutSubviews

    [super layoutSubviews];

    //    1.设置按钮的frame

    self.btn.frame = self.bounds;

    //    2.设置label的frame

    CGFloat padding = 20;// 间隙

    CGFloat labelY = 0;

    CGFloat labelH = self.bounds.size.height;

    CGFloat labelW = 150;

    CGFloat labelX = self.bounds.size.width - padding - labelW;

    self.label.frame = CGRectMake(labelX, labelY, labelW, labelH);

}


- (void)btnOnClick:(UIButton *)btn

{

    NSLog(@"按钮被点击了");

    //     1.修改组模型的isOpen属性

    //    修改模型数据数据

    self.qqGroup.open = !self.qqGroup.isOpen;

    //    2. 刷新表格(通知代理)

    if ([self.delegate respondsToSelector:@selector(headerViewDidClickHeaderView:)]) {

        [self.delegate headerViewDidClickHeaderView:self];

    }

    

    //    3.修改btn上图片,让图片旋转

    // self.btn.imageView.transform = CGAffineTransformMakeRotation(M_PI_2);

    

    //    NSLog(@"%p %@", self, self.qqGroup.name);

}


#pragma mark - 当一个控件被添加到其它视图上的时候会调用以下方法

// 已经被添加到父视图上的时候会调用

- (void)didMoveToSuperview

{

    // 在这个方法中就快要拿到最新的被添加到tableview上的头部视图修改它的图片

    if (self.qqGroup.isOpen) {

        self.btn.imageView.transform = CGAffineTransformMakeRotation(M_PI_2);

    }

}

// 即将被添加到父视图上的时候会调用

- (void)willMoveToSuperview:(UIView *)newSuperview

{

    // NSLog(@"willMoveToSuperview");

}


- (void)setQqGroup:(GYLGroupModel *)qqGroup

{

    _qqGroup = qqGroup;

    

    //    1.设置按钮上的文字

    [self.btn setTitle:_qqGroup.name forState:UIControlStateNormal];

    //    2.设置在线人数

    self.label.text = [NSString stringWithFormat:@"%@/%d",  _qqGroup.online, (int)_qqGroup.friends.count];

}

UIApplication的特征

UIApplication对象是应用程序的象征


每一个应用都有自己的UIApplication对象,而且是单例的


通过[UIApplication sharedApplication]可以获得这个单例对象


一个iOS程序启动后创建的第一个对象就是UIApplication对象


利用UIApplication对象,能进行一些应用级别的操作


设置应用程序图标右上角的红色提醒数字

@property(nonatomic) NSInteger applicationIconBadgeNumber;


设置联网指示器的可见性

@property(nonatomic,getter=isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible;

从iOS7开始,系统提供了2种管理状态栏的方式

通过UIViewController管理(每一个UIViewController都可以拥有自己不同的状态栏)

通过UIApplication管理(一个应用程序的状态栏都由它统一管理)



在iOS7中,默认情况下,状态栏都是由UIViewController管理的,UIViewController实现下列方法就可以轻松管理状态栏的可见性和样式

状态栏的样式

- (UIStatusBarStyle)preferredStatusBarStyle; 


状态栏的可见性

(BOOL)prefersStatusBarHidden; 

…  

UIApplication有个功能十分强大的openURL:方法

- (BOOL)openURL:(NSURL*)url;


openURL:方法的部分功能有

打电话

UIApplication *app = [UIApplication sharedApplication];

[app openURL:[NSURL URLWithString:@"tel://10086"]];


发短信

[app openURL:[NSURL URLWithString:@"sms://10086"]];


发邮件

[app openURL:[NSURL URLWithString:@"mailto://12345@qq.com"]];


打开一个网页资源

[app openURL:[NSURL URLWithString:@"http://blog.csdn.net/guoyule2010"]];


打开其他app程序

 

UIPickerView的常见属性

一.UIPickerView

1.UIPickerView的常见属性

// 数据源(用来告诉UIPickerView有多少列多少行)

@property(nonatomic,assign) id<UIPickerViewDataSource> dataSource;

// 代理(用来告诉UIPickerView每1列的每1行显示什么内容,监听UIPickerView的选择)

@property(nonatomic,assign) id<UIPickerViewDelegate>   delegate;

// 是否要显示选中的指示器

@property(nonatomic)        BOOL                       showsSelectionIndicator;

// 一共有多少列

@property(nonatomic,readonly) NSInteger numberOfComponents;


2.UIPickerView的常见方法

// 重新刷新所有列

- (void)reloadAllComponents;

// 重新刷新第component列

- (void)reloadComponent:(NSInteger)component;


// 主动选中第component列的第row行

- (void)selectRow:(NSInteger)row inComponent:(NSInteger)component animated:(BOOL)animated;


// 获得第component列的当前选中的行号

- (NSInteger)selectedRowInComponent:(NSInteger)component;


3.数据源方法(UIPickerViewDataSource)

//  一共有多少列

- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView;

//  第component列一共有多少行

- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component;


4.代理方法(UIPickerViewDelegate)

//  第component列的宽度是多少

- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component;

//  第component列的行高是多少

- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component;


//  第component列第row行显示什么文字

- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component;


//  第component列第row行显示怎样的view(内容)

- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view;


//  选中了pickerView的第component列第row行

- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component;


二.UIDatePicker

1.常见属性

// datePicker的显示模式

@property (nonatomic) UIDatePickerMode datePickerMode;

// 显示的区域语言

@property (nonatomic, retain) NSLocale   *locale;


2.监听UIDatePicker的选择

* 因为UIDatePicker继承自UIControl,所以通过addTarget:...监听


三.程序启动的完整过程

1.main函数


2.UIApplicationMain

* 创建UIApplication对象

* 创建UIApplication的delegate对象


3.delegate对象开始处理(监听)系统事件(没有storyboard)

* 程序启动完毕的时候, 就会调用代理的application:didFinishLaunchingWithOptions:方法

* 在application:didFinishLaunchingWithOptions:中创建UIWindow

* 创建和设置UIWindow的rootViewController

* 显示窗口


3.根据Info.plist获得最主要storyboard的文件名,加载最主要的storyboard(有storyboard)

* 创建UIWindow

* 创建和设置UIWindow的rootViewController

* 显示窗口

UIWindow

 

1.UIWindow

* 主窗口的概念

* 新建UIWindow


2.UIViewController

* 控制器的创建方式

* 控制器view的创建方式

* view的懒加载

* loadView、viewDidLoad、viewDidUnload、didReceiveMemoryWarning


3.UINavigationController

* 通过“设置”演示基本用途

* 通过非storyboard方式,感受导航的作用

1> 创建导航控制器

2> 设置UIWindow的根控制器

3> push 1个、2个、3个 子控制器

4> 解释push的原理(栈、导航控制器的管理过程)

5> 栈底、栈顶控制器的概念

6> 如何设置导航栏上面的内容、返回文字的设置

7> pop的方法使用

8> push和addChild、viewControllers和childViewController的关系


* 通过storyboard方式,感受导航的作用


4.UIViewController的生命周期方法、AppDelegate的生命周期方法



如何创建一个控制器



控制器常见的创建方式有以下几种

通过storyboard创建


直接创建

控制器常见的创建方式有以下几种

通过storyboard创建


直接创建

GYLViewController *nj = [[GYLViewController alloc] init];


指定xib文件来创建

GYLViewController *nj = [[GYLViewController alloc] initWithNibName:@”GYLViewController" bundle:nil];


先加载storyboard文件(Test是storyboard的文件名)

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Test" bundle:nil];


接着初始化storyboard中的控制器

初始化“初始控制器”(箭头所指的控制器)

GYLViewController *nj = [storyboard instantiateInitialViewController];


通过一个标识初始化对应的控制器

GYLViewController *nj = [storyboard instantiateViewControllerWithIdentifier:@”nj"];




控制器view的延迟加载



控制器的view是延迟加载的:用到时再加载


可以用isViewLoaded方法判断一个UIViewController的view是否已经被加载


控制器的view加载完毕就会调用viewDidLoad方法




多控制器


一个iOS的app很少只由一个控制器组成,除非这个app极其简单


当app中有多个控制器的时候,我们就需要对这些控制器进行管理


有多个view时,可以用一个大的view去管理1个或者多个小view


控制器也是如此,用1个控制器去管理其他多个控制器


比如,用一个控制器A去管理3个控制器B、C、D

控制器A被称为控制器B、C、D的“父控制器”

控制器B、C、D的被称为控制器A的“子控制器”


为了便于管理控制器,iOS提供了2个比较特殊的控制器

UINavigationController

UITabBarController



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;



如何修改导航栏的内容


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


UINavigationItem有以下属性影响着导航栏的内容

左上角的返回按钮

@property(nonatomic,retain) UIBarButtonItem *backBarButtonItem;

中间的标题视图

@property(nonatomic,retain) UIView          *titleView;

中间的标题文字

@property(nonatomic,copy)   NSString        *title;

左上角的视图

@property(nonatomic,retain) UIBarButtonItem *leftBarButtonItem;

UIBarButtonItem *rightBarButtonItem  右上角的视图

@property(nonatomic,retain) UIBarButtonItem *rightBarButtonItem;


Segue的属性


每一个Segue对象,都有3个属性

唯一标识

@property (nonatomic, readonly) NSString *identifier;

来源控制器

@property (nonatomic, readonly) id sourceViewController;

目标控制器

@property (nonatomic, readonly) id destinationViewController;



Segue的类型


根据Segue的执行(跳转)时刻,Segue可以分为2大类型

自动型:点击某个控件后(比如按钮),自动执行Segue,自动完成界面跳转


手动型:需要通过写代码手动执行Segue,才能完成界面跳转



自动型Segue


按住Control键,直接从控件拖线到目标控制器


点击“登录”按钮后,就会自动跳转到右边的控制器


如果点击某个控件后,不需要做任何判断,一定要跳转到下一个界面,建议使用“自动型Segue”

手动型Segue


按住Control键,从来源控制器拖线到目标控制器


手动型的Segue需要设置一个标识(如右图)



在恰当的时刻,使用perform方法执行对应的Segue

[self performSegueWithIdentifier:@"login2contacts" sender:nil];

// Segue必须由来源控制器来执行,也就是说,这个perform方法必须由来源控制器来调用


如果点击某个控件后,需要做一些判断,也就是说:满足一定条件后才跳转到下一个界面,建议使用“手动型Segue”

performSegueWithIdentifier:sender:

利用performSegueWithIdentifier:方法可以执行某个Segue,完成界面跳转


接下来研究performSegueWithIdentifier:sender:方法的完整执行过程

[self performSegueWithIdentifier:@“login2contacts” sender:nil];

// 这个self是来源控制器

根据identifier去storyboard中找到对应的线,新建UIStoryboardSegue对象

设置Segue对象的sourceViewController(来源控制器)

新建并且设置Segue对象的destinationViewController(目标控制器)

performSegueWithIdentifier:sender:

调用sourceViewController的下面方法,做一些跳转前的准备工作并且传入创建好的Segue对象

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender;

// 这个sender是当初performSegueWithIdentifier:sender:中传入的sender


调用Segue对象的- (void)perform;方法开始执行界面跳转操作

取得sourceViewController所在的UINavigationController

调用UINavigationController的push方法将destinationViewController压入栈中,完成跳转


Sender参数的传递

[self performSegueWithIdentifier:@“login2contacts” sender:@“jack”];


- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender;


控制器的数据传递


控制器之间的数据传递主要有2种情况:顺传和逆传

顺传

控制器的跳转方向: A  C

数据的传递方向    : A  C

数据的传递方式    :  在A的prepareForSegue:sender:方法中根据segue参数取得destinationViewController, 也就是控制器C, 直接给控制器C传递数据

(要在C的viewDidLoad方法中取得数据,来赋值给界面上的UI控件)



逆传

控制器的跳转方向: A  C

数据的传递方向    : C  A

数据的传递方式    :  让A成为C的代理, 在C中调用A的代理方法,通过代理方法的参数传递数据给A



面试

 

1. 字符串

如果IDE没有代码自动补全功能,所以你应该记住下面的这些方法。

toCharArray() // 获得字符串对应的char数组

Arrays.sort()  // 数组[排序](/tag/%e6%8e%92%e5%ba%8f "View all posts in 排序")

Arrays.toString(char[] a) // 数组转成字符串

charAt(int x) // 获得某个索引处的字符

length() // 字符串长度

length // 数组大小

2. 链表

在Java中,链表的实现非常简单,每个节点Node都有一个值val和指向下个节点的链接next。

class Node {

    int val;

    Node next;

 

    Node(int x) {

        val = x;

        next = null;

    }

}

链表两个著名的应用是栈Stack和队列Queue。

栈:

class Stack{

    Node top; 

 

    public Node peek(){

        if(top != null){

            return top;

        }

 

        return null;

    }

 

    public Node pop(){

        if(top == null){

            return null;

        }else{

            Node temp = new Node(top.val);

            top = top.next;

            return temp;    

        }

    }

 

    public void push(Node n){

        if(n != null){

            n.next = top;

            top = n;

        }

    }

}

队列:

class Queue{

    Node first, last;

 

    public void enqueue(Node n){

        if(first == null){

            first = n;

            last = first;

        }else{

            last.next = n;

            last = n;

        }

    }

 

    public Node dequeue(){

        if(first == null){

            return null;

        }else{

            Node temp = new Node(first.val);

            first = first.next;

            return temp;

        }   

    }

}

3.

这里的树通常是指二叉树,每个节点都包含一个左孩子节点和右孩子节点,像下面这样:

class TreeNode{

    int value;

    TreeNode left;

    TreeNode right;

}

下面是与树相关的一些概念:

  1. 平衡 vs. 非平衡:平衡二叉树中,每个节点的左右子树的深度相差至多为1(1或0)。
  2. 满二叉树(Full Binary Tree):除叶子节点以为的每个节点都有两个孩子。
  3. 完美二叉树(Perfect Binary Tree):是具有下列性质的满二叉树:所有的叶子节点都有相同的深度或处在同一层次,且每个父节点都必须有两个孩子。
  4. 完全二叉树(Complete Binary Tree):二叉树中,可能除了最后一个,每一层都被完全填满,且所有节点都必须尽可能想左靠。

译者注:完美二叉树也隐约称为完全二叉树。完美二叉树的一个例子是一个人在给定深度的祖先图,因为每个人都一定有两个生父母。完全二叉树可以看成是可以有若干额外向左靠的叶子节点的完美二叉树。疑问:完美二叉树和满二叉树的区别?(参考:http://xlinux.nist.gov/dads/HTML/perfectBinaryTree.html

4.

图相关的问题主要集中在深度优先搜索(depth first search)和广度优先搜索(breath first search)。

下面是一个简单的图广度优先搜索的实现。

1) 定义GraphNode

class GraphNode{ 

    int val;

    GraphNode next;

    GraphNode[] neighbors;

    boolean visited;

 

    GraphNode(int x) {

        val = x;

    }

 

    GraphNode(int x, GraphNode[] n){

        val = x;

        neighbors = n;

    }

 

    public String toString(){

        return "value: "+ this.val; 

    }

}

2) 定义一个队列Queue

class Queue{

    GraphNode first, last;

 

    public void enqueue(GraphNode n){

        if(first == null){

            first = n;

            last = first;

        }else{

            last.next = n;

            last = n;

        }

    }

 

    public GraphNode dequeue(){

        if(first == null){

            return null;

        }else{

            GraphNode temp = new GraphNode(first.val, first.neighbors);

            first = first.next;

            return temp;

        }   

    }

}

3) 用队列Queue实现广度优先搜索

public class GraphTest {

 

    public static void main(String[] args) {

        GraphNode n1 = new GraphNode(1); 

        GraphNode n2 = new GraphNode(2); 

        GraphNode n3 = new GraphNode(3); 

        GraphNode n4 = new GraphNode(4); 

        GraphNode n5 = new GraphNode(5); 

 

        n1.neighbors = new GraphNode[]{n2,n3,n5};

        n2.neighbors = new GraphNode[]{n1,n4};

        n3.neighbors = new GraphNode[]{n1,n4,n5};

        n4.neighbors = new GraphNode[]{n2,n3,n5};

        n5.neighbors = new GraphNode[]{n1,n3,n4};

 

        breathFirstSearch(n1, 5);

    }

 

    public static void breathFirstSearch(GraphNode root, int x){

        if(root.val == x)

            System.out.println("find in root");

 

        Queue queue = new Queue();

        root.visited = true;

        queue.enqueue(root);

 

        while(queue.first != null){

            GraphNode c = (GraphNode) queue.dequeue();

            for(GraphNode n: c.neighbors){

 

                if(!n.visited){

                    System.out.print(n + " ");

                    n.visited = true;

                    if(n.val == x)

                        System.out.println("Find "+n);

                    queue.enqueue(n);

                }

            }

        }

    }

}

Output:

value: 2 value: 3 value: 5 Find value: 5

value: 4

5. 排序

 

下面是不同排序算法的时间复杂度,你可以去wiki看一下这些算法的基本思想。

Algorithm

Average Time

Worst Time

Space

冒泡排序

n^2

n^2

1

选择排序

n^2

n^2

1

Counting Sort

n+k

n+k

n+k

Insertion sort

n^2

n^2

 

Quick sort

n log(n)

n^2

 

Merge sort

n log(n)

n log(n)

depends

另外,这里有一些实现/演示:: Counting sortMergesort、 Quicksort、 InsertionSort

6. 递归 vs. 迭代

对程序员来说,递归应该是一个与生俱来的思想(a built-in thought),可以通过一个简单的例子来说明。

问题: 有n步台阶,一次只能上1步或2步,共有多少种走法。

步骤1:找到走完前n步台阶和前n-1步台阶之间的关系。

为了走完n步台阶,只有两种方法:从n-1步台阶爬1步走到或从n-2步台阶处爬2步走到。如果f(n)是爬到第n步台阶的方法数,那么f(n) = f(n-1) + f(n-2)。

步骤2: 确保开始条件是正确的。

f(0) = 0;

f(1) = 1;

public static int f(int n){

    if(n <= 2) return n;

    int x = f(n-1) + f(n-2);

    return x;

}

递归方法的时间复杂度是n的指数级,因为有很多冗余的计算,如下:

f(5)

f(4) + f(3)

f(3) + f(2) + f(2) + f(1)

f(2) + f(1) + f(1) + f(0) + f(1) + f(0) + f(1)

f(1) + f(0) + f(1) + f(1) + f(0) + f(1) + f(0) + f(1)

直接的想法是将递归转换为迭代:

public static int f(int n) {

 

    if (n <= 2){

        return n;

    }

 

    int first = 1, second = 2;

    int third = 0;

 

    for (int i = 3; i <= n; i++) {

        third = first + second;

        first = second;

        second = third;

    }

 

    return third;

}

对这个例子而言,迭代花费的时间更少,你可能也想看看Recursion vs Iteration

7. 动态规划

动态规划是解决下面这些性质类问题的技术:

  1. 一个问题可以通过更小子问题的解决方法来解决(译者注:即问题的最优解包含了其子问题的最优解,也就是最优子结构性质)。
  2. 有些子问题的解可能需要计算多次(译者注:也就是子问题重叠性质)。
  3. 子问题的解存储在一张表格里,这样每个子问题只用计算一次。
  4. 需要额外的空间以节省时间。

爬台阶问题完全符合上面的四条性质,因此可以用动态规划法来解决。

public static int[] A = new int[100];

 

public static int f3(int n) {

    if (n <= 2)

        A[n]= n;

 

    if(A[n] > 0)

        return A[n];

    else

        A[n] = f3(n-1) + f3(n-2);//store results so only calculate once!

    return A[n];

}

**8. 位操作

**

位操作符:

OR (|)

AND (&)

XOR (^)

Left Shift (<<)

Right Shift (>>)

Not (~)

1|0=1

1&0=0

1^0=1

0010<<2=1000

1100>>2=0011

~1=0

获得给定数字n的第i位:(i从0计数并从右边开始)

public static boolean getBit(int num, int i){

    int result = num & (1<<i);

 

    if(result == 0){

        return false;

    }else{

        return true;

    }

例如,获得数字10的第2位:

i=1, n=10

1<<1= 10

1010&10=10

10 is not 0, so return true;

9. 概率问题

解决概率相关的问题通常需要很好的规划了解问题(formatting the problem),这里刚好有一个这类问题的简单例子:

一个房间里有50个人,那么至少有两个人生日相同的概率是多少?(忽略闰年的事实,也就是一年365天)

计算某些事情的概率很多时候都可以转换成先计算其相对面。在这个例子里,我们可以计算所有人生日都互不相同的概率,也就是:365/365 * 364/365 * 363/365 * … * (365-49)/365,这样至少两个人生日相同的概率就是1 – 这个值。

public static double caculateProbability(int n){

    double x = 1; 

 

    for(int i=0; i<n; i++){

        x *=  (365.0-i)/365.0;

    }

 

    double pro = Math.round((1-x) * 100);

    return pro/100;

calculateProbability(50) = 0.97

10. 排列组合

组合和排列的区别在于次序是否关键。

如果你有任何问题请在下面评论。

参考/推荐资料:

1. Binary tree

2. Introduction to Dynamic Programming

3. UTSA Dynamic Programming slides

4. Birthday paradox

  1. Cracking the Coding Interview: 150 Programming Interview Questions and Solutions, Gayle Laakmann McDowell

数据存取

 

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

 

XML属性列表(plist)归档

Preference(偏好设置)

NSKeyedArchiver归档(NSCoding)

SQLite3 

Core Data


 

应用沙盒

 

每个iOS应用都有自己的应用沙盒(应用沙盒就是文件系统目录),与其他文件系统隔离。应用必须待在自己的沙盒里,其他应用不能访问该沙盒

应用沙盒的文件系统目录,如下图所示(假设应用的名称叫Layer)


 

模拟器应用沙盒的根路径在: (guoyule是用户名, 8.1是模拟器版本)

/Users/guoyule/Library/Application Support/iPhone Simulator/8./1Applications

 

应用沙盒结构分析

 

应用程序包:(上图中的Layer)包含了所有的资源文件和可执行文件

Documents:保存应用运行时生成的需要持久化的数据,iTunes同步设备时会备份该目录。例如,游戏应用可将游戏存档保存在该目录


tmp:保存应用运行时所需的临时数据,使用完毕后再将相应的文件从该目录删除。应用没有运行时,系统也可能会清除该目录下的文件。iTunes同步设备时不会备份该目录


Library/Caches:保存应用运行时生成的需要持久化的数据,iTunes同步设备时不会备份该目录。一般存储体积大、不需要备份的非重要数据


Library/Preference:保存应用的所有偏好设置,iOS的Settings(设置)应用会在该目录中查找应用的设置信息。iTunes同步设备时会备份该目录


应用沙盒目录的常见获取方式


 

沙盒根目录:NSString *home = NSHomeDirectory();


Documents:(2种方式)

利用沙盒根目录拼接”Documents”字符串

NSString *home = NSHomeDirectory();

NSString *documents = [home stringByAppendingPathComponent:@"Documents"];

// 不建议采用,因为新版本的操作系统可能会修改目录名

利用NSSearchPathForDirectoriesInDomains函数

// NSUserDomainMask 代表从用户文件夹下找

// YES 代表展开路径中的波浪字符“~”

NSArray *array =  NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, NO);

// 在iOS中,只有一个目录跟传入的参数匹配,所以这个集合里面只有一个元素

NSString *documents = [array objectAtIndex:0];


tmp:NSString *tmp = NSTemporaryDirectory();


Library/Caches:(跟Documents类似的2种方法)

利用沙盒根目录拼接”Caches”字符串

利用NSSearchPathForDirectoriesInDomains函数(将函数的第2个参数改为:NSCachesDirectory即可)


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


 

属性列表


属性列表是一种XML格式的文件,拓展名为plist

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


属性列表-归档NSDictionary

将一个NSDictionary对象归档到一个plist属性列表中

// 将数据封装成字典

NSMutableDictionary *dict = [NSMutableDictionary dictionary];

[dict setObject:@"郭玉乐" forKey:@"name"];

[dict setObject:@"18501956963" forKey:@"phone"];

[dict setObject:@"24" forKey:@"age"];

// 将字典持久化到Documents/stu.plist文件中

[dict writeToFile:path atomically:YES];


 

属性列表-恢复NSDictionary


读取属性列表,恢复NSDictionary对象

// 读取Documents/stu.plist的内容,实例化NSDictionary

NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path];

NSLog(@"name:%@", [dict objectForKey:@"name"]);

NSLog(@"phone:%@", [dict objectForKey:@"phone"]);

NSLog(@"age:%@", [dict objectForKey:@"age"]);

偏好设置

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

每个应用都有个NSUserDefaults实例,通过它来存取偏好设置

比如,保存用户名、字体大小、是否自动登录

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

[defaults setObject:@"guoyule" forKey:@"username"];

[defaults setFloat:18.0f forKey:@"text_size"];

[defaults setBool:YES forKey:@"auto_login"];

读取上次保存的设置

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

NSKeyedArchiver

如果对象是NSString、NSDictionary、NSArray、NSData、NSNumber等类型,可以直接用NSKeyedArchiver进行归档和恢复

不是所有的对象都可以直接用这种方法进行归档,只有遵守了NSCoding协议的对象才可以

NSCoding协议有2个方法:

encodeWithCoder:

每次归档对象时,都会调用这个方法。一般在这个方法里面指定如何归档对象中的每个实例变量,可以使用encodeObject:forKey:方法归档实例变量

initWithCoder:

每次从文件中恢复(解码)对象时,都会调用这个方法。一般在这个方法里面指定如何解码文件中的数据为对象的实例变量,可以使用decodeObject:forKey方法解码实例变量

NSKeyedArchiver-归档NSArray

归档一个NSArray对象到Documents/array.archive

NSArray *array = [NSArray arrayWithObjects:@”a”,@”b”,nil];

[NSKeyedArchiver archiveRootObject:array toFile:path];

恢复(解码)NSArray对象

NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithFile:path];

NSKeyedArchiver-归档Person对象(Person.h

@interface Person : NSObject<NSCoding>

@property (nonatomic, copy) NSString *name;

@property (nonatomic, assign) int age;

@property (nonatomic, assign) float height;

@end

NSKeyedArchiver-归档Person对象(Person.m

@implementation Person

- (void)encodeWithCoder:(NSCoder *)encoder {

    [encoder encodeObject:self.name forKey:@"name"];

    [encoder encodeInt:self.age forKey:@"age"];

    [encoder encodeFloat:self.height forKey:@"height"];

}

- (id)initWithCoder:(NSCoder *)decoder {

    self.name = [decoder decodeObjectForKey:@"name"];

    self.age = [decoder decodeIntForKey:@"age"];

    self.height = [decoder decodeFloatForKey:@"height"];

    return self;

}

- (void)dealloc {

    [super dealloc];

    [_name release];

}

@end

NSKeyedArchiver-归档Person对象(编码和解码)

归档(编码)

Person *person = [[[Person alloc] init] autorelease];

person.name = @"GUOYULE";

person.age = 24;

person.height = 1.78f;

[NSKeyedArchiver archiveRootObject:person toFile:path];

恢复(解码)

Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:path];

NSKeyedArchiver-归档对象的注意

如果父类也遵守了NSCoding协议,请注意:

应该在encodeWithCoder:方法中加上一句

[super encodeWithCode:encode];

确保继承的实例变量也能被编码,即也能被归档

应该在initWithCoder:方法中加上一句

self = [super initWithCoder:decoder];

确保继承的实例变量也能被解码,即也能被恢复

NSData

使用archiveRootObject:toFile:方法可以将一个对象直接写入到一个文件中,但有时候可能想将多个对象写入到同一个文件中,那么就要使用NSData来进行归档对象

NSData可以为一些数据提供临时存储空间,以便随后写入文件,或者存放从磁盘读取的文件内容。可以使用[NSMutableData data]创建可变数据空间

NSData-归档2Person对象到同一文件中

归档(编码)

// 新建一块可变数据区

NSMutableData *data = [NSMutableData data];

// 将数据区连接到一个NSKeyedArchiver对象

NSKeyedArchiver *archiver = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:data] autorelease];

// 开始存档对象,存档的数据都会存储到NSMutableData中

[archiver encodeObject:person1 forKey:@"person1"];

[archiver encodeObject:person2 forKey:@"person2"];

// 存档完毕(一定要调用这个方法)

[archiver finishEncoding];

// 将存档的数据写入文件

[data writeToFile:path atomically:YES];

NSData-从同一文件中恢复2Person对象

恢复(解码)

// 从文件中读取数据

NSData *data = [NSData dataWithContentsOfFile:path];

// 根据数据,解析成一个NSKeyedUnarchiver对象

NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];

Person *person1 = [unarchiver decodeObjectForKey:@"person1"];

Person *person2 = [unarchiver decodeObjectForKey:@"person2"];

// 恢复完毕

[unarchiver finishDecoding];


利用归档实现深复制


比如对一个Person对象进行深复制

// 临时存储person1的数据

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:person1];

// 解析data,生成一个新的Person对象

Student *person2 = [NSKeyedUnarchiver unarchiveObjectWithData:data];

// 分别打印内存地址

NSLog(@"person1:0x%x", person1); // person1:0x7177a60

NSLog(@"person2:0x%x", person2); // person2:0x7177cf0


SQLite3

SQLite3是一款开源的嵌入式关系型数据库,可移植性好、易使用、内存开销小

SQLite3是无类型的,意味着你可以保存任何类型的数据到任意表的任意字段中。比如下列的创表语句是合法的:

   create table t_person(name, age);

   为了保证可读性,建议还是把字段类型加上:

   create table t_person(name text, age integer);

SQLite3常用的5种数据类型:text、integer、float、boolean、blob

在iOS中使用SQLite3,首先要添加库文件libsqlite3.dylib和导入主头文件

 

创建、打开、关闭数据库


创建或打开数据库

// path为:~/Documents/person.db

sqlite3 *db;

int result = sqlite3_open([path UTF8String], &db); 


代码解析:

sqlite3_open()将根据文件路径打开数据库,如果不存在,则会创建一个新的数据库。如果result等于常量SQLITE_OK,则表示成功打开数据库

sqlite3 *db:一个打开的数据库实例

数据库文件的路径必须以C字符串(而非NSString)传入


关闭数据库:sqlite3_close(db);

执行不返回数据的SQL语句

执行创表语句

char *errorMsg;  // 用来存储错误信息

char *sql = "create table if not exists t_person(id integer primary key autoincrement, name text, age integer);";

int result = sqlite3_exec(db, sql, NULL, NULL, &errorMsg);


代码解析:

sqlite3_exec()可以执行任何SQL语句,比如创表、更新、插入和删除操作。但是一般不用它执行查询语句,因为它不会返回查询到的数据

sqlite3_exec()还可以执行的语句:

开启事务:begin transaction;

回滚事务:rollback;

提交事务:commit;

带占位符插入数据

char *sql = "insert into t_person(name, age) values(?, ?);";

sqlite3_stmt *stmt;

if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {

    sqlite3_bind_text(stmt, 1, "母鸡", -1, NULL);

    sqlite3_bind_int(stmt, 2, 27);

}

if (sqlite3_step(stmt) != SQLITE_DONE) {

    NSLog(@"插入数据错误");

}

sqlite3_finalize(stmt);


代码解析:

sqlite3_prepare_v2()返回值等于SQLITE_OK,说明SQL语句已经准备成功,没有语法问题


 

sqlite3_bind_text():大部分绑定函数都只有3个参数

第1个参数是sqlite3_stmt *类型

第2个参数指占位符的位置,第一个占位符的位置是1,不是0

第3个参数指占位符要绑定的值

第4个参数指在第3个参数中所传递数据的长度,对于C字符串,可以传递-1代替字符串的长度

第5个参数是一个可选的函数回调,一般用于在语句执行后完成内存清理工作

sqlite_step():执行SQL语句,返回SQLITE_DONE代表成功执行完毕

sqlite_finalize():销毁sqlite3_stmt *对象


查询数据

 

char *sql = "select id,name,age from t_person;";

sqlite3_stmt *stmt;

if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) {

    while (sqlite3_step(stmt) == SQLITE_ROW) {

        int _id = sqlite3_column_int(stmt, 0);

        char *_name = (char *)sqlite3_column_text(stmt, 1);

        NSString *name = [NSString stringWithUTF8String:_name];

        int _age = sqlite3_column_int(stmt, 2);

        NSLog(@"id=%i, name=%@, age=%i", _id, name, _age);

    }

}

sqlite3_finalize(stmt);

代码解析

sqlite3_step()返回SQLITE_ROW代表遍历到一条新记录

sqlite3_column_*()用于获取每个字段对应的值,第2个参数是字段的索引,从0开始

 



 

NSManagedObject


通过Core Data从数据库取出的对象,默认情况下都是NSManagedObject对象

NSManagedObject的工作模式有点类似于NSDictionary对象,通过键-值对来存取所有的实体属性

setValue:forKey: 存储属性值(属性名为key)

valueForKey: 获取属性值(属性名为key)



搭建Core Data上下文环境


从应用程序包中加载模型文件

NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];

传入模型,初始化NSPersistentStoreCoordinator

NSPersistentStoreCoordinator *psc = [[[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model] autorelease];

构建SQLite文件路径

NSString *docs = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];

NSURL *url = [NSURL fileURLWithPath:[docs stringByAppendingPathComponent:@"person.data"]];

添加持久化存储库,这里使用SQLite作为存储库

NSError *error = nil;

NSPersistentStore *store = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:nil error:&error];

if (store == nil) { // 直接抛异常

    [NSException raise:@"添加数据库错误" format:@"%@", [error localizedDescription]];

}

初始化上下文,设置persistentStoreCoordinator属性

NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];

context.persistentStoreCoordinator = psc;

// 用完之后,还是要[context release];

添加数据

传入上下文,创建一个Person实体对象

NSManagedObject *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];

设置简单属性

[person setValue:@"guoyule" forKey:@"name"];

[person setValue:[NSNumber numberWithInt:24] forKey:@"age"];

传入上下文,创建一个Card实体对象

NSManagedObject *card = [NSEntityDescription insertNewObjectForEntityForName:@"Card" inManagedObjectContext:context];

[card setValue:@"4414241933432" forKey:@"no"];

设置Person和Card之间的关联关系

[person setValue:card forKey:@"card"];

利用上下文对象,将数据同步到持久化存储库

NSError *error = nil;

BOOL success = [context save:&error];

if (!success) {

    [NSException raise:@"访问数据库错误" format:@"%@", [error localizedDescription]];

}

// 如果是想做更新操作:只要在更改了实体对象的属性后调用[context save:&error],就能将更改的数据同步到数据库

查询数据

初始化一个查询请求

NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];

设置要查询的实体

NSEntityDescription *desc = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:context];

设置排序(按照age降序)

NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:NO];

request.sortDescriptors = [NSArray arrayWithObject:sort];

设置条件过滤(name like '%guoyule-1%')

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name like %@", @"*guoyule-1*"];

request.predicate = predicate;


执行请求

NSError *error = nil;

NSArray *objs = [context executeFetchRequest:request error:&error];

if (error) {

    [NSException raise:@"查询错误" format:@"%@", [error localizedDescription]];

}

遍历数据

for (NSManagedObject *obj in objs) {

    NSLog(@"name=%@", [obj valueForKey:@"name"]

}

删除数据

传入需要删除的实体对象

[context deleteObject:managedObject];

将结果同步到数据库

NSError *error = nil;

[context save:&error];

if (error) {

    [NSException raise:@"删除错误" format:@"%@", [error localizedDescription]];

}

Core Data的延迟加载

Core Data不会根据实体中的关联关系立即获取相应的关联对象

比如通过Core Data取出Person实体时,并不会立即查询相关联的Card实体;当应用真的需要使用Card时,才会查询数据库,加载Card实体的信息

创建NSManagedObject的子类

默认情况下,利用Core Data取出的实体都是NSManagedObject类型的,能够利用键-值对来存取数据

但是一般情况下,实体在存取数据的基础上,有时还需要添加一些业务方法来完成一些其他任务,那么就必须创建NSManagedObject的子类

创建NSManagedObject的子类

那么生成一个Person实体对象就应该这样写

Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];

person.name = @"GUOYULE";

person.age = [NSNumber numberWithInt:24];


Card *card = [NSEntityDescription insertNewObjectForEntityForName:@”Card" inManagedObjectContext:context];

card.no = @”4414245465656";

person.card = card;

 

 

 

数据存储代码

 

- (IBAction)savebtn:(id)sender {

//    GYLPerson * p = [[GYLPerson alloc]init];

//    p.name = @"guoyule";

//    p.age = 24;

//    p.hight = 178.0f;

    GYLStudent * stu = [[GYLStudent alloc]init];

    stu.name = @"guoyule";

    stu.age = 24;

    stu.hight = 178.0f;

    stu.email = @"guoyulehit@icloud.com";


    


    //2.获取文件路径

    NSString * docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory , NSUserDomainMask , YES) lastObject];

    //尾加文件名

    NSString * path = [docPath stringByAppendingString:@"guoyule.arc"];

    NSLog(@"path = %@",path);

//    3.将自己定义的对象保存到文件中

    [NSKeyedArchiver archiveRootObject:stu toFile:path];

    

    


}


- (IBAction)readBtn:(id)sender {


    NSString * docPth = [NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory,NSUserDomainMask,YES) lastObject];

    NSString * path = [docPth stringByAppendingString:@"guoyule.arc"];

//    2.从文件中读取对象

    GYLStudent * guo = [NSKeyedUnarchiver unarchiveObjectWithFile:path];

    NSLog(@"%@,%d,%.1f,%@",guo.name,guo.age,guo.hight,guo.email);


}

 

 

偏好设置

 

//

//  ViewController.m

//  偏好设置

//

//  Created by GuoYule on 15/3/10.

//  Copyright (c) 2015年 GuoYule. All rights reserved.

//


#import "ViewController.h"


@interface ViewController ()

- (IBAction)saveBtn:(id)sender;

- (IBAction)readBtn:(id)sender;


@end


@implementation ViewController


- (void)viewDidLoad {

    [super viewDidLoad];

    // Do any additional setup after loading the view, typically from a nib.

}


- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}


- (IBAction)saveBtn:(id)sender {


//    偏好设置是专门用来保存应用程序的配置的信息的,一般情况下不要在偏好设置中保存其他的数据

//    如果利用系统的偏好设置来保存数据,默认就是存储在Preferences文件夹下面的

//    偏好设置会将所有的数据保存到同一个文件夹中

//    获取默认的NSUserDefaults

    NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];

    // 保存数据(如果设置数据之后没有同步, 会在将来某一时间点自动将数据保存到Preferences文件夹下面)

    [defaults setObject:@"guoyule" forKey:@"name"];

    [defaults setFloat:178.0 forKey:@"hight"];

    [defaults setInteger:24 forKey:@"age"];

//    让NSUserDefaults马上保存

    [defaults synchronize];

    

}


- (IBAction)readBtn:(id)sender {

    NSUserDefaults * de = [NSUserDefaults standardUserDefaults];

    NSLog(@"name = %@,hight = %f,age = %ld",[de objectForKey:@"name"],[de floatForKey:@"hight"],(long)[de integerForKey:@"age"]);

}

@end

 

 

modal的注意点

[self dismissViewControllerAnimated:YES completion:^{

        NSLog(@"OK!!!");

    }];



 

/*

 如果控制器之间的关系比较紧密一般用 UINavigationController

 如果控制器之间的关系不是很紧密可以用Modal

*/

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

{

    NSLog(@"prepareForSegue");

    // 1.取出目标控制器

     UINavigationController *nav = segue.destinationViewController;

    NJTwoViewController *two = ( NJTwoViewController *)nav.topViewController; // 获取导航控制器栈顶的控制器

    

    // 2.给目标控制器传递数据

    two.name = @"lnj";

}


UITabBarController

 

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    //    1.创建window

    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];

    self.window.backgroundColor = [UIColor whiteColor];

    //    2.给window设置根控制器

    UITabBarController * tab = [[UITabBarController alloc]init];

    // 2.设置UITabBarController为winow的根控制器

    self.window.rootViewController = tab;

    //创建并添加自子控制器

    UIViewController * vc1 = [[UIViewController alloc]init];

    vc1.tabBarItem.title = @"消息";

    vc1.tabBarItem.image = [UIImage imageNamed:@"tab_recent_nor"];

    vc1.tabBarItem.badgeValue = @"111";

    vc1.view.backgroundColor = [UIColor blackColor];

    UIViewController * vc2 = [[UIViewController alloc]init];

    vc2.tabBarItem.title = @"联系人";

    vc2.tabBarItem.image = [UIImage imageNamed:@"tab_buddy_nor"];

    //    vc2.tabBarItem.badgeValue = @"111";

    vc2.view.backgroundColor = [UIColor blueColor];

    

    UIViewController * vc3 = [[UIViewController alloc]init];

    vc3.tabBarItem.title = @"空间";

    vc3.tabBarItem.image = [UIImage imageNamed:@"tab_qworld_nor"];

    vc3.tabBarItem.badgeValue = @"11";

    vc3.view.backgroundColor = [UIColor greenColor];

    

    UIViewController * vc4 = [[UIViewController alloc]init];

    vc4.tabBarItem.title = @"设置";

    vc4.tabBarItem.image = [UIImage imageNamed:@"tab_me_nor"];

    //    vc4.tabBarItem.badgeValue = @"111";

    vc4.view.backgroundColor = [UIColor grayColor];

    [tab addChildViewController:vc1];

    [tab addChildViewController:vc2];

    [tab addChildViewController:vc3];

    [tab addChildViewController:vc4];

    

    [self.window makeKeyAndVisible];

    

    return YES;

}

 

 

tableView的刷新

 

1.tableView的刷新

1> 数据刷新的总体步骤

* 修改模型数据

* 刷新表格(刷新界面)


2> 刷新表格(刷新界面)的方法

* 全局刷新(每一行都会重新刷新)

- (void)reloadData;


* 局部刷新(使用前提: 刷新前后, 模型数据的个数不变)

- (void)reloadRows:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;


* 局部删除(使用前提: 模型数据减少的个数 == indexPaths的长度)

- (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;


2.@property属性的用法

* weak(assign) :  代理\UI控件

* strong(retain) : 其他对象(除代理\UI控件\字符串以外的对象)

* copy : 字符串

* assign : 非对象类型(基本数据类型int\float\BOOL\枚举\结构体)

 

 

绘图

- (void)drawRect:(CGRect)rect {

    //    这里是Layer Graphics Context

    CGContextRef  ctx = UIGraphicsGetCurrentContext();

//    绘制图形

//    设置起点

    CGContextMoveToPoint(ctx, 10, 10);

//    设置终点 ctx 是上下文

    CGContextAddLineToPoint(ctx, 10, 100);

//    绘制图形(渲染到layer上)view

    CGContextStrokePath(ctx);

    

}

 

// Only override drawRect: if you perform custom drawing.

// An empty implementation adversely affects performance during animation.

- (void)drawRect:(CGRect)rect {

    // Drawing code

//    获得图形的上下文

    CGContextRef ctx = UIGraphicsGetCurrentContext();

//    绘制图形的起点

    CGContextMoveToPoint(ctx, 50, 0);

//    设置第二个点

    CGContextAddLineToPoint(ctx, 0, 100);

//    设置第三个点

    CGContextAddLineToPoint(ctx, 100, 100);

//    进行封口操作

    CGContextClosePath(ctx);

//    渲染

    CGContextStrokePath(ctx);

    CGContextAddRect(ctx, CGRectMake(50, 100, 50, 100));

    [[UIColor redColor]set];

//    CGContextStrokePath(ctx);

    CGContextFillPath(ctx);

    

}



 

// Only override drawRect: if you perform custom drawing.

// An empty implementation adversely affects performance during animation.

//当自定义的view即将显示的时候 就会调用这个方法

- (void)drawRect:(CGRect)rect {

    //    这里是Layer Graphics Context

    CGContextRef  ctx = UIGraphicsGetCurrentContext();

//    绘制图形

//    设置起点

    CGContextMoveToPoint(ctx, 10, 10);

//    设置终点 ctx 是上下文

    CGContextAddLineToPoint(ctx, 10, 100);

    CGContextAddLineToPoint(ctx, 100, 100);

//    设置绘图状态

//    设置线条颜色

    CGContextSetRGBStrokeColor(ctx, 1.0, 0, 0, 1.0);

//    设置线条宽度

    CGContextSetLineWidth(ctx, 10.2);

//    设置线条圆头

    CGContextSetLineCap(ctx, kCGLineCapRound);

//    设置线条转角的圆形

    CGContextSetLineJoin(ctx, kCGLineJoinRound);

//    绘制图形(渲染到layer上)view

    CGContextStrokePath(ctx);

    

}





 

新建一个起点

void CGContextMoveToPoint(CGContextRef c, CGFloat x, CGFloat y)


添加新的线段到某个点

void CGContextAddLineToPoint(CGContextRef c, CGFloat x, CGFloat y)


添加一个矩形

void CGContextAddRect(CGContextRef c, CGRect rect)


添加一个椭圆

void CGContextAddEllipseInRect(CGContextRef context, CGRect rect)


添加一个圆弧

void CGContextAddArc(CGContextRef c, CGFloat x, CGFloat y,

  CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise)


绘制平行线

 

/**

 *  绘制平行线

 *

 *  @param rect <#rect description#>

 */

- (void)drawRect:(CGRect)rect {

//    注意这是一个C语言的函数 没有*

    CGContextRef  ctx = UIGraphicsGetCurrentContext();

    CGPoint addLines[] = {CGPointMake(10.0, 200),CGPointMake(50.0, 100),CGPointMake(90.0, 200),CGPointMake(130.0, 100),CGPointMake(170.0, 200),CGPointMake(210.0, 100)};

//    绘制

    CGContextStrokeLineSegments(ctx, addLines, sizeof(addLines)/sizeof(addLines[0]));

}

 

渐变

 

- (void)drawRect:(CGRect)rect {

    

//    绘制渐变色

    CGContextRef ctx = UIGraphicsGetCurrentContext();

    CGColorSpaceRef color = CGColorSpaceCreateDeviceRGB();//创建色彩空间

//    开始设置颜色

    UIColor * start = [UIColor blueColor];

    CGFloat * startColorComp = (CGFloat *)CGColorGetComponents([start CGColor]);

//    设置结束颜色

    UIColor *end = [UIColor yellowColor];

    CGFloat * endColorComp = (CGFloat *)CGColorGetComponents([end CGColor]);

//  创建颜色分量数组

    CGFloat colorComponents[8] = {

        startColorComp[0],startColorComp[1],startColorComp[2],startColorComp[3],endColorComp[0],endColorComp[1],endColorComp[2],endColorComp[3],

    };

//    指定渐变开始位置和渐变结束位置

    CGFloat colorIndices[2] = {0.0f ,1.0f,};

//    创建渐变

    CGGradientRef gradient = CGGradientCreateWithColorComponents(color, (const CGFloat *)&colorComponents, (const CGFloat *)&colorIndices, 2);

    CGPoint startPoint ,endPoint;

    startPoint = CGPointMake(120, 260);

    endPoint = CGPointMake(200.0, 200);

    CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint, 0);

}

 

图片的添加

 

- (void)drawRect:(CGRect)rect {

    UIImage * image = [UIImage imageNamed:@"abc"];

    CGContextRef ctx = UIGraphicsGetCurrentContext();

    CGRect re = CGRectMake(60, 60, 60, 60);

    CGContextClipToRect(ctx, CGRectMake(0, 0, 320, 480));

    CGContextDrawTiledImage(ctx, re, image.CGImage);

}

 

文字处理

 

// Only override drawRect: if you perform custom drawing.

// An empty implementation adversely affects performance during animation.

- (void)drawRect:(CGRect)rect {

    // Drawing code

    /**

     *  CGTextDrawingMode mode 枚举kCGTextFill,

     kCGTextStroke,描边

     kCGTextFillStroke, 描边加填充

     kCGTextInvisible,

     kCGTextFillClip,  kCGTextFill,填充

     kCGTextStrokeClip,

     kCGTextFillStrokeClip,

     kCGTextClip

     */

   


//    CGContextSetTextDrawingMode(<#CGContextRef c#>, CGTextDrawingMode mode)

    

    

    

    CGContextRef ctx = UIGraphicsGetCurrentContext();

    //    取得系统的字体

    CGFontRef font = CGFontCreateWithFontName((CFStringRef)@"Helvetica");

    //    自定义字体

    CGContextSetFont(ctx, font);

    //    设置字体字号

    CGContextSetFontSize(ctx, 100.0);

    CGContextSetRGBFillColor(ctx, 0.0, 1.0, 0, 1.0);

    CGContextSetRGBStrokeColor(ctx, 1.0, 1.0, 0, 1.0);

    CGContextSetTextMatrix(ctx, CGAffineTransformMakeScale(1.0, -1.0));

    CGPoint point[] = {

        CGPointMake(20, -120),

        CGPointMake(120, -200),

        CGPointMake(220, -300),

    };

    CGGlyph glyphs[23] ={90,91,92};

    //    填充

    CGContextSetTextDrawingMode(ctx, kCGTextFill);

    CGContextShowGlyphsAtPositions(ctx, &glyphs[0], &point[0], 1);

//    描边

    CGContextSetTextDrawingMode(ctx, kCGTextStroke);

    CGContextShowGlyphsAtPositions(ctx, &glyphs[1], &point[1], 1);

//    

    CGContextSetTextDrawingMode(ctx, kCGTextFillStrokeClip);

    CGContextShowGlyphsAtPositions(ctx, &glyphs[2], &point[2], 1);

    



}

 

 

OC 画图

 

#import "GYLcircle.h"


@implementation GYLcircle


- (void)drawRect:(CGRect)rect

{

    

    // 1.获取上下文

    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // 画圆

    CGContextAddArc(ctx, 100, 100, 50, 0, 2 * M_PI, 0);

    

    // 3.渲染 (注意, 画线只能通过空心来画)

    CGContextFillPath(ctx);

}


- (void)test3

{

    

    // 1.获取上下文

    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // 2.画饼状图

    // 画线

    CGContextMoveToPoint(ctx, 100, 100);

    CGContextAddLineToPoint(ctx, 100, 150);

    // 画圆弧

    CGContextAddArc(ctx, 100, 100, 50, M_PI_2, M_PI, 0);

    //    CGContextAddArc(ctx, 100, 100, 50, -M_PI, M_PI_2, 1);

    

    // 关闭路径

    CGContextClosePath(ctx);

    

    

    // 3.渲染 (注意, 画线只能通过空心来画)

    CGContextFillPath(ctx);

    //    CGContextStrokePath(ctx);

}


- (void)test2

{

    // 画圆弧

    // 1.获取上下文

    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // 2.画圆弧

    // x/y 圆心

    // radius 半径

    // startAngle 开始的弧度

    // endAngle 结束的弧度

    // clockwise 画圆弧的方向 (0 顺时针, 1 逆时针)

    //    CGContextAddArc(ctx, 100, 100, 50, -M_PI_2, M_PI_2, 0);

    CGContextAddArc(ctx, 100, 100, 50, M_PI_2, M_PI, 0);

    CGContextClosePath(ctx);

    

    // 3.渲染

    //     CGContextStrokePath(ctx);

    CGContextFillPath(ctx);

}

- (void)test

{

    // 画圆

    // 1.获取上下文

    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // 2.画圆

    CGContextAddEllipseInRect(ctx, CGRectMake(50, 100, 50, 50));

    

    [[UIColor greenColor] set];

    

    // 3.渲染

    //    CGContextStrokePath(ctx);

    CGContextFillPath(ctx);

}




// Only override drawRect: if you perform custom drawing.

// An empty implementation adversely affects performance during animation.

- (void)drawRect:(CGRect)rect {

    // Drawing code

//    加载的图片

    UIImage * image = [UIImage imageNamed:@"bg"];

//    利用OC方法将图片绘制到layer上面

//1    将图片绘制到指定的位置 point

    [image drawAtPoint:CGPointMake(0, 0)];

//    2利用drawInRect的方法将图片绘制到layer上面去 ,是通过拉伸原有的图片进行的绘制

    [image drawInRect:CGRectMake(0, 0, 200, 200)];

//    3.利用drawAsPatternInRect的方法将图片绘制到layer上面去,是通过平铺图片实现的

    [image drawAsPatternInRect:CGRectMake(0, 0, 320, 480)];

    

    

}


-(void)test

{

//    画文字

    NSString * str = @"Lenny";

//    获取上下文

//    CGContextRef ctx = UIGraphicsGetCurrentContext();

//    绘图

    // 不推荐使用C语言的方法绘制文字, 因为quraz2d中的坐标系和UIkit中的坐标系不一致, 绘制出来的文字是颠倒的, 而且通过C语言的方法绘制文字相当麻烦

    //    CGContextSelectFont(<#CGContextRef c#>, <#const char *name#>, <#CGFloat size#>, <#CGTextEncoding textEncoding#>)

    //    CGContextShowText(ctx, <#const char *string#>, <#size_t length#>)

    

    

//    绘制矩形

//    获取上下文

    CGContextRef ctx = UIGraphicsGetCurrentContext();

//    绘图

    CGContextAddRect(ctx, CGRectMake(50, 50, 100, 100));

//    渲染

    CGContextStrokePath(ctx);

    NSMutableDictionary * md = [NSMutableDictionary dictionary];

//    设置字体的大小

    md[NSFontAttributeName] = [UIFont systemFontOfSize:50];

//    设置字体的颜色

    md[NSForegroundColorAttributeName] = [UIColor redColor];

//    设置字体的背景颜色

    md[NSBackgroundColorAttributeName] = [UIColor whiteColor];

//    将字体画到指定的点的位置

    [str drawAtPoint:CGPointMake(10, 10) withAttributes:md];

//    将文字绘制到指定的范围内, 如果一行装不下会自动换行, 当文字超出范围后就不显示

    [str drawInRect:CGRectMake(50, 50, 100, 100) withAttributes:md];

    


}

 

矩阵操作

 

// 画四边形

    CGContextRef ctx = UIGraphicsGetCurrentContext();

    

    // 保存上下文

    CGContextSaveGState(ctx);

    

    // 注意:设置矩阵操作必须在添加绘图信息之前

    CGContextRotateCTM(ctx, M_PI_4);

//    CGContextScaleCTM(ctx, 0.5, 0.5);

//    CGContextTranslateCTM(ctx, 0, 150);

    

    CGContextAddRect(ctx, CGRectMake(200, 100, 100, 100));

    

    

    CGContextRestoreGState(ctx);

    

    CGContextAddEllipseInRect(ctx, CGRectMake(20, 20, 100, 100));

    

    CGContextStrokePath(ctx);

    

 

 

图形上下文栈

 

- (void)drawRect:(CGRect)rect

{

    

    

    // 获取上下文

    CGContextRef ctx = UIGraphicsGetCurrentContext();


    // 保存一份最纯洁的图形上下文

    // 调用一次该方法就会拷贝一个上下文到栈中

    CGContextSaveGState(ctx);

//CGContextSaveGState(ctx);


    

    // 第一条线

    // 利用图形上下文保存绘图信息

    CGContextMoveToPoint(ctx, 150, 20);

    CGContextAddLineToPoint(ctx, 20, 100);

    

    // 设置第一条线的状态

    CGContextSetLineWidth(ctx, 10);

    CGContextSetLineCap(ctx, kCGLineCapRound);

    [[UIColor redColor] set];

    

    // 渲染

    CGContextStrokePath(ctx);

    

    

    // 还原开始保存的那份最纯洁的图形上下文

    CGContextRestoreGState(ctx);

    

    // 第二条线

    CGContextMoveToPoint(ctx, 80, 30);

    CGContextAddLineToPoint(ctx, 80, 150);

    /*

    // 清空状态

    CGContextSetLineWidth(ctx, 5);

    CGContextSetLineCap(ctx, kCGLineCapButt);

    [[UIColor greenColor] set];

    */

   

    // 还原开始保存的那份最纯洁的图形上下文

    CGContextRestoreGState(ctx);

    /*

    // 第3条线

    CGContextMoveToPoint(ctx, 200, 30);

    CGContextAddLineToPoint(ctx, 80, 150);

    */



    // 渲染

    CGContextStrokePath(ctx);

 

 

Quartz 2D图片的剪切

 

- (void)drawRect:(CGRect)rect

{

    // Drawing code

    

    // 画圆, 以便于以后指定可以显示内容范围

    CGContextRef ctx = UIGraphicsGetCurrentContext();

    

    CGContextAddEllipseInRect(ctx, CGRectMake(100, 100, 50, 50));

    

    // 指定上下文中可以显示内容的范围

    CGContextClip(ctx);

    

    CGContextStrokePath(ctx);

    

    /*

     CGContextRef ctx = UIGraphicsGetCurrentContext();


    // 2. 绘制三角形

    // 设置起点

    CGContextMoveToPoint(ctx, 100, 10);

    // 设置第二个点

    CGContextAddLineToPoint(ctx, 50, 100);

    // 设置第三个点

    CGContextAddLineToPoint(ctx, 150, 100);

    // 设置终点

    //    CGContextAddLineToPoint(ctx, 100, 10);

    // 关闭起点和终点

    CGContextClosePath(ctx);

    

    // 指定上下文中可以显示内容的范围

    // 注意,指定范围(也就是指点剪切的方法一定要在绘制范围之前调用)

    CGContextClip(ctx);

    

    // 3.渲染图形到layer上

    CGContextStrokePath(ctx);

    */

    

    UIImage *image = [UIImage imageNamed:@"me"];

    

    // 按照原始大小绘制

    [image  drawAtPoint:CGPointMake(100, 100)];

    

    

    CGContextAddRect(ctx, CGRectMake(10, 10, 100, 100));

    

    CGContextFillPath(ctx);

    

}

 

动态刷帧

- (void)drawRect:(CGRect)rect

{

    CGContextRef ctx = UIGraphicsGetCurrentContext();

    //     进行清屏的操作

    CGContextClearRect(ctx, rect);

    

    self.imageY += 5;

    

    

    if (self.imageY > rect.size.height) {

        self.imageY = 0;

    }

    // Drawing code

    UIImage *image = [UIImage imageNamed:@"snow"];

    [image   drawAtPoint:CGPointMake(10, self.imageY)];

    

    //    [self setNeedsDisplay];

    

}


-(void)awakeFromNib

{

    

    NSLog(@"awakeFromNib");

    // 创建CADisplayLink, 默认每秒60次

    CADisplayLink *display = [CADisplayLink displayLinkWithTarget:self selector:@selector(updataImage)];

    // 将CADisplayLink加入到消息循环中

    [display addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

}

- (void)updataImage

{

    [self setNeedsDisplay];

}

 

画四边形的方法

 

// 画四边形

    CGContextRef ctx = UIGraphicsGetCurrentContext();

    

    //    1.第一种方式, 通过连接固定的点绘制四边形

    //    CGContextMoveToPoint(ctx, 0, 0);

    //    CGContextAddLineToPoint(ctx, <#CGFloat x#>, <#CGFloat y#>)

    //    CGContextAddLineToPoint(ctx, <#CGFloat x#>, <#CGFloat y#>)

    //    CGContextAddLineToPoint(ctx, <#CGFloat x#>, <#CGFloat y#>)

    //    CGContextAddLineToPoint(ctx, <#CGFloat x#>, <#CGFloat y#>)

    

    

    //    2.指定起点和宽高绘制四边形

    //    CGContextAddRect(ctx, CGRectMake(10, 10, 100, 100));

    //    CGContextStrokePath(ctx);

    

    // 3.两步合为一部

    //    CGContextStrokeRect(ctx, CGRectMake(10, 10, 100, 100));

    //    CGContextFillRect(ctx, CGRectMake(10, 10, 100, 100));

    

    // 4.通过OC的方法绘制实心的四边形, 注意没有空心的方法

    //    UIRectFill(CGRectMake(10, 10, 100, 100));

    

    // 5.通过绘制线条设置宽度

    CGContextMoveToPoint(ctx, 10, 10);

    CGContextAddLineToPoint(ctx, 100, 100);

    CGContextSetLineWidth(ctx, 50);

    CGContextStrokePath(ctx);

 

 

内存管理

 

- (void)drawRect:(CGRect)rect

{

    // 1.获取上下文

    CGContextRef ctx = UIGraphicsGetCurrentContext();

    

    // 2.绘制图形

    /*

    // 设置起点

    CGContextMoveToPoint(ctx, 10, 10);

    // 设置终点

    CGContextAddLineToPoint(ctx, 100, 100);

    

    // 3.画圆

    CGContextAddEllipseInRect(ctx, CGRectMake(50, 50, 50, 50));

    */

    

   

    // 2.创建路径(一个path就代表一条路径)

    // 但凡通过quarzt2d中的带有create/ copy / retain 方法创建出来的值都必须手动的释放

    CGMutablePathRef path = CGPathCreateMutable();

    // 设置起点

    CGPathMoveToPoint(path, NULL, 10, 10);

    // 设置终点

    CGPathAddLineToPoint(path, NULL, 100, 100);

    // 将路径添加到上下文中

    CGContextAddPath(ctx, path);

    

    // 3.再创建一条路径用于保存圆

     CGMutablePathRef path2 = CGPathCreateMutable();

    // 在path中添加画的路径

    CGPathAddEllipseInRect(path2, NULL, CGRectMake(50, 50, 50, 50));

    CGContextAddPath(ctx, path2);

    

    // 3.渲染'

    CGContextStrokePath(ctx);

    

    

    // 释放前面创建的两条路径

    CGPathRelease(path);

    CGPathRelease(path2);

    

    // 下面这种方式也可以释放路径

    

    CFRelease(path);

    CFRelease(path2);

 

 

bitmap

 

- (void)viewDidLoad {

    [super viewDidLoad];

    //    0 创建一个bitmap的上文

    

    UIGraphicsGetImageFromCurrentImageContext();

    

    /*

     size :指定将来创建出来的bitmap的大小

     opaque : YES:不透明  NO:透明

     scale: 缩放比例

     创建出来的bitmap就对应一个UIImage

     */

    

    UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 200), NO, 0);

    

    //      1.获取文件的下文

    CGContextRef ctx = UIGraphicsGetCurrentContext();

    //    2.绘图

    CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, 20, 20));

    

    //3.渲染

    CGContextStrokePath(ctx);

    //    4.生成图片

    UIImage * image = UIGraphicsGetImageFromCurrentImageContext();

    //    5.显示生成图片

    self.vi.image = image;

    //    6.保存生成的图片 但是要先保存成nsdata数据 然后再写到本地

    // 先将图片转换为二进制数据, 然后再将图片写到文件中

    NSData * data =UIImagePNGRepresentation(image);

    [data writeToFile:@"/Users/guoyule/Desktop/Lenny.png" atomically:YES];

    

    

}

 

生成水印catagray

 

#import <UIKit/UIKit.h>


@interface UIImage (LK)


/**

 *  生成水印

 *

 *  @param bgName  背景图片

 *  @param logNmae 水印图片

 *

 *  @return 生成好的图片(带水印的图片)

 */

+ (instancetype)imageWithBackgroundImageName:(NSString *)bgName log:(NSString *)logNmae;



@end


 

#import "UIImage+LK.h"


@implementation UIImage (LK)



+ (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;

}

@end



UIResponder事件处理 _Lenny Kwok

 

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;


加速计事件

- (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;


UIView的触摸事件处理

UIView是UIResponder的子类,可以覆盖下列4个方法处理不同的触摸事件

一根或者多根手指开始触摸view,系统会自动调用view的下面方法

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event


一根或者多根手指在view上移动,系统会自动调用view的下面方法(随着手指的移动,会持续调用该方法)

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event


一根或者多根手指离开view,系统会自动调用view的下面方法

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event


触摸结束前,某个系统事件(例如电话呼入)会打断触摸过程,系统会自动调用view的下面方法

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event


提示:touches中存放的都是UITouch对象


UITouch


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


一根手指对应一个UITouch对象


UITouch的作用

保存着跟手指相关的信息,比如触摸的位置、时间、阶段


当手指移动时,系统会更新同一个UITouch对象,使之能够一直保存该手指在的触摸位置


当手指离开屏幕时,系统会销毁相应的UITouch对象


提示:iPhone开发中,要避免使用双击事件!


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;


- (CGPoint)locationInView:(UIView *)view;

返回值表示触摸在view上的位置

这里返回的位置是针对view的坐标系的(以view的左上角为原点(0, 0))

调用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置


- (CGPoint)previousLocationInView:(UIView *)view;

该方法记录了前一个触摸点的位置


UIEvent


每产生一个事件,就会产生一个UIEvent对象


UIEvent:称为事件对象,记录事件产生的时刻和类型


常见属性

事件类型

@property(nonatomic,readonly) UIEventType     type;

@property(nonatomic,readonly) UIEventSubtype  subtype;


事件产生的时间

@property(nonatomic,readonly) NSTimeInterval  timestamp;


UIEvent还提供了相应的方法可以获得在某个view上面的触摸对象(UITouch)


touchesevent参数


一次完整的触摸过程,会经历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的个数可以判断出是单点触摸还是多点触摸


事件的产生和传递


发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中


UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)


主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件


找到合适的视图控件后,就会调用视图控件的touches方法来作具体的事件处理

touchesBegan…

touchesMoved…

touchedEnded…

 

 

重点触摸事件的传递

 

触摸事件的传递是从父控件传递到子控件

点击了绿色的view:

UIApplication -> UIWindow -> 白色 -> 绿色

点击了蓝色的view:

UIApplication -> UIWindow -> 白色 -> 橙色 -> 蓝色

点击了黄色的view:

UIApplication -> UIWindow -> 白色 -> 橙色 -> 蓝色 -> 黄色


如果父控件不能接收触摸事件,那么子控件就不可能接收到触摸事件(掌握)


如何找到最合适的控件来处理事件?

自己是否能接收触摸事件?

触摸点是否在自己身上?

从后往前遍历子控件,重复前面的两个步骤

如果没有符合条件的子控件,那么就自己最适合处理



1.事件的完整处理过程:

1> 先将事件对象由上往下传递(由父控件传递给子控件), 找到最合适的控件来处理这个事件

2> 调用最合适控件的touches.....方法

3> 如果调用了[super touches...];就会将事件顺着响应者链条往上传递,传递给上一个响应者

4> 接着就会调用上一个响应者的touches.....方法


2.什么是响应者链条?

1> 响应者链条是由多个响应者对象连接起来的链条(什么是响应者对象: 能处理事件的对象)

2> 利用响应者链条, 能让多个控件 处理 同一个触摸事件

3> 怎么利用链条往上传递?谁是上一个响应者


3.谁是上一个响应者(nextResponder)

1> 如果当前这个view是控制器的view, 那么控制器就是上一个响应者

2> 如果当前这个view不是控制器的view, 那么父控件就是上一个响应者

 

 

 

触摸事件

 

- (void)viewDidLoad

{

    [super viewDidLoad];

// Do any additional setup after loading the view, typically from a nib.

//    1.打开交互

    self.iconView.userInteractionEnabled = YES;

    

//    2.创建手势识别器

    UITapGestureRecognizer * tp = [[UITapGestureRecognizer alloc]init];

//    3.添加手势到View

    [self.iconView addGestureRecognizer:tp];

//    表示连着按下两次

    tp.numberOfTapsRequired = 2;

//    表示手指的根数

    tp.numberOfTouchesRequired = 2;

   NSInteger x= tp.numberOfTouches;

    NSLog(@"%ld",(long)x);

//    4.监听手势识别器

    [tp addTarget:self action:@selector(change)];

}

-(void)change

{

    NSLog(@"他摸我");

    

}

 

 

UIGestureRecognizer

 

为了完成手势识别,必须借助于手势识别器----UIGestureRecognizer


利用UIGestureRecognizer,能轻松识别用户在某个view上面做的一些常见手势


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

UITapGestureRecognizer(敲击)

UIPinchGestureRecognizer(捏合,用于缩放)

UIPanGestureRecognizer(拖拽)

UISwipeGestureRecognizer(轻扫)

UIRotationGestureRecognizer(旋转)

UILongPressGestureRecognizer(长按)


 

UITapGestureRecognizer


每一个手势识别器的用法都差不多,比如UITapGestureRecognizer的使用步骤如下

创建手势识别器对象

UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] init];


设置手势识别器对象的具体属性

// 连续敲击2次

tap.numberOfTapsRequired = 2;

// 需要2根手指一起敲击

tap.numberOfTouchesRequired = 2;


添加手势识别器到对应的view上

[self.iconView addGestureRecognizer:tap];


监听手势的触发

[tap addTarget:self action:@selector(tapIconView:)];



 

手势识别的状态



typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {

    // 没有触摸事件发生,所有手势识别的默认状态

    UIGestureRecognizerStatePossible,

    // 一个手势已经开始但尚未改变或者完成时

    UIGestureRecognizerStateBegan,

    // 手势状态改变

    UIGestureRecognizerStateChanged,

    // 手势完成

    UIGestureRecognizerStateEnded,

    // 手势取消,恢复至Possible状态

    UIGestureRecognizerStateCancelled, 

    // 手势失败,恢复至Possible状态

    UIGestureRecognizerStateFailed,

    // 识别到手势识别

    UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded

};

捏合旋转

// 该方法返回的BOOL值决定了view是否能够同时响应多个手势

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer

{

    NSLog(@"%@ - %@", gestureRecognizer.class, otherGestureRecognizer.class);

    return YES;

}



- (void)pichTest

{

    // 捏合手势

    UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] init];

    pinch.delegate = self;

    

    [self.iconView addGestureRecognizer:pinch];

    [pinch addTarget:self action:@selector(pinchView:)];

}



- (void)pinchView:(UIPinchGestureRecognizer *)pinch

{

//    NSLog(@"捏合事件 %.1f", pinch.scale);

//    self.iconView.transform = CGAffineTransformMakeScale(pinch.scale, pinch.scale);

    // 1.0 * 0.9

    self.iconView.transform = CGAffineTransformScale(self.iconView.transform, pinch.scale, pinch.scale);

    

    pinch.scale = 1.0;

}


- (void)rotationTest

{

    // 旋转

    UIRotationGestureRecognizer *gesture = [[UIRotationGestureRecognizer alloc] init];

    gesture.delegate = self;

    

    [self.iconView addGestureRecognizer:gesture];

    [gesture addTarget:self action:@selector(rotationView:)];

}


- (void)rotationView:(UIRotationGestureRecognizer *)gesture

{

//    NSLog(@"旋转事件 %.1f", gesture.rotation);

    

    

//    每次从最初的位置开始

//    self.iconView.transform = CGAffineTransformMakeRotation(gesture.rotation);

    

//    在传入的transform基础上递增一个弧度

    self.iconView.transform = CGAffineTransformRotate(self.iconView.transform, gesture.rotation);

    // 将旋转的弧度清零(注意不是将图片旋转的弧度清零, 而是将当前手指旋转的弧度清零)

    gesture.rotation = 0;// 如果理解不了 , 记住就OK

}

 

拖拽

 

- (void)viewDidLoad

{

    [super viewDidLoad];

UIPanGestureRecognizer  *pan = [[UIPanGestureRecognizer alloc] init];

    [self.customView addGestureRecognizer:pan];

    

    [pan addTarget:self action:@selector(panView:)];

}


- (void)panView:(UIPanGestureRecognizer *)pan

{

    // 返回的值是以手指按下的点为原点

    // 1 2 3 4 5

    CGPoint point = [pan translationInView:pan.view];

    

    NSLog(@"拖拽事件 %@", NSStringFromCGPoint(point));

    CGPoint temp = self.customView.center;

    temp.x += point.x;

    temp.y += point.y;

    self.customView.center = temp;

    

    // 理解不了就记住就OK

    [pan setTranslation:CGPointZero inView:pan.view];

}

 

 

长按和轻扫

 

- (void)viewDidLoad

{

    [super viewDidLoad];

    

    // 向上

    UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] init];

    // 设置轻扫的方向

    swipe.direction = UISwipeGestureRecognizerDirectionUp;

    [self.customView addGestureRecognizer:swipe];

    [swipe addTarget:self action:@selector(swipeView)];

    

    // 向下

    UISwipeGestureRecognizer *swipe2 = [[UISwipeGestureRecognizer alloc] init];

    // 设置轻扫的方向

    swipe2.direction = UISwipeGestureRecognizerDirectionDown;

    [self.customView addGestureRecognizer:swipe2];

    [swipe2 addTarget:self action:@selector(swipeView2)];

    

    // 左边

    UISwipeGestureRecognizer *swipe3 = [[UISwipeGestureRecognizer alloc] init];

    // 设置轻扫的方向

    swipe3.direction = UISwipeGestureRecognizerDirectionLeft;

    [self.customView addGestureRecognizer:swipe3];

    [swipe3 addTarget:self action:@selector(swipeView3)];

    

    // 右边

    UISwipeGestureRecognizer *swipe4 = [[UISwipeGestureRecognizer alloc] init];

    // 设置轻扫的方向

    swipe4.direction = UISwipeGestureRecognizerDirectionRight;

    [self.customView addGestureRecognizer:swipe4];

    [swipe4 addTarget:self action:@selector(swipeView4)];

    

    

    

}

- (void)swipeView4

{

    NSLog(@"轻扫事件右");

}


- (void)swipeView3

{

    NSLog(@"轻扫事件左");

}


- (void)swipeView2

{

    NSLog(@"轻扫事件下");

}


- (void)swipeView

{

    NSLog(@"轻扫事件上");

}


- (void)test

{

    // 长按事件

    // 1.创建手势识别器

    UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] init];

    // 1.1设置长按手势识别器的属性

    //    longPress.minimumPressDuration = 5;

    

    // 手指按下后事件响应之前允许手指移动的偏移位

    longPress.allowableMovement = 50;

    

    

    // 2.添加手势识别器到View

    [self.customView addGestureRecognizer:longPress];

    

    // 3.监听手势识别器

    [longPress addTarget:self action:@selector(longPressView)];

}


 -(void)longPressView

{

    NSLog(@"长按事件");

}

 

 

CAlayer

 

- (void)viewDidLoad {

    [super viewDidLoad];

//    设置layer边框

    self.imageView.layer.borderWidth = 10;

//    设置边框的颜色

    self.imageView.layer.borderColor = [UIColor redColor].CGColor;//注意borderColor的类型

//    设置layer的圆角(设置主图层的圆角)

    self.imageView.layer.cornerRadius = 10;

//    设置超出主图层的部分进行剪切操作

//    self.imageView.layer.masksToBounds = YES;

//    self.imageView.clipsToBounds = YES;

//    设置的image不是展示在主图层上的,是展示在子图层上的

    self.imageView.layer.contents = (id)[UIImage imageNamed:@"LoginScreen"].CGImage;

//    设置阴影的颜色

    self.imageView.layer.shadowColor  = [UIColor blackColor].CGColor;

//    设置阴影的偏移位

//    如果是正数,代表向右移动

//    上

    self.imageView.layer.shadowOffset =  CGSizeMake(10, 10);

//    设置阴影的透明度0~1 1 完全不透明 0 完全透明

    self.imageView.layer.shadowOpacity =1;

    

     }



 

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

{

//    self.imageView.transform = CGAffineTransformMakeTranslation(0, 100);

//    self.imageView.layer.transform = CATransform3DMakeTranslation(0, 100, 0);

//    NSValue *v = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, -200, 0)];

//    [self.imageView.layer setValue:v forKey:@"transform"];

//    [self.imageView.layer setValue:@(100) forKey:@"transform.translation.x"];

//    self.imageView.transform = CGAffineTransformMakeRotationxx(M_PI_4);

//    self.imageView.layer.transform = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);

    [self.imageView.layer setValue:@"100" forKey:@"transform.translation.x"];

}



- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

   }

-(void)test

{

    //    设置layer边框

    self.imageView.layer.borderWidth = 10;

    //    设置边框的颜色

    self.imageView.layer.borderColor = [UIColor redColor].CGColor;//注意borderColor的类型

    //    设置layer的圆角(设置主图层的圆角)

    self.imageView.layer.cornerRadius = 10;

    //    设置超出主图层的部分进行剪切操作

    //    self.imageView.layer.masksToBounds = YES;

    //    self.imageView.clipsToBounds = YES;

    //    设置的image不是展示在主图层上的,是展示在子图层上的

    self.imageView.layer.contents = (id)[UIImage imageNamed:@"LoginScreen"].CGImage;

    //    设置阴影的颜色

    self.imageView.layer.shadowColor  = [UIColor blackColor].CGColor;

    //    设置阴影的偏移位

    //    如果是正数,代表向右移动

    //    上

    self.imageView.layer.shadowOffset =  CGSizeMake(10, 10);

    //    设置阴影的透明度0~1 1 完全不透明 0 完全透明

    self.imageView.layer.shadowOpacity =1;

    


}

-(void)test2

{

    self.picyture.layer.borderWidth = 10;

    self.imageView.layer.borderColor = [UIColor redColor].CGColor;

    self.imageView.layer.cornerRadius = 10;

    //    self.iconView.layer.masksToBounds = YES;

    //    self.iconView.layer.bounds = CGRectMake(0, 0, 200, 200);

    //    self.iconView.layer.position = CGPointMake(100 , 100);

    self.picyture.layer.masksToBounds = YES;

    self.picyture.layer.bounds = CGRectMake(0, 0, 200, 200);

    self.picyture.layer.position = CGPointMake(400, 200);


}


CALayer 新建

- (void)viewDidLoad

{

    [super viewDidLoad];

    // 如果一个控制是另外一个控件的子控件, 那么这个控件中的layer也是另外一个控件的子layer

//     NSLog(@"star - %@", self.view.layer.sublayers);

    CALayer *layer = [CALayer layer];

    layer.backgroundColor = [UIColor redColor].CGColor;

    layer.bounds = CGRectMake(0, 0, 100, 100);

//    layer.position = CGPointMake(200, 200);

//    layer.contents = (id)[UIImage imageNamed:@"me"].CGImage;

    [self.view.layer addSublayer:layer];


    

}


- (void)test

{

    

    NSLog(@"star - %@", self.view.layer.sublayers);

    

    // 1.创建layer

    // CALayer *layer = [[CALayer alloc] init];

    CALayer *layer = [CALayer layer];

    layer.backgroundColor = [UIColor redColor].CGColor;

    layer.bounds = CGRectMake(0, 0, 100, 100);

    layer.position = CGPointMake(200, 200);

    layer.borderWidth = 10;

    layer.cornerRadius = 10;

    // 将layer添加在界面上

    [self.view.layer addSublayer:layer];

    

    //    NSLog(@"%@", layer.superlayer); // 获取layer的父视图

    NSLog(@"end - %@", self.view.layer.sublayers);

    

    

    //

    //    UIView *view = [[UIView alloc] init];

    //    view.superview;

    //    view.subviews;

    //    [self.view addSubview:view];

}

关于CALayer的疑惑

关于CALayer的疑惑


 

首先

CALayer是定义在QuartzCore框架中的

CGImageRef、CGColorRef两种数据类型是定义在CoreGraphics框架中的

UIColor、UIImage是定义在UIKit框架中的


其次

QuartzCore框架和CoreGraphics框架是可以跨平台使用的,在iOS和Mac OS X上都能使用

但是UIKit只能在iOS中使用


为了保证可移植性,QuartzCore不能使用UIImage、UIColor,只能使用CGImageRef、CGColorRef


UIViewCALayer的选择



通过CALayer,就能做出跟UIImageView一样的界面效果


既然CALayer和UIView都能实现相同的显示效果,那究竟该选择谁好呢?

其实,对比CALayer,UIView多了一个事件处理的功能。也就是说,CALayer不能处理用户的触摸事件,而UIView可以

所以,如果显示出来的东西需要跟用户进行交互的话,用UIView;如果不需要跟用户进行交互,用UIView或者CALayer都可以

当然,CALayer的性能会高一些,因为它少了事件处理的功能,更加轻量级



position和anchorPoint

 

CALayer有2个非常重要的属性:position和anchorPoint


@property CGPoint position;

用来设置CALayer在父层中的位置

以父层的左上角为原点(0, 0)


@property CGPoint anchorPoint;

称为“定位点”、“锚点”

决定着CALayer身上的哪个点会在position属性所指的位置

以自己的左上角为原点(0, 0)

它的x、y取值范围都是0~1,默认值为(0.5, 0.5)


 

红色图层的anchorPoint是(0,0)

红色图层的anchorPoint是(0.5,0.5)

红色图层的anchorPoint是(1,1)

红色图层的anchorPoint是(0.5,0)

红色图层的anchorPoint是(1,0.5)


隐式动画

 

每一个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];




 

- (void)viewDidLoad {

    [super viewDidLoad];

    CALayer * layer  = [[CALayer alloc]init];

    layer.bounds = CGRectMake(0, 0, 200, 200);

    layer.anchorPoint = CGPointMake(0.5, 0.5);

    layer.backgroundColor = [UIColor redColor].CGColor;

    [self.view.layer addSublayer:layer];

    self.layer = layer;


}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{

//    关闭动画

    [CATransaction begin];

    [CATransaction setDisableActions:YES];

    self.layer.backgroundColor = [UIColor greenColor].CGColor;

    self.layer.position = CGPointMake(200, 200);

    //    self.layer.position // 如何查看CALayer的某个属性是否支持隐式动画, 查看头文件是否有 Animatable

//    如何查看是否CAlayer支持隐式动画,查看头文件是否有Animation

    [CATransaction commit];

    

}


核心动画(简介)

Core Animation,中文翻译为核心动画,它是一组非常强大的动画处理API,使用它能做出非常炫丽的动画效果,而且往往是事半功倍。也就是说,使用少量的代码就可以实现非常强大的功能。

 Core Animation可以用在Mac OS X和iOS平台。


Core Animation的动画执行过程都是在后台操作的,不会阻塞主线程。

 要注意的是,Core Animation是直接作用在CALayer上的,并非UIView。

Core Animation的使用步骤


1.使用它需要先添加QuartzCore.framework框架和引入主头文件<QuartzCore/QuartzCore.h>(iOS7不需要)


2.初始化一个CAAnimation对象,并设置一些动画相关属性

3.通过调用CALayer的addAnimation:forKey:方法增加CAAnimation对象到CALayer中,这样就能开始执行动画了


4.通过调用CALayer的removeAnimationForKey:方法可以停止CALayer中的动画



 

 

 

CAAnimation



 

所有动画对象的父类,负责控制动画的持续时间和速度,是个抽象类,不能直接使用,应该使用它具体的子类

属性解析:(红色代表来自CAMediaTiming协议的属性)

duration:动画的持续时间

repeatCount:动画的重复次数

repeatDuration:动画的重复时间

removedOnCompletion:默认为YES,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。如果想让图层保持显示动画执行后的状态,那就设置为NO,不过还要设置fillMode为kCAFillModeForwards

fillMode:决定当前对象在非active时间段的行为.比如动画开始之前,动画结束之后

beginTime:可以用来设置动画延迟执行时间,若想延迟2s,就设置为CACurrentMediaTime()+2,CACurrentMediaTime()为图层的当前时间

timingFunction:速度控制函数,控制动画运行的节奏

delegate:动画代理



 

CAPropertyAnimation


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

属性解析:

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


 

CABasicAnimation



CAPropertyAnimation的子类

属性解析:

fromValue:keyPath相应属性的初始值

toValue:keyPath相应属性的结束值

随着动画的进行,在长度为duration的持续时间内,keyPath相应属性的值从fromValue渐渐地变为toValue

如果fillMode=kCAFillModeForwardsremovedOnComletion=NO,那么在动画执行完毕后,图层会保持显示动画执行后的状态。但在实质上,图层的属性值还是动画执行前的初始值,并没有真正被改变。比如,CALayer的position初始值为(0,0),CABasicAnimation的fromValue为(10,10),toValue为(100,100),虽然动画执行完毕后图层保持在(100,100)这个位置,实质上图层的position还是为(0,0)



CAKeyframeAnimation



CApropertyAnimation的子类,跟CABasicAnimation的区别是:CABasicAnimation只能从一个数值(fromValue)变到另一个数值(toValue),而CAKeyframeAnimation会使用一个NSArray保存这些数值

属性解析:

values:就是上述的NSArray对象。里面的元素称为”关键帧”(keyframe)。动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧

path:可以设置一个CGPathRef\CGMutablePathRef,让层跟着路径移动。path只对CALayer的anchorPoint和position起作用。如果你设置了path,那么values将被忽略

keyTimes:可以为对应的关键帧指定对应的时间点,其取值范围为0到1.0,keyTimes中的每一个时间值都对应values中的每一帧.当keyTimes没有设置的时候,各个关键帧的时间是平分的

CABasicAnimation可看做是最多只有2个关键帧的CAKeyframeAnimation

CAAnimationGroup

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

属性解析:

animations:用来保存一组动画对象的NSArray

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


CATransition


 

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

UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果

属性解析:

type:动画过渡类型

subtype:动画过渡方向

startProgress:动画起点(在整体动画的百分比)

endProgress:动画终点(在整体动画的百分比)


UIView动画


 

UIKit直接将动画集成到UIView类中,当内部的一些属性发生改变时,UIView将为这些改变提供动画支持

执行动画所需要的工作由UIView类自动完成,但仍要在希望执行动画时通知视图,为此需要将改变属性的代码放在[UIView beginAnimations:nil context:nil]和[UIView commitAnimations]之间

常见方法解析:

+ (void)setAnimationDelegate:(id)delegate

设置动画代理对象,当动画开始或者结束时会发消息给代理对象

+ (void)setAnimationWillStartSelector:(SEL)selector

当动画即将开始时,执行delegate对象的selector,并且把beginAnimations:context:中传入的参数传进selector

+ (void)setAnimationDidStopSelector:(SEL)selector

当动画结束时,执行delegate对象的selector,并且把beginAnimations:context:中传入的参数传进selector


+ (void)setAnimationDuration:(NSTimeInterval)duration

动画的持续时间,秒为单位

+ (void)setAnimationDelay:(NSTimeInterval)delay

动画延迟delay秒后再开始

+ (void)setAnimationStartDate:(NSDate *)startDate

动画的开始时间,默认为now

+ (void)setAnimationCurve:(UIViewAnimationCurve)curve

动画的节奏控制,具体看下面的”备注”

+ (void)setAnimationRepeatCount:(float)repeatCount

动画的重复次数

+ (void)setAnimationRepeatAutoreverses:(BOOL)repeatAutoreverses

如果设置为YES,代表动画每次重复执行的效果会跟上一次相反

+ (void)setAnimationTransition:(UIViewAnimationTransition)transition forView:(UIView *)view cache:(BOOL)cache

设置视图view的过渡效果, transition指定过渡类型, cache设置YES代表使用视图缓存,性能较好




Block动画


+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion

参数解析:

duration:动画的持续时间

delay:动画延迟delay秒后开始

options:动画的节奏控制

animations:将改变视图属性的代码放在这个block中

completion:动画结束后,会自动调用这个block

+ (void)transitionWithView:(UIView *)view duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion

参数解析:

duration:动画的持续时间

view:需要进行转场动画的视图

options:转场动画的类型

animations:将改变视图属性的代码放在这个block中

completion:动画结束后,会自动调用这个block

+ (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^)(BOOL finished))completion

方法调用完毕后,相当于执行了下面两句代码:

// 添加toView到父视图

[fromView.superview addSubview:toView]; 

// 把fromView从父视图中移除

[fromView.superview removeFromSuperview];

参数解析:

duration:动画的持续时间

options:转场动画的类型

animations:将改变视图属性的代码放在这个block中

completion:动画结束后,会自动调用这个block

UIImageView的帧动画

UIImageView可以让一系列的图片在特定的时间内按顺序显示

相关属性解析:

animationImages:要显示的图片(一个装着UIImage的NSArray)

animationDuration:完整地显示一次animationImages中的所有图片所需的时间

animationRepeatCount:动画的执行次数(默认为0,代表无限循环)

相关方法解析:

- (void)startAnimating; 开始动画

- (void)stopAnimating;  停止动画

- (BOOL)isAnimating;  是否正在运行动画

UIActivityIndicatorView

是一个旋转进度轮,可以用来告知用户有一个操作正在进行中,一般用initWithActivityIndicatorStyle初始化

方法解析:

- (void)startAnimating; 开始动画

- (void)stopAnimating;  停止动画

- (BOOL)isAnimating;  是否正在运行动画

UIActivityIndicatorViewStyle有3个值可供选择:

UIActivityIndicatorViewStyleWhiteLarge   //大型白色指示器    

UIActivityIndicatorViewStyleWhite      //标准尺寸白色指示器    

UIActivityIndicatorViewStyleGray    //灰色指示器,用于白色背景

 

 

抖动代码

 

//

//  ViewController.m

//  01-抖动

//

//  Created by Lenny  on 3/16/15.

//  Copyright (c) 2015 Lenny. All rights reserved.

//


#import "ViewController.h"

#define angle2Radian(angle) ((angle) / 180.0 * M_PI)

@interface ViewController ()


@property (weak, nonatomic) IBOutlet UIView *imageView;

@end


@implementation ViewController


-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{

//    1.创建核心动画

    CAKeyframeAnimation * kfr = [[CAKeyframeAnimation alloc]init];

    //    2.创建核心动画的类型

    kfr.keyPath = @"transform.rotation";

//    度数/ 180.0 *M_PI

    kfr.values = @[@(-angle2Radian(4)), @(angle2Radian(4)), @(-angle2Radian(4))];

    kfr.removedOnCompletion = NO;

    kfr.fillMode = kCAFillModeBackwards;

    kfr.duration = 0.1;

//    设置重复次数

    kfr.repeatCount = MAXFLOAT;


//    3添加核心动画

    [self.imageView.layer addAnimation:kfr forKey:nil];

}

@end

转场帧动画

- (IBAction)preImage:(id)sender {

    self.index--;

    if (self.index < 1) {

        self.index = 7;

    }

    NSString * imageName = [NSString stringWithFormat:@"%d.jpg",self.index];

    UIImage * image = [UIImage imageNamed:imageName];

    self.imageView.image = image;

//    创建核心动画

    CATransition * ca = [CATransition animation];

//    动画的过度类型

    ca.type = @"cube";

//    动画过度的方向

    ca.subtype = kCATransitionFromRight;

//    动画的起点终点动画

//    ca.startProgress = 0.5;

//    ca.endProgress = 0.5;

//    动画时间

    ca.duration = 1;

    [self.imageView.layer addAnimation:ca forKey:nil];

    

    

}


- (IBAction)nextImage:(id)sender {

    self.index++;

    if (self.index >7) {

        self.index = 1;

    }

    NSString * imageName = [NSString stringWithFormat:@"%d.jpg",self.index];

    UIImage * image = [UIImage imageNamed:imageName];

    self.imageView.image = image;

    //    创建核心动画

    CATransition * ca = [CATransition animation];

    //    动画的过度类型

    ca.type = @"cube";

    //    动画过度的方向

    ca.subtype = kCATransitionFromLeft;

    //    动画的起点终点动画

//    ca.startProgress = 0.5;

//    ca.endProgress = 0.5;

    //    动画时间

    ca.duration = 1;

    [self.imageView.layer addAnimation:ca forKey:nil];

    


}

 

图片的拉伸

 

UIImage *image = [UIImage imageNamed:@"RedButton"];

    // 通过一张原始图片生成一张可拉伸的图片

    CGFloat imageW = image.size.width * 0.5;

    CGFloat imageH = image.size.height * 0.5;

    UIImage *newImage = [image resizableImageWithCapInsets:UIEdgeInsetsMake(imageH, imageW, imageH, imageW) resizingMode:UIImageResizingModeStretch];

    [self.loginBtn setBackgroundImage:newImage forState:UIControlStateNormal];

    

    UIImage *selImage = [UIImage imageNamed:@"RedButtonPressed"];

    CGFloat selImageW = selImage.size.width * 0.5;

    CGFloat selImageH = selImage.size.height * 0.5;

    UIImage *selNewImage = [selImage resizableImageWithCapInsets:UIEdgeInsetsMake(selImageH, selImageW, selImageH, selImageW) resizingMode:UIImageResizingModeStretch];

    [self.loginBtn setBackgroundImage:selNewImage forState:UIControlStateHighlighted];

 

 

项目总结

 

自定义导航栏


- (void)addTabBarButtonWithNormalImageName:(NSString *)norName andDisableImageName:(NSString *)disName

{

    // 3.1创建按钮

    LKTabBarBtn *btn = [[LKTabBarBtn alloc] init];

    // 3.2设置按钮上显示的图片

    // 3.2.1设置默认状态图片

    [btn setBackgroundImage:[UIImage imageNamed:norName] forState:UIControlStateNormal];

    

    // 3.2.2设置不可用状态图片

    [btn setBackgroundImage:[UIImage imageNamed:disName] forState:UIControlStateDisabled];

    

    // 3.4添加按钮到自定义TabBar

    [self addSubview:btn];

    

    // 3.5监听按钮点击事件

    [btn addTarget:self action:@selector(btnOnClick:) forControlEvents:UIControlEventTouchDown];

    

    // 3.6设置默认选中按钮

    if (1 == self.subviews.count) {

        [self btnOnClick:btn];

    }

    

    // 3.7设置按钮高亮状态不调整图片

    btn.adjustsImageWhenHighlighted = NO;

//    3.8设置tag

   

}

-(void)layoutSubviews

{

    


    //    注意这是必须要写的不然会出现不可预知的错误

//    [super layoutSubviews];

    NSLog(@"self.subviews.count = %lu",(unsigned long)self.subviews.count);


    for (int i = 0; i < self.subviews.count ; i++) {

//        NSLog(@"self.subviews.count = %lu",(unsigned long)self.subviews.count);

        

        UIButton *btn = self.subviews[i];

        

        // 3.3设置frame

        //  NSLog(@"initWithFrame %@", NSStringFromCGRect(self.frame));

        

        CGFloat btnY = 0;

        CGFloat btnW = self.frame.size.width / 5;

        CGFloat btnH = self.frame.size.height;

        CGFloat btnX = i * btnW;

        btn.frame = CGRectMake(btnX, btnY, btnW, btnH);

         btn.tag = i;

    }

    

}


-(void)btnOnClick:(LKTabBarBtn * )btn

{

//    NSLog(@"按钮被点击了 = %ld",(long)btn.tag);

    if ([self.delegate respondsToSelector:@selector(tarBarDidSelectBtnFrom:to:)]) {

        [self.delegate tarBarDidSelectBtnFrom:self.selectBtn.tag to:btn.tag];

        

    }

    

    //    0.取消上一次选中的按钮

    //    self.selectBtn.selected = NO;

    self.selectBtn.enabled = YES;

    //    1.设置当前的点击的按钮的选中状态

    btn.enabled = NO;

    //    2.记录当前的按钮

    self.selectBtn = btn;

    //    3.切换控制器

   

}


数据的传递通过代理

 

@protocol LKTabBarDelegate <NSObject>


-(void)tarBarDidSelectBtnFrom:(NSInteger)from to:(NSInteger)to;


@end


@interface LKTabBar : UIView


@property (nonatomic, weak) id<LKTabBarDelegate> delegate;

- (void)addTabBarButtonWithNormalImageName:(NSString *)norName andDisableImageName:(NSString *)disName;


@end

 


控制器操作

 


    //    1.新建一个tabBar

    LKTabBar * myTabBar = [[LKTabBar alloc]init];

    //    myTabBar.backgroundColor = [UIColor redColor];

    myTabBar.frame = self.tabBar.bounds;

    //    设置代理事件

    myTabBar.delegate = self;

    [self.tabBar addSubview:myTabBar];

    for (int i = 0; i < self.viewControllers.count; i++) {

        // 通知自定义TabBar创建按钮

        NSString *norImageName = [NSString stringWithFormat:@"TabBar%d", i + 1];

        NSString *disableImageName = [NSString stringWithFormat:@"TabBar%dSel", i + 1];

        // 只要调用自定义TabBar的该方法就会创建一个按钮

        [myTabBar addTabBarButtonWithNormalImageName:norImageName andDisableImageName:disableImageName];

 


控制器的跳转

 

-(void)tarBarDidSelectBtnFrom:(NSInteger)from to:(NSInteger)to

{

    self.selectedIndex = to;

    NSLog(@"to = %ld",(long)to);

}

 



巧妙的使用父类

 

//只是调用一次

+(void)initialize

{

    //    NSLog(@"initialize");

    // 1.设置导航条的主题

    // 如果要同时设置很多UINavigationBar的样式, 可以通过设置UINavigationBar的主题的方式来设置以便简化代码

    UINavigationBar *navBar = [UINavigationBar appearance];

    // 1.1设置所有导航条的背景图片

    // 判断当前运行的操作系统的版本

    if ([[UIDevice currentDevice].systemVersion doubleValue] > 7.0) {

        [navBar setBackgroundImage:[UIImage imageNamed:@"NavBar64"] forBarMetrics:UIBarMetricsDefault];

    }else

    {

        [navBar setBackgroundImage:[UIImage imageNamed:@"NavBar"] forBarMetrics:UIBarMetricsDefault];

    }

    

    // 设置导航条上返回按钮和图片的颜色

    [navBar setTintColor:[UIColor whiteColor]];

    

    // 1.2设置所有导航条的标题颜色

    NSMutableDictionary *md = [NSMutableDictionary dictionary];

    md[NSFontAttributeName] = [UIFont systemFontOfSize:16];

    md[NSForegroundColorAttributeName] = [UIColor whiteColor];

    [navBar setTitleTextAttributes:md];

    

    // 1.3设置UIBarButtonItem的主题

    UIBarButtonItem *barItem = [UIBarButtonItem appearance];

    

    // 判断是否是IOS6 如果是IOS6就设置图片

    if (!([[UIDevice currentDevice].systemVersion doubleValue] > 7.0)) {

        // 设置普通按钮的图片

        UIImage *norImage = [UIImage imageNamed:@"NavButton"];

        [barItem setBackgroundImage:norImage forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];

        UIImage *higImage = [UIImage imageNamed:@"NavButtonPressed"];

        [barItem setBackgroundImage:higImage forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];

        //  设置返回按钮的图片

        UIImage *norBackImage = [UIImage imageNamed:@"NavBackButton"];

        [barItem setBackButtonBackgroundImage:norBackImage forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];

        

        UIImage *higBackImage = [UIImage imageNamed:@"NavBackButtonPressed"];

        [barItem setBackButtonBackgroundImage:higBackImage forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];

        

    }else

    {

        // 是IOS7

        NSMutableDictionary *barMd = [NSMutableDictionary dictionary];

        barMd[NSFontAttributeName] = [UIFont systemFontOfSize:16];

        barMd[NSForegroundColorAttributeName] = [UIColor whiteColor];

        [barItem setTitleTextAttributes:barMd forState:UIControlStateNormal];

        

    }

    

}


- (void)viewDidLoad {

    [super viewDidLoad];

    // Do any additional setup after loading the view.

}


- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}


-(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{

//    // 拿到目标控制器(即将要入栈的控制器), 设置它的自动隐藏tabbar

//    viewController.hidesBottomBarWhenPushed = YES;

//    [super pushViewController:viewController animated:animated];

//    拿到目标的控制器(即将要入栈的控制器),设置它的自动隐藏的tabBar

    viewController.hidesBottomBarWhenPushed = YES;

    [super pushViewController:viewController animated:YES];


}



不使用系统自带的button

 

//

//  LKTitleBtn.m

//  01-彩票

//

//  Created by Lenny  on 3/17/15.

//  Copyright (c) 2015 Lenny. All rights reserved.

//


#import "LKTitleBtn.h"

#import <Availability.h>


@interface LKTitleBtn ()

@property(nonatomic,strong) UIFont * myFont;

@end

@implementation LKTitleBtn


-(id)initWithCoder:(NSCoder *)aDecoder

{

    if (self = [super initWithCoder:aDecoder])

    {

        [self setup];

    }

    return self;

}

-(instancetype)initWithFrame:(CGRect)frame

{

    if (self = [super initWithFrame:frame]) {

        [self setup];

    }

    return self;

}

-(void)setup

{

//    记录按钮标题的字体

    self.myFont = [UIFont systemFontOfSize:15];

//    设置标题的字体

    self.titleLabel.font = self.myFont;

//    设置按钮的图片显示的内容默认为拉伸不是居中

    self.imageView.contentMode = UIViewContentModeCenter;

    

}

//用于返回按钮上标题的位置,传入按钮的rect

-(CGRect)titleRectForContentRect:(CGRect)contentRect

{

    CGFloat titleX = 0;

    CGFloat titleY = 0;

    CGFloat titleW = 0;

    CGFloat titleH = contentRect.size.height;

//    获取按钮上的字体 1

    [self titleForState:UIControlStateNormal];

//    获取按钮上的字体2

    NSString * title = self.currentTitle;//建议使用这个方法 这个方法获得的是任何状态下的title

    CGSize maxSize = CGSizeMake(MAXFLOAT, MAXFLOAT);

    NSMutableDictionary * md = [NSMutableDictionary dictionary];

     // 死循环的原因是self.titleLabel需要访问titleLabel, 而self.titleLabel又需要调用当前方法获取title的范围, 所有死循环

    //    md[NSFontAttributeName] = self.titleLabel.font;

    //    NSLog(@"%@", self.myFont);

    md[NSFontAttributeName] = self.myFont;

//    计算文字的范围

    // 判断是否是xcode5 , 如果是就编译一下代码, 如果不是就不编译

    #ifdef __IPHONE_7_0

    if (([[UIDevice currentDevice].systemVersion doubleValue] >= 7.0)) {

        CGRect titleRect = [title boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:md context:nil];

        titleW = titleRect.size.width;


    }else{

        CGSize titleSize = [title sizeWithFont:self.myFont];//过时的方法

        titleW  = titleSize.width;

    }

#else

    // XCODE4

    CGSize titleSize = [title sizeWithFont:self.myFont];

    titleW = titleSize.width;

#endif

return CGRectMake(titleX, titleY, titleW, titleH);

}

-(CGRect)imageRectForContentRect:(CGRect)contentRect{

    CGFloat imageY = 0;

    CGFloat imageH = contentRect.size.height;

    CGFloat imageW = 16;//图片的宽度

//    图片的X = 按钮的宽度 - 图片的宽度

    CGFloat imageX= contentRect.size.width - imageW;

    return CGRectMake(imageX, imageY, imageW, imageH);

    

}

 

iOS 打电话的方式

最简单最直接的方式:直接跳到拨号界面

1

NSURL *url = [NSURL URLWithString:@"tel://10010"];

[[UIApplication sharedApplication] openURL:url];

缺点

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

2

 

拨号之前会弹框询问用户是否拨号,拨完后能自动回到原应用

NSURL *url = [NSURL URLWithString:@"telprompt://10010"];

[[UIApplication sharedApplication] openURL:url];

缺点

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

3

 

创建一个UIWebView来加载URL,拨完后能自动回到原应用

if (_webView == nil) {

    _webView = [[UIWebView alloc] initWithFrame:CGRectZero];

}

[_webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"tel://10010"]]];


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



 

发短信-方法1



直接跳到发短信界面,但是不能指定短信内容,而且不能自动回到原应用

NSURL *url = [NSURL URLWithString:@"sms://10010"];

[[UIApplication sharedApplication] openURL:url];



发短信-方法2



如果想指定短信内容,那就得使用MessageUI框架

包含主头文件

#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(@"发送失败");

    }

}



 

发邮件-方法1



用自带的邮件客户端,发完邮件后不会自动回到原应用

NSURL *url = [NSURL URLWithString:@"mailto://10010@qq.com"];

[[UIApplication sharedApplication] openURL:url];


发邮件-方法2

跟发短信的第2种方法差不多,只不过控制器类名叫做:MFMailComposeViewController

假设发送的邮件内容如右图所示,代码实现看备注



邮件发送后的代理方法回调,发完后会自动回到原应用

- (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(@"发送失败");

    }

}



打开其他常见文件



如果想打开一些常见文件,比如html、txt、PDF、PPT等,都可以使用UIWebView打开

只需要告诉UIWebView文件的URL即可

至于打开一个远程的共享资源,比如http协议的,也可以调用系统自带的Safari浏览器:

NSURL *url = [NSURL URLWithString:@”http://www.baidu.com"];

[[UIApplication sharedApplication] openURL:url];



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


首先,B应用得有自己的URL地址(在Info.plist中配置)

 

 


B应用的URL地址就是:mj://ios.itcast.cn


接着在A应用中使用UIApplication完成跳转

NSURL *url = [NSURL URLWithString:@"mj://ios.itcast.cn"];

[[UIApplication sharedApplication] openURL:url];



应用评分


 

为了提高应用的用户体验,经常需要邀请用户对应用进行评分


应用评分无非就是跳转到AppStore展示自己的应用,然后由用户自己撰写评论


如何跳转到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]];



关于图片的切割问题

 

 

        // 8.切割图片,将切割好的图片设置到按钮上

        CGFloat imageH = NJImageHeight * [UIScreen mainScreen].scale;

        CGFloat imageW = NJImageWidth * [UIScreen mainScreen].scale;

        CGFloat imageY = 0;

        CGFloat imageX = index * imageW;//设置第几个的图片的X的值

        CGRect rect = CGRectMake(imageX, imageY, imageW, imageH);//切割的范围

        // 8.1根据rect切割图片

        // CGImage中rect是当做像素来使用

        // UIKit 中是点坐标系

        // 坐标系的特点:如果在非retain屏上 1个点等于1个像素

        //   在retain屏上1个点等于2个像素

        // 剪切默认状态的图片

       CGImageRef norCGImageRef= CGImageCreateWithImageInRect(norImage.CGImage, rect);

        // 将切割好的图片转换为uiimage设置为按钮的背景

        [btn setImage:[UIImage imageWithCGImage:norCGImageRef]  forState:UIControlStateNormal];

        

        //   剪切选中状态图片

        CGImageRef selCGImageRef= CGImageCreateWithImageInRect(selImage.CGImage, rect);

        // 将切割好的图片转换为uiimage设置为按钮的背景

        [btn setImage:[UIImage imageWithCGImage:selCGImageRef]  forState:UIControlStateSelected];

CGFloat imageH = NJImageHeight * [UIScreen mainScreen].scale;//坐标转化为像素

CGFloat imageW = NJImageWidth * [UIScreen mainScreen].scale;

[UIScreen mainScreen].scale  是否是Retina屏 这里的返回值是 1 或者 2

转盘的问题

 

// 创建12个按钮添加到中间的轮盘上

    for (int index = 0; index < 12; index++) {

        // 1.创建按钮

        NJWheelButton *btn = [[NJWheelButton alloc] init];

//        btn.backgroundColor = [UIColor redColor];

        // 2.设置按钮选中状态的图片

        [btn setBackgroundImage:[UIImage imageNamed:@"LuckyRototeSelected"] forState:UIControlStateSelected];

        // 3.设置按钮的bounds

        btn.bounds = CGRectMake(0, 0, 68, 143);

        

        // 4.设置按钮的锚点

        btn.layer.anchorPoint = CGPointMake(0.5, 1);

        // 5.设置按钮的position

        btn.layer.position = CGPointMake(self.frame.size.width * 0.5, self.frame.size.height * 0.5);

        

        // 6然按钮围绕锚点旋转(关键部分 先进行锚点的设置 再进行后面的操作)

        // 6.1计算按钮应该旋转的弧度

        CGFloat angle = (30 * index)/180.0 * M_PI;

        btn.transform = CGAffineTransformMakeRotation(angle);

        

        // 7.监听按钮的点击事件

        [btn addTarget:self action:@selector(update:) forControlEvents:UIControlEventTouchUpInside];

        

        // 获取当前是否是retain屏

//        NSLog(@"%.1f", [UIScreen mainScreen].scale);

        

        // 8.切割图片,将切割好的图片设置到按钮上

        CGFloat imageH = NJImageHeight * [UIScreen mainScreen].scale;//坐标转化为像素

        CGFloat imageW = NJImageWidth * [UIScreen mainScreen].scale;

        CGFloat imageY = 0;

        CGFloat imageX = index * imageW;//设置第几个的图片的X的值

        CGRect rect = CGRectMake(imageX, imageY, imageW, imageH);//切割的范围

        // 8.1根据rect切割图片

        // CGImage中rect是当做像素来使用(这里是关键点)

        // UIKit 中是点坐标系 但是在 CGImage中rect是当做像素来使用的 这里所区分的是Retain屏幕的问题

        // 坐标系的特点:如果在非retain屏上 1个点等于1个像素

        //   在retain屏上1个点等于2个像素

        // 剪切默认状态的图片

       CGImageRef norCGImageRef= CGImageCreateWithImageInRect(norImage.CGImage, rect);

        // 将切割好的图片转换为uiimage设置为按钮的背景(这里必须进行uiimage的转换否则会出现问题)

        [btn setImage:[UIImage imageWithCGImage:norCGImageRef]  forState:UIControlStateNormal];

        

        //   剪切选中状态图片

        CGImageRef selCGImageRef= CGImageCreateWithImageInRect(selImage.CGImage, rect);

        // 将切割好的图片转换为uiimage设置为按钮的背景

        [btn setImage:[UIImage imageWithCGImage:selCGImageRef]  forState:UIControlStateSelected];

        

        

        // 添加按钮到中间轮盘图片上

        [self.centerWheel addSubview:btn];

    }

}

//让轮盘上面的按钮的点击事件

- (void)update:(UIButton *)btn

{

    self.selectButton.selected = NO;

    

    btn.selected = YES;

    

    self.selectButton = btn;

}








 


#import <UIKit/UIKit.h>


#define NJImageWidth 40

#define NJImageHeight 47


@interface NJWheelButton : UIButton


@end






 

#import "NJWheelButton.h"

//自定义了一个按钮上面显示图片的操作 自定义一个button


@implementation NJWheelButton


- (CGRect)imageRectForContentRect:(CGRect)contentRect

{

    CGFloat imageX = (contentRect.size.width - NJImageWidth ) * 0.5;

    CGFloat imageY = 18;

    return CGRectMake(imageX, imageY, NJImageWidth, NJImageHeight);

}


//这里是为了解决刚点击下去的时候 会出现高亮的状态 这里实现的是自定义的高亮的状态 将系统的高亮状态屏蔽掉

- (void)setHighlighted:(BOOL)highlighted

{

    

}

@end


block

 

如果在block中有引用那个一定要讲self设置微弱引用 __unsafe_unretained


 

 

//    __unsafe_unretained NJShareViewController *unsafeSelf = self;

    

//    __weak NJShareViewController *unsafeSelf = self;

//    __weak 当对象释放之后会自动设置为nil, 而__unsafe_unretained不会 会出现空指针的错误




//        self.age = 10;

//        _age =  10;// 注意改行代码会自动变成 self->_age = 10;


以后直接使用以下的操作


 

 __weak typeof(self) unsafeSelf = self;//装逼专用


进程

 

什么是进程

进程是指在系统中正在运行的一个应用程序

每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内


比如同时打开QQ、Xcode,系统就会分别启动2个进程

通过“活动监视器”可以查看Mac系统中所开启的进程



什么是线程

1个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程)

线程是进程的基本执行单元,一个进程(程序)的所有任务都在线程中执行

比如使用酷狗播放音乐、使用迅雷下载电影,都需要在线程中执行


线程的串行



 

1个线程中任务的执行是串行的

如果要在1个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务

也就是说,在同一时间内,1个线程只能执行1个任务


比如在1个线程中下载3个文件(分别是文件A、文件B、文件C)



多线程


 

什么是多线程

1个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务

进程  车间,线程  车间工人

多线程技术可以提高程序的执行效率


比如同时开启3条线程分别下载3个文件(分别是文件A、文件B、文件C)

 


 

多线程的原理


 

多线程的原理

同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)

多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)

如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象

思考:如果线程非常非常多,会发生什么情况?

CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源

每条线程被调度执行的频次会降低(线程的执行效率降低)


多线程的优缺点



多线程的优点

能适当提高程序的执行效率

能适当提高资源利用率(CPU、内存利用率)


多线程的缺点

开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能

线程越多,CPU在调度线程上的开销就越大

程序设计更加复杂:比如线程之间的通信、多线程的数据共享


多线程在iOS开发中的应用




什么是主线程

一个iOS程序运行后,默认会开启1条线程,称为“主线程”或“UI线程”


主线程的主要作用

显示\刷新UI界面

处理UI事件(比如点击事件、滚动事件、拖拽事件等)


主线程的使用注意

别将比较耗时的操作放到主线程中

耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验




 

开发中的注意点

/ 凡是函数名种带有create\copy\new\retain等字眼, 都需要在不需要使用这个数据的时候进行release

// GCD的数据类型在ARC环境下不需要再做release

// CF(Core Foundation)的数据类型在ARC环境下还是需要再做release



在常用

 

 

数据请求GET方法

 

//

//  ViewController.m

//  NetWork 1

//

//  Created by Lenny  on 3/21/15.

//  Copyright (c) 2015 Lenny. All rights reserved.

//


#import "ViewController.h"

#import "MBProgressHUD+MJ.h"


@interface ViewController ()<NSURLConnectionDataDelegate>

@property (weak, nonatomic) IBOutlet UITextField *nameField;

@property (weak, nonatomic) IBOutlet UITextField *pwdField;

- (IBAction)loginBtnClick;

/**

 用来存放服务器返回的所有的数据

 */

@property(nonatomic,strong)NSMutableData * responseData;

@end


@implementation ViewController


- (void)viewDidLoad {

    [super viewDidLoad];

}


/**

 登录逻辑

 */

- (IBAction)loginBtnClick {

    NSString *username  = self.nameField.text;

    NSString * pwd = self.pwdField.text;

    if (username.length == 0) {//没有用户名的输入

        [MBProgressHUD showError:@"enter the user name"];

        return;

    }

    if (pwd.length == 0) {

        [MBProgressHUD showError:@"enter the pwd"];

        return;

    }

//    弹框正在登录中....

    [MBProgressHUD showMessage:@"正在拼命的为您加载中...."];

//    2.发送请求给服务器(账户名称和用户密码)

//    GET请求:请求行\请求头

//    2.1设置请求路径

    NSString * urlStr = [NSString stringWithFormat:@"http://192.168.1.200:8080/MJServer/login?username=%@&pwd=%@",username,pwd];

//    进行转码的操作 如果其中有中文字符 那么将其转走

    urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

//    URL里面不可以含有中文

    NSURL * url = [NSURL URLWithString:urlStr];

//    2.2创建请求对象

    NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url];//默认就是GET请求的方式

    request.timeoutInterval = 10;//设置请求超时

//    发送请求 注意这里的请求是异步请求

    [self sendAsync:request];

    NSLog(@"请求已经发出");

    

}


/**

 发送异步请求的第一种方式1:类方法 block

 */

-(void)sendAsync:(NSURLRequest *)request

{

    //    这里队列使用的是主线程

    NSOperationQueue * queue = [NSOperationQueue mainQueue];

    [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {//当请求结束的时候调用(拿到了服务器的数据,请求失败)

        //        隐藏HUD(刷新UI界面,一定要放在主线程中使用,不能放在子线程中使用)

        [MBProgressHUD hideHUD];

        /**

         解析data :

         {"error":"用户名不存在"}

         {"error":"密码不正确"}

         {"success":"登录成功"}

         */

        if (data) {//请求成功

            NSDictionary * dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];

            NSString * error = dict[@"error"];

            if (error) {//表示登录失败

                [MBProgressHUD showError:error];

            }else

            {

                [MBProgressHUD showSuccess:dict[@"success"]];

            }

        }else{

            [MBProgressHUD showError:@"网络繁忙,请稍后重试...."];

        }

        

        

        

    }];

}

/**

 发送异步请求的方式2 :start方法,代理

 

 */

-(void)sendAsync2:(NSURLRequest *)request

{

    NSURLConnection * conn = [NSURLConnection connectionWithRequest:request delegate:self];

    [conn start];//异步执行开始

    

}


#pragma mark -NSURLConnectionDataDelegate

/**

 请求错误(失败)的时候调用(请求超时\断网\没有网络,一般是指客户顿的错误)

 */

-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error

{

    NSLog(@"connection:didFailWithError");

    [MBProgressHUD hideHUD];

    [MBProgressHUD showError:@"网络繁忙,请稍后再试"];

}

/**当接受到服务器的响应的时候(联通了服务器)就会调用

 */

-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response

{

    NSLog(@"connection:didReceiveResponse");

//    初始化数据 告诉容器 你可以接收数据了 将数据器准备好

    self.responseData = [NSMutableData data];

    

}

-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data

{

    NSLog(@"connection:didReceiveData");

//    将接收到数据放入到容器

    [self.responseData appendData:data];

    

}

/**当服务器的数据接受完毕之后就会调用

 */

-(void)connectionDidFinishLoading:(NSURLConnection *)connection

{

    NSLog(@"connectionDidFinishLoading");

//    隐藏HUD

    [MBProgressHUD hideHUD];

//    解析服务器返回来的数据

    NSDictionary * dict = [NSJSONSerialization JSONObjectWithData:self.responseData options:NSJSONReadingMutableLeaves error:nil ];

    NSString *error = dict[@"error"];

    if (error) {//登录失败

        [MBProgressHUD showError:error];

    }else{

        NSString * success = dict[@"success"];

        [MBProgressHUD showSuccess:success];

    }

    

}

@end

 

 

POST

 

//

//  ViewController.m

//  POST

//

//  Created by Lenny  on 3/21/15.

//  Copyright (c) 2015 Lenny. All rights reserved.

//


#import "ViewController.h"

#import "MBProgressHUD+MJ.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UITextField *nameField;

@property (weak, nonatomic) IBOutlet UITextField *pwdField;

- (IBAction)loginBtnClick;


@end


@implementation ViewController


- (void)viewDidLoad {

    [super viewDidLoad];

    // Do any additional setup after loading the view, typically from a nib.

}


/**

 登录逻辑

 */

- (IBAction)loginBtnClick {

    NSString *username  = self.nameField.text;

    NSString * pwd = self.pwdField.text;

    if (username.length == 0) {//没有用户名的输入

        [MBProgressHUD showError:@"enter the user name"];

        return;

    }

    if (pwd.length == 0) {

        [MBProgressHUD showError:@"enter the pwd"];

        return;

    }

    //    弹框正在登录中....

    [MBProgressHUD showMessage:@"正在拼命的为您加载中...."];

    //    2.发送请求给服务器(账户名称和用户密码)

    //    GET请求:请求行\请求头

    //    2.1设置请求路径

    NSString * urlStr = [NSString stringWithFormat:@"http://192.168.1.200:8080/LKServer/login"];

    //    进行转码的操作 如果其中有中文字符 那么将其转走

    urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

    //    URL里面不可以含有中文

    NSURL * url = [NSURL URLWithString:urlStr];

    //    2.2创建请求对象

    NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url];//默认就是GET请求的方式

    request.HTTPMethod = @"POST";//设置成为POST请求的方式

    

//    通过请求头告诉服务器 客户端的类型

    [request setValue:@"iOS" forKey:@"User-Agent"];

//    设置请求体

    NSString * param = [NSString stringWithFormat:@"username=%@&pwd=%@", username, pwd];

    request.HTTPBody = [param dataUsingEncoding:NSUTF8StringEncoding];

    request.timeoutInterval = 5;//设置请求超时

    //    发送请求 注意这里的请求是异步请求

//    发送数据请求

    NSOperationQueue * queue = [NSOperationQueue mainQueue];

    [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {// 当请求结束的时候调用 (拿到了服务器的数据, 请求失败)

        // 隐藏HUD (刷新UI界面, 一定要放在主线程, 不能放在子线程)

        [MBProgressHUD hideHUD];

        if (data) {//请求成功

            NSDictionary * dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];

            NSString * error = dict[@"error"];

            if (error) {//登录失败

                [MBProgressHUD showError:error];

            }else

            {

                [MBProgressHUD showSuccess:dict[@"success"]];

            }

        }else{

            [MBProgressHUD showError:@"网络繁忙,请稍后再试"];

        }


        

    }];

    

}

@end

 

文件的解压与压缩

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

{

    NSString * path = [[NSBundle mainBundle]pathForResource:@"guo.zip" ofType:nil];

    NSString * despth = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,NSUserDomainMask , YES)lastObject];

    NSString *filepath = [despth stringByAppendingPathComponent:@"guo"];

    NSLog(@"解压中");

    NSLog(@"%@",filepath);

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    [SSZipArchive unzipFileAtPath:path toDestination:filepath];

});

}

- (void)createZip

{

    //    [[NSBundle mainBundle] pathForResource:@"minion_01.png" ofType:nil];

    

    // 1.获得mainBundle中所有的png的图片路径

    NSArray *pngs = [[NSBundle mainBundle] pathsForResourcesOfType:@"png" inDirectory:nil];

    

    // 2.zip文件路径

    NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];

    NSString *zipFilepath = [caches stringByAppendingPathComponent:@"pngs.zip"];

    

    // 3.创建zip文件

    [SSZipArchive createZipFileAtPath:zipFilepath withFilesAtPaths:pngs];

}

 

四个容易混淆的属性

四个容易混淆的属性:

1. textAligment : 文字的水平方向的对齐方式

1> 取值

NSTextAlignmentLeft      = 0,    // 左对齐

NSTextAlignmentCenter    = 1,    // 居中对齐

NSTextAlignmentRight    = 2,    // 右对齐


2> 哪些控件有这个属性 : 一般能够显示文字的控件都有这个属性

* UITextField

* UILabel

* UITextView


2. contentVerticalAlignment : 内容的垂直方向的对齐方式

1> 取值

UIControlContentVerticalAlignmentCenter  = 0, // 居中对齐

UIControlContentVerticalAlignmentTop     = 1, // 顶部对齐

UIControlContentVerticalAlignmentBottom  = 2, // 底部对齐


2> 哪些控件有这个属性 : 继承自UIControl的控件或者UIControl本身

* UIControl

* UIButton

* UITextField

* ...


3. contentHorizontalAlignment : 内容的水平方向的对齐方式

1> 取值

UIControlContentHorizontalAlignmentCenter = 0, // 居中对齐

UIControlContentHorizontalAlignmentLeft   = 1, // 左对齐

UIControlContentHorizontalAlignmentRight  = 2, // 右对齐


2> 哪些控件有这个属性 : 继承自UIControl的控件或者UIControl本身

* UIControl

* UIButton

* UITextField

* ...


4. contentMode : 内容模式(控制内容的对齐方式), 一般对UIImageView很有用

1> 取值

/**

 规律:

 1.Scale : 图片会拉伸

 2.Aspect : 图片会保持原来的宽高比

 */

// 前3个情况, 图片都会拉伸

// (默认)拉伸图片至填充整个UIImageView(图片的显示尺寸会跟UIImageView的尺寸一样)

UIViewContentModeScaleToFill,

// 按照图片原来的宽高比进行伸缩, 伸缩至适应整个UIImageView(图片的内容不能超出UIImageView的尺寸范围)

UIViewContentModeScaleAspectFit,

// 按照图片原来的宽高比进行伸缩, 伸缩至 图片的宽度和UIImageView的宽度一样 或者 图片的高度和UIImageView的高度一样

UIViewContentModeScaleAspectFill,


// 后面的所有情况, 都会按照图片的原来尺寸显示, 不会进行拉伸

UIViewContentModeRedraw,  // 当控件的尺寸改变了, 就会重绘一次(重新调用setNeedsDisplay, 调用drawRect:)

UIViewContentModeCenter,

UIViewContentModeTop,

UIViewContentModeBottom,

UIViewContentModeLeft,

UIViewContentModeRight,

UIViewContentModeTopLeft,

UIViewContentModeTopRight,

UIViewContentModeBottomLeft,

UIViewContentModeBottomRight,


2> 哪些控件有这个属性 : 所有UI控件都有


5. 如果有多个属性的作用冲突了, 只有1个属性有效(就近原则)

 

 

item

 

一、UINavigationItem

1> 获得方式

self.navigationItem // self是指控制器


2> 作用

可以用来设置当前控制器顶部导航栏的内容

// 设置导航栏中间的内容

self.navigationItem.title

self.navigationItem.titleView


二、UIBarButtonItem

1> 用在什么地方

// 设置导航栏左上角的内容

self.navigationItem.leftBarButtonItem

// 设置导航栏右上角的内容

self.navigationItem.rightBarButtonItem


2> 作用

相当于一个按钮


三、UITabBarItem

1> 获得方式

self.tabBarItem // self是指控制器


2> 作用

可以用来设置当前控制器对应的选项卡标签的内容

// 标签的标题

self.tabBarItem.title

// 标签的图标

self.tabBarItem.image

// 标签的选中图标

self.tabBarItem.selectdImage


四、UINavigationBar

1. 导航控制器顶部的栏(UI控件)

2. UINavigationBar上面显示什么内容, 取决于当前控制器的navigationItem属性

3. UINavigationBar是view, navigationItem是model

4. 由navigationItem给UINavigationBar提供显示的数据


五、UITabBar

1. UITabBarController底部的选项卡条


六、UITabBarButton

1. UITabBar底部的每一个标签

2. 每一个UITabBarButton里面显示什么内容,取决于当前控制器的tabBarItem属性

3. UITabBarButton是view, tabBarItem是model

4. 由tabBarItem给UITabBarButton提供显示的数据

 

转载于:https://www.cnblogs.com/LennyKwok/p/4368467.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值