视图、绘图、贴图、手势、变形、布局、动画、动力、特效
UIBezierPath、UIGestureRecognizer、CGAffineTransform、frame、bounds、center、transform、UITouch、UIEvent、Layout、Autoresizing、Auto Layout、Animation、UIImage、NSTimer、UIView、Core Animation、CALayer、CAAnimation、CABasicAnimation、CAKeyframeAnimation、CAAnimationGroup、CATransform3D、UIDynamicAnimator、UIGravityBehavior、UICollisionBehavior、UIAttachmentBehavior、 UISnapBehavior、UIPushBehavior、NSNotification
=========================================================================================================
知识点
一、绘图
今天:
1.绘图
1.1基本概念
屏幕:很多个晶体组成的,分辨率1920X1080,一行有1920个晶体,一共有1080行,每一个晶体发三种颜色的光(red,green,blue)
图片:
点阵图:一堆点,每一个点是一个颜色,图片的分辨率指的就是存储的图片的点的个数
图片中的每一个点是用4个整数来存储的,这四个整数分别对象该点的red(0-255)、green(0-255),blue(0-255),alpha。一个点就需要4个字节来存储,依据这个规律就可以计算图片的大小了。但为了压缩图片的大小,将一个点周围的四个点计算一个平均值,用一个点的值来代替这四个点,
矢量图:
存储的是生成图形所需要经过计算的数学公式,所以放大后不会失真
像素
2.OC对象与图形的转换
内存中的OC对象,会基于系统提供的默认规则,绘制成平面图后显示到屏幕上。
绘制的过程本质是由底层的Core Graphics 这一组C语言的API实现
系统为编程人员提供了一个可编程的接口,在指定的位置添加绘制的代码以后,就可以让系统在原有的绘制基础上增加自定义的绘制内容
3.绘图的实现
[Demo1_Graphics]
实现步骤:
a。重写UIView的drawRect方法。该方法由系统自动调用,不能自己手动调用。因为drawRect方法只是系统一整套绘制流程中的一个环节。
b。获取绘制的上下文对象
c。设置上下文对象的绘制起始点
d。添加路径
e。设置描边或填充的颜色
f。绘制路径
MyView.h 继承自UIView
MyView.m
#import "MyView.h"
@implementation MyView
- (void)drawRect:(CGRect)rect
{
// 获取系统的上下文对象
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextMoveToPoint(context, 40, 40);
CGContextAddLineToPoint(context, 40, 140);
CGContextAddLineToPoint(context, 140, 40);
CGContextAddLineToPoint(context
, 40, 40);
//设置描边的颜色
CGContextSetStrokeColorWithColor(context
, [[UIColor redColor] CGColor]);
CGContextSetFillColorWithColor(context, [[UIColor greenColor]CGColor]);
//按照路径描边
//CGContextStrokePath(context);
//CGContextFillPath(context);
CGContextDrawPath(context, kCGPathFillStroke);
}
@end
MyViewController.h
MyViewController.m
#import "MyViewController.h"
#import "MyView.h"
@interface MyViewController ()
@end
@implementation MyViewController
- (void)viewDidLoad
{
[super viewDidLoad];
MyView *view = [[MyView alloc]initWithFrame:self.view.frame]; 将自定义的view加到controller
view.backgroundColor = [UIColor whiteColor];
[self.view addSubview:view];
}

4. UIBezierPath贝塞尔曲线
4.1 是OC语言对c语言绘图的一部分API的封装结果
4.2 作用:更方便的绘制直线、曲线、矩形、圆弧、椭圆等
4.3 绘制直线
【Demo2_BezierPath_Line】
path.lineWidth 设置线条宽度
path.lineCapStyle = kCGLineCapButt;设置线头儿的样式
path.lineJoinStyle = kCGLineJoinRound;设置交叉点的样式
[[UIColor redColor] setStroke];设置线条颜色
[[UIColor greenColor] setFill];设置填充色
1)在故事板中拉入一个view,并在第三个检查器class与自己创建的类关联起来
2)创建贝塞尔路径实例
UIBezierPath *path = [UIBezierPath bezierPath];
3)绘制,填充颜色、线条显示出来
[path stroke]; [path fill];
MyView.h
MyView.m
#import "MyView.h"
@implementation MyView
- (void)drawRect:(CGRect)rect
{
//创建贝塞尔路径的实例
UIBezierPath *path = [UIBezierPath bezierPath];
[path addArcWithCenter:CGPointMake(120, 120) radius:50 startAngle:M_PI_2 endAngle:M_PI clockwise:YES];
[path addLineToPoint:CGPointMake(50, 50)];
//移动到起始点
//[path moveToPoint:CGPointMake(50, 50)];
//[path addLineToPoint:CGPointMake(50, 150)];
//[path addLineToPoint:CGPointMake(200, 50)];
//[path addLineToPoint:CGPointMake(50, 50)];
//设置颜色
[[UIColor redColor] setStroke];
[[UIColor greenColor] setFill];
//设置path的常用属性
path.lineWidth = 10;
//设置线头儿的样式
//path.lineCapStyle = kCGLineCapButt;
//设置交叉点的样式
//path.lineJoinStyle = kCGLineJoinRound;
//绘制
[path stroke];
//[path fill];
}
4.4绘制圆弧
【Demo2_BezierPath_Line】
M_PI_2 M_PI
[path addArcWithCenter:CGPointMake(120, 120) radius:50 startAngle:M_PI_2 endAngle:M_PI clockwise:YES];
练习:
定制圆形的下载进度提示条
【Demo3_Custom_DownloadView】
// 重绘视图
[self setNeedsDisplay];
DownloadView.h
DownloadView.m
#import "ViewController.h"
#import "DownloadView.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet DownloadView *downloadView;
@property (weak, nonatomic) IBOutlet UISlider *slider;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.slider.value = 0;
}
- (IBAction)downloadNumber:(UISlider *)sender {
self.downloadView.progressValue = sender.value;
}
DownloadView.h
#import <UIKit/UIKit.h>
@interface DownloadView : UIView
@property(nonatomic,strong)UIColor *progressColor;
@property(nonatomic)CGFloat progressValue;//0~1之间的浮点数
@end
DownloadView.m
#import "DownloadView.h"
@implementation DownloadView
//重写setter方法
- (void)setProgressValue:(CGFloat)progressValue{
// 保留原有的set方法的操作
_progressValue = progressValue;
// 重绘视图
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
UIBezierPath *path = [UIBezierPath bezierPath];
//圆显示在屏幕中心
CGPoint center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2);
[path addArcWithCenter:center radius :(self.bounds.size.width-20)/2 startAngle:M_PI_2*3 endAngle:self.progressValue*2*M_PI+M_PI_2*3 clockwise:YES];
if (self.progressColor) {//设置了即为设置后的颜色,如果没设置就显示默认的蓝色
[self.progressColor setStroke];
}else{
[[UIColor blueColor] setStroke];
}
path.lineWidth = 8;
[path stroke];
}
@end
 
4.5绘制曲线
【Demo4_BezierPath_Curve】
MyView.h
MyView.m
#import "MyView.h"
@implementation MyView
- (void)drawRect:(CGRect)rect
{
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(140, 40)];
起始坐标 第一个拉伸的点
[path addCurveToPoint:CGPointMake(40, 180) controlPoint1:CGPointMake(40, 40) controlPoint2:CGPointMake(140, 180)];
[path addCurveToPoint:CGPointMake(140, 320) controlPoint1:CGPointMake(140, 180) controlPoint2:CGPointMake(40, 320)];
[[UIColor redColor] setStroke];
path.lineWidth = 6;
[path stroke];
}
@end

4.6绘制其他图形 (矩形,圆形,椭圆)
【Demo5_BezierPath_Others】
MyView.h
MyView.m
#import "MyView.h"
@implementation MyView
- (void)drawRect:(CGRect)rect
{
//绘制圆角矩形
// UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(40, 40, 200, 150) cornerRadius:10];
//圆形
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(40, 40, 200, 200)];
[[UIColor redColor] setStroke];
[[UIColor greenColor] setFill];
path.lineWidth = 8;
[path fill];
[path stroke];
}
@end

5.绘制字符串
【Demo6_Draw_String】
使用NSString带有的绘制方法完成:
drawAtPoint:
drawInRect:
根据字符串的内容计算对应的高度:
boundingRectWithSize:
MyView.h
MyView.m
#import "MyView.h"
@implementation MyView
- (void)drawRect:(CGRect)rect
{
NSString *str = @"Hello World";
//设置字符串的格式
NSDictionary *strAttributes =
@{
NSFontAttributeName:[UIFont systemFontOfSize:24],
NSForegroundColorAttributeName:[UIColor redColor]
};
[str drawAtPoint:CGPointMake(40, 40) withAttributes:strAttributes];
//长字符串的绘制
NSString *str2 = @"looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooog String";
//计算出该字符串所占的空间大小
//计算字符串的高
CGRect strRect = [str2 boundingRectWithSize:CGSizeMake(250, 999) options:2 attributes:strAttributes context:nil];
[str2 drawInRect:CGRectMake(40, 80,250,strRect.size.height) withAttributes:strAttributes];
}
@end

6.绘制图片
【Demo7_Draw_Image】
UIImage *image = [UIImage imageNamed:@"icon80.png"];
[image drawAtPoint:CGPointMake(50, 50)];//按图片原始大小显示
[image drawInRect:imageRect];//根据设定的范围对图片进行缩放
MyView.h
MyView.m
#import "MyView.h"
@implementation MyView
- (void)drawRect:(CGRect)rect
{
CGRect imageRect = CGRectMake(50, 50, 200, 200);
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:imageRect];
//按照路径剪切
[path addClip];
UIImage *image = [UIImage imageNamed:@"icon80.png"];
//[image drawAtPoint:CGPointMake(50, 50)];
[image drawInRect:imageRect];
}
@end

作业:
1.参考资源1,绘制聊天气泡
写一个视图类:MessageView
+message:NSString
要求:
a。只做保持在视图右上角的情况
b。最宽不能超过200,高度根据消息的内容来计算
ViewController.h
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end
ViewController.m
#import "ViewController.h"
#import "TRMessageView.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
TRMessageView *view = [[TRMessageView alloc]initWithMessage:@"中文测试一下整体到了屏幕的右侧会是什么样子呢?"];
view.frame = CGRectMake(0, 20, 320, view.height);
[self.view addSubview:view];
TRMessageView *view2 = [[TRMessageView alloc]initWithMessage:@"你来我往的对话中,为什么会有一条灰色的这么难看的横线显示在哪里呢?"];
view2.frame = CGRectMake(0, 20+view.height, 320, view2.height);
[self.view addSubview:view2];
}
@end
TRMessageView.h
#import <UIKit/UIKit.h>
@interface TRMessageView : UIView
@property(nonatomic,readonly)CGFloat height;
-(instancetype)initWithMessage:(NSString *)message;
@end
TRMessageView.m
#import "TRMessageView.h"
@interface TRMessageView()
@property(nonatomic,strong)NSString *message;
@property(nonatomic)CGFloat messageWidth;
@property(nonatomic)CGFloat messageHeight;
@property(nonatomic,readwrite)CGFloat height;
@end
@implementation TRMessageView
-(instancetype)initWithMessage:(NSString *)message{
self = [super init];
if (self) {
self.message = message;
CGRect msgRect = [self.message boundingRectWithSize:CGSizeMake(200, 999)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{
NSFontAttributeName:[UIFont systemFontOfSize:15]
}
context:nil];
self.messageWidth = msgRect.size.width;
self.messageHeight = msgRect.size.height;
self.height = 40+self.messageHeight;
self.backgroundColor = [UIColor whiteColor];
}
return self;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
//绘制气泡
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(280-self.messageWidth, 10, self.messageWidth+20, self.messageHeight+20) cornerRadius:10];
[path moveToPoint:CGPointMake(300, self.messageHeight+20)];
[path addLineToPoint:CGPointMake(310, 30+self.messageHeight)];
[path addLineToPoint:CGPointMake(290, 30+self.messageHeight)];
[[UIColor lightGrayColor] setFill];
[path fill];
CGContextRestoreGState(context);
CGContextSaveGState(context);
//绘制字符串
[self.message drawInRect:CGRectMake(290-self.messageWidth, 20, self.messageWidth, self.messageHeight) withAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:15],NSForegroundColorAttributeName:[UIColor whiteColor]}];
}
@end

2.绘制饼图
写一个视图类:
PieChartView
+radius:CGFloat半径
+width:CGFloat 线宽
+data:NSArray数据
+[item]: PieChartViewItem
PieChartViewItem
+color:UIColor 颜色
+value:CGFloat 0~1 占总体的百分比
TRPieChartViewItem.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface TRPieChartViewItem : NSObject
@property(nonatomic,strong)UIColor *color;
@property(nonatomic)CGFloat value;
-(instancetype)initWithColor:(UIColor *)color andValue:(CGFloat)value;
@end
TRPieChartViewItem.m
#import "TRPieChartViewItem.h"
@implementation TRPieChartViewItem
- (instancetype)initWithColor:(UIColor *)color andValue:(CGFloat)value{
self = [super init];
if (self) {
self.color = color;
self.value = value;
}
return self;
}
@end
TRPieChartView.h
#import <UIKit/UIKit.h>
@interface TRPieChartView : UIView
@property(nonatomic)CGFloat radius;
@property(nonatomic)CGFloat lineWidth;
@property(nonatomic,strong)NSArray *data;
@end
TRPieChartView.m
#import "TRPieChartView.h"
#import "TRPieChartViewItem.h"
@implementation TRPieChartView
-(NSArray *)data{
if (!_data) {
TRPieChartViewItem *item1 = [[TRPieChartViewItem alloc]initWithColor:[UIColor redColor] andValue:0.25];
TRPieChartViewItem *item2 = [[TRPieChartViewItem alloc]initWithColor:[UIColor blueColor] andValue:0.5];
TRPieChartViewItem *item3 = [[TRPieChartViewItem alloc]initWithColor:[UIColor blackColor] andValue:0.15];
TRPieChartViewItem *item4 = [[TRPieChartViewItem alloc]initWithColor:[UIColor greenColor] andValue:0.1];
_data = @[item1,item2,item3,item4];
}
return _data;
}
-(CGFloat)lineWidth{
if (!_lineWidth) {
_lineWidth = 10;
}
return _lineWidth;
}
-(CGFloat)radius{
if (!_radius) {
_radius = 100;
}
return _radius;
}
- (void)drawRect:(CGRect)rect {
CGFloat startAngel = M_PI_2*3;
for (TRPieChartViewItem *item in self.data) {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
UIBezierPath *path = [UIBezierPath bezierPath];
// 画弧线
[path addArcWithCenter:CGPointMake(self.frame.size.width/2, self.frame.size.height/2) radius:self.radius startAngle:startAngel endAngle:(item.value*2*M_PI+startAngel) clockwise:YES];
startAngel +=item.value*2*M_PI;
//画直线
[path addLineToPoint:CGPointMake(self.frame.size.width/2, self.frame.size.height/2)];
//线宽
path.lineWidth = self.lineWidth;
path.lineCapStyle = kCGLineCapButt;
//填充色
[item.color setFill];
//线条颜色
//[[UIColor grayColor] setStroke];
//绘制填充边线
[path fill];
//[path stroke];
CGContextRestoreGState(context);
CGContextSaveGState(context);
}
}
@end

3.自定义Cell,图片是圆角的
新闻客户端中的自定义Cell,里面的图片做成圆角的
===============================================================
知识点
二、贴图、美化
1. iOS的设备的种类
1.1设备的分辨率和坐标系大小(2倍关系)
1.2Retina屏设备对图片的处理
a。程序中的图片:在不同尺寸的图片名称上添加@2x 或 @3x这样的标识即可。系统会根据当前设备的不同,来选择加载哪个版本的图片
b。AppIcon应用程序图标:屏幕上有一个尺寸,设置界面上也有图标,搜索结果中也有图标,设定图片资源库中的AppIcon里面不同尺寸的图片后,完成设置
2、9切片技术
2.1 为什么需要9切片(9 Slice技术)
用代码来实现基于一个简单图片,产生不同尺寸的图片的一种手段
2.2切片原则
4个角不变,中间部分可以横向或纵向拉伸或复制
两种模式:Tile 切片复制 (默认模式)
Stretch切片拉伸
2.3 实现方法
a。利用xcode工具中的图片资源库中的9切片功能
b。编写代码实现切片
如:
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *btnImageView; 和故事版中的image连线
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIImage *btnImage = [[UIImage imageNamed:@"delete_btn"] resizableImageWithCapInsets:UIEdgeInsetsMake(9, 12, 9, 12) resizingMode:UIImageResizingModeStretch]; 编代码实现九切片功能
self.btnImageView.image = btnImage;
}
@end
3. 对控件的贴图美化
3.1 UIButton
四种状态的设置:通过代码或故事板
四种状态的切换:
Normal:正常
Highlighted:高亮,按下后不抬起的状态
Selected:通过修改selected属性设置 .selected=YES(按钮被选中)
Disabled:通过修改enabled属性设置 .enabled=NO(按钮不可用状态)
- (IBAction)changeState:(UIButton *)sender { 将按钮连线,用代码修改
[self.button setSelected:!sender.selected];//连续点击,持续变化
// [sender setSelected:YES];//是否改变,只变化一次
//sender setEnabled:YES
// 按钮摆在那不动:normal
// 点下,不抬起:highLighted
// 点下,松手:normal
// 进入到seleted状态:用代码修改seleted属性
// enabled状态:用代码改
}
如下:view+image+button+field,通过故事板实现即可,不需要编程

3.2 UISlider
setMaximumTrackImage:设置滑动过的区域的背景图
setMinimumTrackImage:设置未滑动到的区域的背景图
setThumbImage:设置滑块中拖动按钮部分的图片
如:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.slider setMaximumTrackImage:[[UIImage imageNamed:@"playing_volumn_slide_foreground"] resizableImageWithCapInsets:UIEdgeInsetsMake(4, 4, 4, 4) resizingMode:UIImageResizingModeTile]forState:UIControlStateNormal];
[self.slider setMinimumTrackImage:[UIImage imageNamed:@"playing_volumn_slide_bg"] forState:UIControlStateNormal];
[self.slider setThumbImage:[UIImage imageNamed:@"playing_volumn_slide_sound_icon"] forState:UIControlStateNormal];
}
4. tintColor
统一管理一个视图中所有子视图和子视图的子视图的颜色,批量修改一些视图的颜色
1.颜色受控制的因素
拥有 xxxTintColor属性,如UISwitch,可以使用属性修改
没有xxxTintColor,受从UIView中继承来的tintColor影响
2.self.window.tintColor影响整个应用的风格,除非某一个视图特别设置了自己的tintColor颜色
5. UIAppearance
遵守此协议的对象,可以批量设置某种控件的外观(颜色、贴图等)(只针对某一类控件)
1.获取方式
+(instancetype)appearance;
2.使用方式
拿到此对象后,通过这个对象设置背景、颜色等来批量设置某一类控件的外观
[ [ UISlider appearance] setTintColor: [ UIColor redColor ] ]
如:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[UISlider appearance]setTintColor:[UIColor purpleColor]];
[[UIButton appearance]setBackgroundImage:[UIImage imageNamed:@"delete_btn"] forState:UIControlStateNormal];
return YES;
}
6. UINavigationBar美化
6.1 设置NavigationBar的颜色
//设置导航栏的背景色
naviBar.barTintColor = [UIColor blackColor];
//设置是否透明
naviBar.translucent = YES;
6.2 给NavigationBar贴图
//设置背景图(图片的高度,竖屏时一般设置为64个点,横屏时高度是52个点)
[naviBar setBackgroundImage:[UIImage imageNamed:@"NavigationBarDefault"] forBarMetrics:UIBarMetricsDefault];竖屏显示
[naviBar setBackgroundImage:[UIImage imageNamed:@"NavigationBarLandscapePhone"] forBarMetrics:UIBarMetricsLandscapePhone];横屏显示
6.3 设置返回按钮的图片
naviBar.backIndicatorImage = [UIImage imageNamed:@"back_btn"];
naviBar.backIndicatorTransitionMaskImage =[UIImage imageNamed:@"back_btn"];
naviBar.tintColor = [UIColor redColor];设置按钮颜色
6.4 设置标题栏的文字字体
naviBar.titleTextAttributes =
@{
NSFontAttributeName:[UIFont boldSystemFontOfSize:24],
NSForegroundColorAttributeName:[UIColor redColor]
};
6.5 设置标题为任意视图
UIStepper *stepper = [[UIStepper alloc]init];
self.navigationItem.titleView = stepper;
6.6 设置状态栏风格 (显示电池栏的风格)
//重写方法,用于设置状态的风格
- (UIStatusBarStyle)preferredStatusBarStyle
{
return UIStatusBarStyleLightContent;
}
6.7 是否显示状态栏
//重写方法,设置状态栏隐藏
- (BOOL)prefersStatusBarHidden
{
return YES;
}
6.8 隐藏NavigationBar
[self.navigationController setNavigationBarHidden:!self.navigationController.navigationBarHidden animated:YES];
ViewController.h
ViewController.m
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UINavigationBar *naviBar = self.navigationController.navigationBar;
naviBar.barTintColor = [UIColor blackColor];
naviBar.translucent = YES;
//设置背景图
[naviBar setBackgroundImage:[UIImage imageNamed:@"NavigationBarDefault"] forBarMetrics:UIBarMetricsDefault];
[naviBar setBackgroundImage:[UIImage imageNamed:@"NavigationBarLandscapePhone"] forBarMetrics:UIBarMetricsLandscapePhone];
//设置返回按钮的的图片
naviBar.backIndicatorImage = [UIImage imageNamed:@"back_btn"];
naviBar.backIndicatorTransitionMaskImage =[UIImage imageNamed:@"back_btn"];
naviBar.tintColor = [UIColor redColor];
//设置标题栏的文字字体
naviBar.titleTextAttributes =
@{
NSFontAttributeName:[UIFont boldSystemFontOfSize:24],
NSForegroundColorAttributeName:[UIColor redColor]
};
//设置标题为其他视图
//UITextField *textField = [[UITextField alloc]init];
UIStepper *stepper = [[UIStepper alloc]init];
self.navigationItem.titleView = stepper;
}
- (IBAction)hideNavigationBar:(UIButton *)sender {
[self.navigationController setNavigationBarHidden:!self.navigationController.navigationBarHidden animated:YES];
}
@end
GreenViewController.h
GreenViewController.m
#import "GreenViewController.h"
@interface GreenViewController ()
@end
@implementation GreenViewController
//重写方法,用于设置状态的风格
- (UIStatusBarStyle)preferredStatusBarStyle
{
return UIStatusBarStyleLightContent;
}
//重写方法,设置状态栏隐藏
- (BOOL)prefersStatusBarHidden
{
return YES;
}
  
点击item得到的界面
7. UITableViewCell的背景贴图
实现步骤:
a。故事板中修改TableView的分割线(Separator)为None
b。故事板中修改TableViewCell的背景色(Background)为clearColor
c。cell.backgroundView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"list"]];
cell.selectedBackgroundView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"listSeleted”]];选中后的背景图设置显示
如:
MyTableViewController.h
MyTableViewController.m
#import "MyTableViewController.h"
@interface MyTableViewController ()
@end
@implementation MyTableViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 10;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
cell.textLabel.text = @"Hello World";
cell.backgroundView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"list"]];
cell.selectedBackgroundView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"listSelected"]];
return cell;
}
 格显示的是虚线
作业:
聊天气泡:
用9slicing实现
类:MessageView
+message:NSString
+fromMe:BOOL
+messageLabel:UILabel
+messagePopImageView:UIImageView//图,切片
效果:
a。屏幕最上方添加一个文本框和一个发送按钮
b。屏幕下方已经存在一些聊天的消息,其中自己说的内容在屏幕右侧,对方说的内容在屏幕的左侧
c。在文本框中输入内容后,算作自己说的内容,然后点击发送按钮,在界面中追加一个气泡,在屏幕的右侧,里面显示文本框中输入的内容,并且键盘收起,文本框内容清空
-(void)refresh{
if(fromMe)
显示蓝色的气泡在右上角
大小根据message属性计算
else
显示灰色的气泡在左上角
大小,计算
}
参考:H02资源包
追加:看Apple官方的NavigationBar的demo
=====================================================================
知识点
三、手势(UIGestureRecognizer)、变形
1.手势(GestureRecognizer)
1.1什么是手势
用户在view上的一些触屏操作,诸如 点、滑动、捏合。。。
1.2 手势的分类
a。一次性手势
触屏动作发生以后,方法只会响应一次。如:点击以下屏幕、解锁动作
b。连续性手势
触屏动作发生以后,方法会连续响应多次。如:长按、捏合、移动、旋转
1.3 手势的本质
本质是一个对象,当用户针对视图发生了一定的动作之后,系统会检测到该动作,并根据具体的动作创建不同种类的手势对象,该对象中会存储与动作有关的一些数据,如触屏动作的坐标点、滑动的快慢、移动的距离。
如果发生的是一次性手势动作,那么就调用一次方法,如果发生的是连续性手势动作,那么就多次调用响应方法
1.4 如何使用手势
step1:创建手势对象
step2:设置与该种手势相关的属性
step3:将手势对象与需要检测的视图关联在一起
1.5 具体的手势种类
所有手势的父类:UIGestureRecognizer
6种手势: UIXXXGestureRecognizer
UITapGestureRecognizer 点击一下屏幕
UISwipeGestureRecognizer 轻扫屏幕,如解锁
UIPinchGestureRecognizer 捏合手势
UIPanGestureRecognizer 移动手势
UILongPressGestureRecognizer 长按手势
UIRotationGestureRecognizer 旋转手势
2.具体的手势使用
2.1 UITapGestureRecognizer (一次性手势)
【Demo1_TapGestureRecognizer】 常用属性:
.numberOfTapsRequired 设置点击数 手指点几下
.numberOfTouchesRequired 设置触点数 几个手指点击
CGPoint location=[gr locationInView:self.view];获取点击动作时,出点的绝对坐标
ViewController.m
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//step1 创建手势对象
UITapGestureRecognizer *tapGR = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap:)];
//step2 设置手势的属性
tapGR.numberOfTapsRequired = 1;
tapGR.numberOfTouchesRequired = 1;
//step3 将手势与视图关联起来
[self.view addGestureRecognizer:tapGR];
}
-(void)tap:(UITapGestureRecognizer *)gr
{
CGPoint location = [gr locationInView:self.view];
NSLog(@"(%.2f,%.2f)",location.x,location.y);
}
@end
2.2 UISwipeGestureRecognizer(一次性手势)
【Demo2_SwipeGestureRecognizer】
常用属性:
.numberOfTouchesRequired触点的个数
.direction 轻扫的方向
注意:direction属性为枚举值,并且是可以进行组合的枚举值,多个枚举值之间使用 “|”按位“或”进行运算。
补充:1<<? 表达式的含义是:对于二进制的数字1进行向左移位,符号右侧是几,就向左移动几位。
ViewController.m
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UISwipeGestureRecognizer *swipeGR = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipe:)];
swipeGR.numberOfTouchesRequired = 1;
//设置轻扫动作的方向
swipeGR.direction = UISwipeGestureRecognizerDirectionLeft|UISwipeGestureRecognizerDirectionRight;
[self.view addGestureRecognizer:swipeGR];
}
-(void)swipe:(UISwipeGestureRecognizer *)gr{
NSLog(@"swipe... ...");
}
@end
2.3 UILongPressGestureRecognizer(连续性手势)
【Demo3_LongPressGestureRecognizer】
常用属性:
longGR.minimumPressDuration = 1;//设置最少按下的持续时间
ViewController.m
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UILongPressGestureRecognizer *longGR = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPress:)];
//设置最少按下的持续时间
longGR.minimumPressDuration = 1;
[self.view addGestureRecognizer:longGR];
}
-(void)longPress:(UILongPressGestureRecognizer *)gr{
NSLog(@"long press... ...");
}
@end
2.4 UIPinchGestureRecognizer(连续性手势,捏合)
【Demo4_PinchGestureRecognizer】
CGFloat scale = gr.scale; //手势的变化比率,向外扩展时,为大于1的数,向内捏合时,为小与1的数
CGFloat velocity = gr.velocity;//手势的变化速率,向外扩展时,为正数,向内捏合时,为负数
以上两个属性不是用来设置的,而是在手势发生时用来读取的。
练习:
界面上 有一个UITextView,看小说,增加手势识别,如果快速扩,小说出现,快速捏,小说隐藏,速度不快时,代表放大或缩小小说的文字大
ViewController.m
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextView *textView;//连线
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.textView.editable = NO;
UIPinchGestureRecognizer *pinchGR = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinch:)];
[self.view addGestureRecognizer:pinchGR];
}
-(void)pinch:(UIPinchGestureRecognizer *)gr{
CGFloat scale = gr.scale;比率
CGFloat velocity = gr.velocity; 速率
//NSLog(@"scale=%.2f,velocity=%.2f",scale,velocity);
if (velocity > 6) {
self.textView.hidden = NO;
} else if (velocity < -6){
self.textView.hidden = YES;
} else{
// 改变字体
self.textView.font = [UIFont systemFontOfSize:17*scale];
}
}
2.5 UIRotationGestureRecognizer (连续性手势,旋转)
【Demo5_RotationGestureRecognizer】
常用属性:
CGFloat rotation = gr.rotation;
代表手势旋转的弧度。顺时针旋转时,为正数,逆时针旋转时为负数
ViewController.m
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;//image上添加图片
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIRotationGestureRecognizer *rotationGR = [[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(rotation:)];
[self.view addGestureRecognizer:rotationGR];
}
-(void)rotation:(UIRotationGestureRecognizer *)gr{
//CGFloat rotation = gr.rotation;
//NSLog(@"%.2f",rotation);
//实现图片的选转
self.imageView.transform = CGAffineTransformMakeRotation(gr.rotation);
}
@end
2.6 UIPanGestureRecognizer(连续性手势,拖动)
【Demo6_PanGestureRecognizer】
常用属性:
//获取移动到的位置在视图坐标系中的绝对位置
CGPoint location = [gr locationInView:self.view];
//获取移动到的新位置相对于移动动作起始点的坐标的横纵向的偏移
CGPoint translation = [gr translationInView:self.view];
ViewController.m
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIPanGestureRecognizer *panGR = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];
[self.view addGestureRecognizer:panGR];
}
-(void)pan:(UIPanGestureRecognizer *)gr{
//获取移动到的位置在视图坐标系中的绝对位置
CGPoint location = [gr locationInView:self.view];
//获取移动到的新位置相对于移动动作起始点的坐标的横纵向的偏移
CGPoint translation = [gr translationInView:self.view];
self.myImageView.transform=CGAffineTransformMakeTranslation(translation.x, translation.y);//相对
NSLog(@"location:(%.2f,%.2f) translation:(%.2f,%.2f)",location.x,location.y,translation.x,translation.y);
}
@end
3.变形(Transform)
3.1什么是变形
视图发生了位移、缩放、旋转这样的变化叫做变形。
3.2 如何实现变形?
通过修改视图对象的.transform属性完成变化的效果
位移:translation
缩放:scale
旋转:rotation
3.3 transform属性
类型:CGAffineTransform(仿射)类型的结构体
结构体中包含了6个可变的值和3个定值组成的 3 X 3的矩阵,修改了6个数据中的某一个或某几个就可以实现变形,实际上这6个数很难计算,借助于一些系统的API实现数值的改变。
3.4 修改transform属性的API
位移变换:CGAffineTransformMakeTranslation() 相对最原始变化的
CGAffineTransformTranslate() 相对修改后变化的
缩放变换:CGAffineTransformMakeScale()
CGAffineTransformScale()
旋转变换:CGAffineTransformMakeRotation()
CGAffineTransformRotate()
回到原始状态: CGAffineTransformIdentity
重点注意:变形与自动布局是冲突的,所以在使用变形时,一定要关闭AutoLayout,不关闭的话,产生的效果无法预计。
【Demo7_Transform】
界面创建后,没有做任何变形之前,系统会将每一个视图当前的transform记录到一个常量中CGAffineTransformIdentity,当使用Makexxx()方法进行仿射变换,计算新的矩阵,都是基于这个常量进行变形计算的。当使用没有Make的那组方法时,每次计算新的矩阵都是以传入的transform的值作为基准。
3.5 transform属性的初始常量
CGAffineTransformIdentity
如:通过点击按钮实现图片变化
ViewController.m
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
//位移
- (IBAction)translation:(UIButton *)sender {
//self.imageView.transform = CGAffineTransformMakeTranslation(0, 350);
self.imageView.transform = CGAffineTransformTranslate(self.imageView.transform, 3, 3);
//self.imageView.center = CGPointMake(self.imageView.center.x+2, self.imageView.center.y+2);
}
//缩放
- (IBAction)scale:(UIButton *)sender {
//self.imageView.transform = CGAffineTransformMakeScale(1.5, 1.5);
self.imageView.transform = CGAffineTransformScale(self.imageView.transform, 1.2, 1.2);
}
//旋转
- (IBAction)rotation:(UIButton *)sender {
//self.imageView.transform = CGAffineTransformMakeRotation(M_PI_4);
self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, -M_PI_4);
}
//回到原始位置
- (IBAction)identity:(UIButton *)sender {
self.imageView.transform = CGAffineTransformIdentity;
}
@end

4.手势+变形
4.1 使用pan手势实现位移
CGPoint translation = [gr translationInView:self.view];
CGPoint center = CGPointMake(self.imageView.center.x+translation.x, self.imageView.center.y+translation.y);
self.imageView.center = center;
//将这一次移动的偏移量归零
[gr setTranslation:CGPointZero inView:self.view];
4.2 使用pinch手势实现缩放变形
// 实现图片的缩放
self.imageView.transform = CGAffineTransformScale(self.imageView.transform,gr.scale, gr.scale);
gr.scale = 1;//
4.3 使用rotation手势实现旋转变形 // 实现图片的旋转
self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, gr.rotation);
gr.rotation = 0;
4.4 多手势共存的问题
解决步骤:
a。遵守协议
b。设置各个手势的代理
c。实现一个方法 (遵守协议时点开发放寻找粘贴过来即可)
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
5.在storyboard中实现手势
【Demo8_GestureRecognizer_Storyboard】
1.从资源库中拖拽某种类型的手势对象到视图中
2.如果拖拽时就想与某视图关联起来,那么就把手势对象拖到视图中,如果不想关联,那么就拖拽到场景的资源条上
3.选中要关联的视图,按住control,连线到场景的资源条上的手势对象
4.
TransformViewController.m
#import "TransformViewController.h"
@interface TransformViewController ()<UIGestureRecognizerDelegate> //遵守协议
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation TransformViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//pan手势
UIPanGestureRecognizer *panGR = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];
[self.view addGestureRecognizer:panGR];
//pinch手势
UIPinchGestureRecognizer *pinchGR = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinch:)];
//设置pinch的代理
pinchGR.delegate = self;
[self.view addGestureRecognizer:pinchGR];
//rotation手势
UIRotationGestureRecognizer *rotationGR = [[ UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(rotation:)];
//设置rotation的代理
rotationGR.delegate = self;
[self.view addGestureRecognizer:rotationGR];
}
-(void)pan:(UIPanGestureRecognizer *)gr{
//实现图片的位移
//CGPoint location = [gr locationInView:self.view];
CGPoint translation = [gr translationInView:self.view];
CGPoint center = CGPointMake(self.imageView.center.x+translation.x, self.imageView.center.y+translation.y);
self.imageView.center = center;
//将这一次移动的偏移量归零
[gr setTranslation:CGPointZero inView:self.view];
}
-(void)pinch:(UIPinchGestureRecognizer *)gr{
// 实现图片的缩放
self.imageView.transform = CGAffineTransformScale(self.imageView.transform,gr.scale, gr.scale);
gr.scale = 1;
}
-(void)rotation:(UIRotationGestureRecognizer *)gr{
// 实现图片的旋转
self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, gr.rotation);
gr.rotation = 0;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
@end

【综合练习】:
图片查看器:
1)使用代码想view中添加一个UIImageView对象,UIImageView的大小和图片大小一致,找一张大图(用大象)
2)使用center属性将ImageView移动到屏幕的中央
3)使用transform属性将imageView缩放到屏幕刚好能显示的下正常图片的内容,且保持宽高比
4)对imageView增加rotation手势,支持图片旋转
5)对imageView增加pinch手势,支持图片的缩放
6)对imageView增加pan手势,支持图片的移动
7)对imageView增加tap手势,床架回到到第3步
注意:手势不要添加到self.view,要添加到图片上,记得打开imageView的用户交互(.userInteractionEnable=YES)
作业:
ViewController.m
#import "ViewController.h"
@interface ViewController ()<UIGestureRecognizerDelegate>//遵守协议
@property(nonatomic,strong)UIImageView *imageView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//创建ImageView
UIImageView *imageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"Elephant.jpg"]];
self.imageView = imageView;
//设置初始的状态
[self loadImageView];
//添加
[self.view addSubview:imageView];
//开启交互
imageView.userInteractionEnabled = YES;
//设置手势
UITapGestureRecognizer *tapGR = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap:)];
tapGR.numberOfTapsRequired = 2;
[self.imageView addGestureRecognizer:tapGR];
UIPinchGestureRecognizer *pinchGR = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinch:)];
pinchGR.delegate = self;
[self.imageView addGestureRecognizer:pinchGR];
UIPanGestureRecognizer *panGR = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];
[self.imageView addGestureRecognizer:panGR];
UIRotationGestureRecognizer *rotationGR = [[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(rotation:)];
rotationGR.delegate =self;
[self.imageView addGestureRecognizer:rotationGR];
}
-(void)loadImageView{
//设置中心点
self.imageView.center = self.view.center;
CGFloat scaleX = self.view.bounds.size.width/self.imageView.bounds.size.width;
CGFloat scaleY = self.view.bounds.size.height/self.imageView.bounds.size.width;
self.imageView.transform = CGAffineTransformMakeScale(MIN(scaleX, scaleY), MIN(scaleX, scaleY));
}
//移动
-(void)pan:(UIPanGestureRecognizer *)panGR{
CGPoint translation = [panGR translationInView:self.view];
CGPoint center = CGPointMake(self.imageView.center.x+translation.x, self.imageView.center.y+translation.y);
self.imageView.center = center;
[panGR setTranslation:CGPointZero inView:self.view];
}
//缩放
-(void)pinch:(UIPinchGestureRecognizer *)pinchGR{
self.imageView.transform = CGAffineTransformScale(self.imageView.transform, pinchGR.scale, pinchGR.scale);
pinchGR.scale = 1;
}
//旋转
-(void)rotation:(UIRotationGestureRecognizer *)rotationGR{
self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, rotationGR.rotation);
rotationGR.rotation = 0;
}
//回到起始点中心点
-(void)tap:(UITapGestureRecognizer *)gr{
[self loadImageView];
}
//实现旋转和缩放同时实现的方法
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
@end

www.raywenderlich.com
2.根据文档,把一个TableCell实现移动。
长按Cell后,拖到哪里就放在哪里
====================================================================
知识点
五、坐标系、触控
1.坐标系(frame、bounds、center、transform)
1.1 frame属性
【Demo1_Frame_Bounds_Center_Transform】
a。什么是frame?
类型:CGRect结构体类型
作用:该视图左顶点在父视图的坐标系中的位置,以及,该视图在父视图中占据的宽和高
b。直接修改了frame属性时,其他属性如何变化?
bounds:会被改变
center:会被改变
transform:不会被改变
c。什么时候使用到frame?
当把一个视图添加到父视图中时,一定要重设定frame属性
1.2 bounds属性 (相对与自己的)
a。什么是bounds属性?
类型:CGRect结构体类型
作用:描述的是该视图的坐标系顶点的值,以及该视图自身的大小
b。直接修改了bounds属性时,其他属性如何变化?
frame: 会被改变
center:不会被改变
transform:不会被改变
c。什么时候使用bounds属性?
当需要定位视图时,要读取父视图的大小,那么就是用父视图的bounds
当修改视图内的子视图的位置时,可以修改视图的bounds的坐标起点,从而让子视图的位置发生偏移,实现移动的效果
#import "OtherViewController.h"
@interface OtherViewController ()
@end
@implementation OtherViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *view1=[[UIView alloc]initWithFrame:CGRectMake(100, 200, 80, 100)];
view1.backgroundColor=[UIColor redColor];
[self.view addSubview:view1];
}
- (IBAction)button:(UIButton *)sender {
[self.view setBounds:CGRectMake(self.view.bounds.origin.x, self.view.bounds.origin.y+10, self.view.bounds.size.width, self.view.bounds.size.height)];
}
@end
点击按钮,图片垂直向上移动
1.3 center属性
a。什么是center属性?
类型:CGPoint结构体类型
作用:描述的是该视图的中心点,在父视图坐标中的位置
b。直接修改了center属性时,其他属性如何变化?
frame:会被改变
bounds:不会被改变
transform:不会被改变
c。什么时候用center?
需要修改视图的位置,也就是位移时,需要修改center
1.4 transform属性
a。什么是transform属性?
类型:CGAffineTransform结构体类型
作用:描述该视图的变形状态
b。直接修改了transform属性时,其他属性如何变化?
frame:会
bounds:不会
center:不会
c。结论:
变形前,frame和bounds保持变化的一致性,变形后,frame代表的是在视图中表现出来的外观,所以会随着变形而记录不同的外观状态,但bounds不是用来表现的,只是记录大小的,所以不会改变,bounds的坐标系也不会改变
ViewController.m
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self print];
}
//直接修改frame
- (IBAction)changFrame:(UIButton *)sender {
CGRect frame=self.imageView.frame;
frame.origin.x+=4;
frame.origin.y+=4;
frame.size.width+=4;
frame.size.height+=4;
self.imageView.frame=frame;
[self print];
}
//直接修改bounds
- (IBAction)changeBounds:(UIButton *)sender {
CGRect bounds=self.imageView.bounds;
//bounds.origin.x+=4;
// bounds.origin.y+=4;
bounds.size.width+=4;
bounds.size.height+=4;
self.imageView.bounds=bounds;
[self print];
}
//直接修改center
- (IBAction)changeCenter:(UIButton *)sender {
CGPoint center=self.imageView.center;
center.x+=4;
center.y+=4;
self.imageView.center=center;
[self print];
}
//直接修改transform
- (IBAction)changeTransform:(UIButton *)sender {
self.imageView.transform=CGAffineTransformTranslate(self.imageView.transform, 4, 4);
self.imageView.transform=CGAffineTransformScale(self.imageView.transform, 1.3, 1.3);
self.imageView.transform=CGAffineTransformRotate(self.imageView.transform, M_1_PI);
[self print];
}
-(void)print{
NSLog(@"\nframe:%@\nbounds:%@\ncenter:%@\ntransform:%@",
NSStringFromCGRect(self.imageView.frame),
NSStringFromCGRect(self.imageView.bounds),
NSStringFromCGPoint(self.imageView.center),
NSStringFromCGAffineTransform(self.imageView.transform));
}
@end
点击了bounds按钮
原始:
frame:{{91, 33}, {112, 100}}
bounds:{{0, 0}, {112, 100}}
center:{147, 83}
transform:[1, 0, 0, 1, 0, 0]
点击后:
frame:{{89, 31}, {116, 104}}
bounds:{{0, 0}, {116, 104}}
center:{147, 83}
transform:[1, 0, 0, 1, 0, 0]
2.触控(UITouch)
2.1 是什么?
是一个UITouch类型的对象,当用户touch视图时,会自动产生UITouch对象
2.2 如何获取Touch
需要自定义视图类,覆盖类中的指定的方法,在方法中才能获取到这个Touch对象
2.3 有什么用?
可以跟踪用户在视图上手指移动的轨迹,判断用户的意图,以此进行绘图、涂鸦、手写等操作
2.4 怎么用?
step1:自定义一个视图类
step2:重写类中的方法即可
touchesBegan:withEvent://手指接触视图时调用
touchesMoved:withEvent://手指在视图上移动时调用
touchesEnded:withEvent://手指离开视图时调用
注意:手势是对触控的一个封装
【Demo3_UITouch】
MyView.h(自己创建的类,关联到VC)
#import "MyView.h"
@implementation MyView
//手指接触调用
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch*touch=[touches anyObject];
CGPoint location=[touch locationInView:self];
NSLog(@"手指接触屏幕:(%.2f,%.2f)",location.x,location.y);
}
//手指在视图移动时
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch*touch=[touches anyObject];
CGPoint location=[touch locationInView:self];
NSLog(@"手指在视图上移动:(%.2f,%.2f)",location.x,location.y);
}
//离开时
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(@"手指离开视图");
}
@end
手指接触屏幕:(147.00,87.00)
手指在视图上移动:(146.50,89.00)
手指在视图上移动:(148.50,92.00)
手指离开视图
练习:简易画板
【Demo4_Touch_Paint】
PaintView.h
PaintView.m
#import "PaintView.h"
@interface PaintView ()
//记录所有路径的可变数组
@property(nonatomic,strong)NSMutableArray *paths;
@end
@implementation PaintView
- (NSMutableArray *)paths{
if (!_paths) {
_paths = [NSMutableArray array];
}
return _paths;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
//获取UITouch对象
UITouch *touch = [touches anyObject];
CGPoint startPoint = [touch locationInView:self];
// 创建路径
UIBezierPath *path = [UIBezierPath bezierPath];
[path setLineWidth:3];
[path moveToPoint:startPoint];
// 将路径对象记录到数组属性中
[self.paths addObject:path];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self];
//将刚刚创建的路径添加一个直线到该点
//从数组中取出最后一个元素,就是刚刚手指落下时
//创建的路径
UIBezierPath *currentPath = [self.paths lastObject];
[currentPath addLineToPoint:currentPoint];
//重绘
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
//绘制所有路径
for (UIBezierPath *path in self.paths) {
[path stroke];
}
}
@end

作业:
1. 做一个界面上画矩形的效果
按下手指后开始画,拖动时大小变化,松手后定在屏幕上,支持绘制多个矩形
2. 在作业1的基础上,支持选择绘制的颜色,支持线条的粗细设置
3. 在作业1或2的基础上,增加橡皮功能
4. 支持各种形状—矩形,圆角矩形,椭圆。。。。
=======================================================================================================
知识点
六、布局
1.布局 (Layout)控制器中的view
1.1 什么是布局?
是指在一个视图中如何摆放它的子视图(安排子视图的位置和大小)
1.2 为什么要布局?
屏幕的尺寸会经常发生变化或者随着设备不同,屏幕尺寸也会不同,只要屏幕大小发生了变化,坐标系也会随之变化,于是变化前设置的frame在新的坐标系中定位就会与期待的不符,所以就需要在屏幕发生变化时重新布局指定frame
【Demo5_Layout】
1.3 可能导致屏幕大小发生变化的原因
a。设备不同(3.5寸 4存。。。。)
b。屏幕方向不同
c。状态栏
隐藏
特殊的状态栏:来电时,绿色状态栏
录音时,红色状态栏
开启个人热点,蓝色状态栏
d。各种Bar
NavigationBar: 44/32 如果设置了prompt,bar会更高
TabBar:49个点高
ToolBar:44/32 个点
iOS7中,导航栏挤占了状态栏,高度是64/52
e。键盘
弹出时挤占屏幕,高度不确定,因为中英文键盘的高度不同
1.4 如何布局
方法一:纯代码布局,古老的方法
理念:当屏幕发生变化时,自动执行一段我们写好的代码,代码中重新计算了视图的frame,从而达到在新坐标系下重新定位的目的
特点:功能强大,非常繁琐
方法二:Auto Resizing 以前的一种自动布局技巧
理念:记录视图与父视图的边缘为可调整或固定值,然后屏幕发生变化时,依据这段相对的距离,重新布局视图
特点:操作简单,功能有限
方法三:Auto Layout 最新的自动布局方法
理念:将视图与视图之间的位置以及视图自身的大小,用多个约束来记录,当屏幕发生变化时,系统根据定好的约束,自动计算满足该约束情况下的新的坐标值,然后调整位置
特点:简单易用
注意:以上的布局方式,选择其一使用。
4. 纯代码布局
4.1 理念:在屏幕大小发生变化时,通过代码的方式改变视图的frame
4.2 重写控制器中方法:viewDidLayoutSubviews:即可
该方法,在屏幕发生变化时,由系统自动调用,所以,改变frame的代码写在这个方法中就会被自动执行了。
注意:使用纯代码布局时,一定要关闭AutoLayout,否则代码可能会无效
【Demo5_Layout】
【练习】
1.两个等宽的按钮,高40,有背景色
———————————————
| 20 20 |
|-20-[button1]-10-[button2]-20-|
2.在1的基础上,加一个大小会变化的ImagView(内有图片)
imageView离屏幕上、下、左、右保持70,50,20,20
3.在2的基础上,增加三个按钮,大小是20X20,永远排列在屏幕的右下角
【b1】-10-【b2】-10-【b3】-20-|
20 20 20 |
———————————————|
b1,2,3离下边缘都是20个点
时间:半个小时
补充:
关掉AutoLayout以后,drawRect方法中针对视图绘制的图形在屏幕旋转时会被拉伸,解决该问题的话,需要设置视图的“contentMode”设置为“redraw”即可
解决方法:设置视图的的contentMode为redraw即可
ViewController.h
ViewController.m
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIButton *button1;
@property (weak, nonatomic) IBOutlet UIButton *button2;
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property (weak, nonatomic) IBOutlet UIButton *b1;
@property (weak, nonatomic) IBOutlet UIButton *b2;
@property (weak, nonatomic) IBOutlet UIButton *b3;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
CGFloat buttonWidth = (self.view.bounds.size.width - 20 - 20 - 10)/2;
CGRect frame = CGRectMake(20, 20, buttonWidth, 40);
self.button1.frame = frame;
frame.origin.x += buttonWidth + 10;
self.button2.frame = frame;
frame.size = CGSizeMake(self.view.bounds.size.width-20-20, self.view.bounds.size.height-70-50);
frame.origin = CGPointMake(20, 70);
self.imageView.frame = frame;
frame.size = CGSizeMake(20, 20);
frame.origin = CGPointMake(self.view.bounds.size.width-20-20, self.view.bounds.size.height-20-20);
self.b3.frame =frame;
frame.origin.x -= (10+20);
self.b2.frame = frame;
frame.origin.x -= (10+20);
self.b1.frame = frame;
}
@end
故事板中的界面随意布局就好,做一个控件的创建就好了
2.UIView对内部的子视图的布局(典型应用:TableViewCell对内部子视图的布局)
1.1 如何实现?
step1:自定义视图,继承自UIView
step2:重写自定义视图的方法
a。 viewWillLayoutSubViews
b。 layoutSubViews
c。 viewDidLayoutSubView
方法执行的顺序:a->b->c
一般重写 layoutSubViews方法即可。
练习:音乐列表
【Demo1_TableViewCell_Layout】
Mondel
TRMusic.h
#import <Foundation/Foundation.h>
@interface TRMusic : NSObject
@property (nonatomic, copy) NSString * name;//歌曲名
@property (nonatomic, copy) NSString * album;//专辑
@property (nonatomic, copy) NSString * artist;//艺术家
@property (nonatomic) NSTimeInterval duration;//时长
@property (nonatomic) BOOL highQuality;//高品质
@property (nonatomic) BOOL downloaded;//下载
@end
TRMusic.m
#import "TRMusic.h"
@implementation TRMusic
@end
TRMusicGroup.h
#import <Foundation/Foundation.h>
#import "TRMusic.h"
typedef NS_ENUM(NSInteger, TRMusicGroupState) {
TRMusicGroupStateNormal, //音乐组的状态
TRMusicGroupStateDownloading,
TRMusicGroupStateDownloaded
};
@interface TRMusicGroup : NSObject
@property (nonatomic, copy) NSString * name;//组名字
@property (nonatomic, strong) NSArray * musics;//多个音乐
@property (nonatomic) TRMusicGroupState state;//状态
+ (NSArray *) fakeData;
@end
TRMusicGroup.m
#import "TRMusicGroup.h"
@implementation TRMusicGroup
+ (NSArray *) fakeData
{
NSMutableArray * musics = nil;
TRMusic * music = nil;
musics = [NSMutableArray array];
music = [[TRMusic alloc] init];
music.name = @"Burn";
music.album = @"Burn - Single";
music.artist = @"Ellie Goulding";
music.duration = [self durationWithMinutes:3 andSeconds:51];
music.downloaded = YES;
music.highQuality = NO;
[musics addObject:music];
music = [[TRMusic alloc] init];
music.name = @"Summertime Sadness (Cedric Gervais Remix)";
music.album = @"Summertime Sadness (Cedric Gervais Remix) - Single";
music.artist = @"Lana Del Rey";
music.duration = [self durationWithMinutes:6 andSeconds:52];
music.downloaded = YES;
music.highQuality = YES;
[musics addObject:music];
music = [[TRMusic alloc] init];
music.name = @"Spectrum";
music.album = @"Clarity";
music.artist = @"Zedd";
music.duration = [self durationWithMinutes:4 andSeconds:3];
music.downloaded = YES;
music.highQuality = YES;
[musics addObject:music];
music = [[TRMusic alloc] init];
music.name = @"It's Time";
music.album = @"It’s Time";
music.artist = @"Imagine Dragons";
music.duration = [self durationWithMinutes:4 andSeconds:0];
music.downloaded = NO;
music.highQuality = YES;
[musics addObject:music];
music = [[TRMusic alloc] init];
music.name = @"Dancing in The Moonlight";
music.album = @"Dancing In The Moonlight: The Best Of Toploader";
music.artist = @"Toploader";
music.duration = [self durationWithMinutes:3 andSeconds:53];
music.downloaded = YES;
music.highQuality = YES;
[musics addObject:music];
TRMusicGroup * g1 = [[TRMusicGroup alloc] init];
g1.name = @"国外单曲";
g1.musics = [musics copy];
g1.state = TRMusicGroupStateDownloaded;
musics = [NSMutableArray array];
music = [[TRMusic alloc] init];
music.name = @"你有本事抢男人";
music.album = @"好大的胆子";
music.artist = @"雪姨";
music.duration = [self durationWithMinutes:3 andSeconds:18];
music.downloaded = NO;
music.highQuality = NO;
[musics addObject:music];
music = [[TRMusic alloc] init];
music.name = @"喂鸡";
music.album = @"六十年代生人";
music.artist = @"刘欢";
music.duration = [self durationWithMinutes:3 andSeconds:41];
music.downloaded = NO;
music.highQuality = YES;
[musics addObject:music];
music = [[TRMusic alloc] init];
music.name = @"忐忑";
music.album = @"自由鸟";
music.artist = @"龚琳娜";
music.duration = [self durationWithMinutes:4 andSeconds:03];
music.downloaded = NO;
music.highQuality = YES;
[musics addObject:music];
TRMusicGroup * g2 = [[TRMusicGroup alloc] init];
g2.name = @"国内神曲";
g2.musics = [musics copy];
g2.state = TRMusicGroupStateNormal;
TRMusicGroup * g3 = [[TRMusicGroup alloc] init];
g3.name = @"Calvin Harris 专辑";
g3.musics = @[];
g3.state = TRMusicGroupStateNormal;
TRMusicGroup * g4 = [[TRMusicGroup alloc] init];
g4.name = @"Ellie Gounding 专辑";
g4.musics = @[];
g4.state = TRMusicGroupStateNormal;
return @[g1, g2, g3, g4];
}
+ (NSTimeInterval) durationWithMinutes:(int)minutes andSeconds:(int)seconds
{
return minutes * 60 + seconds;
}
@end
MusilcTableViewController.h
#import <UIKit/UIKit.h>
#import "TRMusicGroup.h"
@interface MusilcTableViewController : UITableViewController
@property(nonatomic,strong)TRMusicGroup*musicGroup;
@end
MusilcTableViewController.m
#import "MusilcTableViewController.h"
#import "MusicTableViewCell.h"
#import "TRMusic.h"
@interface MusilcTableViewController ()
@end
@implementation MusilcTableViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
//获取列表内容
-(TRMusicGroup *)musicGroup{
if (!_musicGroup) {
_musicGroup=[TRMusicGroup fakeData][0];
}
return _musicGroup;
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.musicGroup.musics.count;
}
/**/
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MusicTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MusicCell" forIndexPath:indexPath];
//找出音乐,获取数据
TRMusic* music=self.musicGroup.musics[indexPath.row];
//将音乐赋给cell
cell.music=music;
return cell;
}
@end
MusicTableViewCell.h
#import <UIKit/UIKit.h>
#import "TRMusic.h"
@interface MusicTableViewCell : UITableViewCell
@property(nonatomic,strong)TRMusic*music;
@end
MusicTableViewCell.m
#import "MusicTableViewCell.h"
@interface MusicTableViewCell () //扩展属性
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
@property (weak, nonatomic) IBOutlet UILabel *durationLabel;
@property (weak, nonatomic) IBOutlet UILabel *artistLabel;
@property (weak, nonatomic) IBOutlet UIImageView *downloadedImageView;
@property (weak, nonatomic) IBOutlet UIImageView *highQualityImageView;
@end
@implementation MusicTableViewCell
//重写set方法
-(void)setMusic:(TRMusic *)music{
_music = music;
//将music的各个属性放到对应的控件上
self.nameLabel.text = self.music.name;
self.durationLabel.text=[NSString stringWithFormat:@"%d:%02d",(int)self.music.duration/60,(int)self.music.duration%60];
self.artistLabel.text=[[self.music.artist stringByAppendingString:@"-"]stringByAppendingString:self.music.album];
self.downloadedImageView.hidden = !self.music.downloaded;
self.highQualityImageView.hidden = !self.music.highQuality;
}
//重写 cell对自己内部子视图的布局
-(void)layoutSubviews{
[super layoutSubviews];
CGFloat x=self.downloadedImageView.frame.origin.x;
if (self.music.downloaded) {
x+=20;
}
if (self.music.highQuality) {
CGRect rect=self.highQualityImageView.frame;
rect.origin.x=x;
self.highQualityImageView.frame=rect;
x+=20;
}
CGRect fram=self.artistLabel.frame;
fram.origin.x=x;
self.artistLabel.frame=fram;
}
@end

1.2. 布局对状态栏和各种Bar的处理
使用属性:topLayoutGuide.length//屏幕上方当前被占据的区域的长度
bottomLayoutGuide.length//屏幕下方当前被占据的长度
【Demo2_Layout_Bar】
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIButton *button;
@property (weak, nonatomic) IBOutlet UILabel *label;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewDidLayoutSubviews{
CGRect frame = self.button.frame;
frame.origin.x = self.view.bounds.size.width-20-frame.size.width;
frame.origin.y = self.topLayoutGuide.length+10;
self.button.frame = frame;
frame = self.label.frame;
frame.origin.x = self.view.bounds.size.width -20 - frame.size.width;
frame.origin.y = self.view.bounds.size.height - self.bottomLayoutGuide.length - frame.size.height;
self.label.frame = frame;
}
//点击屏幕隐藏工具栏
- (IBAction)changeBar:(UITapGestureRecognizer *)sender {
[self.navigationController setNavigationBarHidden:!self.navigationController.navigationBarHidden animated:YES];
[self.navigationController setToolbarHidden:!self.navigationController.toolbarHidden animated:YES];
}
- (BOOL)prefersStatusBarHidden{
return YES;
}
@end

3. 手动设置Autoresizing 布局
3.1 是什么?
是旧版本(iOS5之前)的自动布局奇数。操作简单,API简单,功能也简单,有局限性,很久以前叫 struts/spring(架构/弹性)技术
3.2 核心理念
当界面大小发生变化时,根据变化的比例,对子视图进行同比例的变化
3.3 怎么样
step1:关闭AutoLayout
step2:选中需要布局的子视图
step3:打开检查器5
step4:点亮需要的红线
外框(4个)红线负责子视图到父视图的边缘
内框(2个)红线负责子视图内部是否可以拉伸
【Demo3_Autoresizing】
4.编写代码实现Autoresizing 布局
Autoresizing和代码布局可以同时使用,用代码补齐Autoresizing的不足
button.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleBottomMargin;
代码设置时,规则描述与在检查器中描述相反,只需要设置可变的边距
如:
- (void)viewDidLoad
{
[super viewDidLoad];
/**/
UIButton* button=[UIButton buttonWithType:UIButtonTypeSystem];
[button setTitle:@"button1" forState:UIControlStateNormal];
[button setBackgroundColor:[UIColor redColor]];
button.frame=CGRectMake(self.view.bounds.size.width-120, 20, 100, 40);
[self.view addSubview:button];
//点亮红线
button.autoresizingMask=UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleBottomMargin;
}
5.Autolayout(自动布局)
5.1 是什么?
是从iOS6开始的一个新的布局技术,功能强大,操作复杂。从xcode5开始,慢慢好用了。在xcode6中,功能更强大了。
核心理念:使用约束(constraint)来描述控件在视图中的位置,当屏幕大小发生变化时,系统会根据你设定的约束自动计算出frame的值,然后将该值赋给控件,实现控件的排布
5.2 使用故事板为控件添加约束
【Demo4_AutoLayout】
5.3 操作要点
a。选中控件,分析6点,上下左右及控件的宽高需要哪些约束才能确定
b。约束的添加可以通过屏幕下方的选项,或者是,选中控件后,按住control,连线到屏幕边缘或其他视图
c。可以添加的约束有:对齐方式(与中心点对齐或与其他控件对齐)、与边缘或其他视图的间距(前导间距和尾部边距)、视图的宽高是给定值还是以其他视图做参照标准
d。添加约束后,正确的结果出现时,屏幕中只有蓝色的线,存在红色虚线框框时,代表视图占据的区域,有橘色线条时,代表当前摆放位置与定义的位置有距离,可以通过底部的第三个选项菜单,选择更新某个视图的frame或更新所有视图的frame
e。选中一个视图,查看第5个检查器可以看到该视图已经添加了的约束,可以选中约束修改约束的内容
f。选中一个视图,通过查看场景的文档结构图,观察该场景下的约束是否有错误或警告,如果有,可以点击该场景的右上角的红色点点,进入说明界面,红色的提示为一场,必须修改为正确,方可代表系统认可约束,可以进行布局,橘色的提示,一般是实际位置与约束位置有偏差,只要更新frame,就可以让橘色的警告消失
6.Auto Layout 代码创建约束
1.1 什么时候用?
当子视图对象时代码创建时,并且需要给子视图布局时,只能用代码来进行自动布局
1.2 如何添加约束?
step1:创建约束对象
NSLayoutConstraint (API)
step2:将约束添加到父视图
1.3 创建约束对象
方法一:使用万能公式
+ (instancetype)constraintWithItem:(id)view1
attribute:(NSLayoutAttribute)attr1
relatedBy:(NSLayoutRelation)relation
toItem:(id)view2
attribute:(NSLayoutAttribute)attr2
multiplier:(CGFloat)multiplier
constant:(CGFloat)c
公式:view1.attr1<relation>view2.attr2*multiplier +contant
如:
button.left = self.view.left*0 + 20
button.right = self.view.width*1 + (-20)
注意:translateAutoresizingToConstraints这个属性默认是YES,这个属性代表:将视图默认自带的Auto resizing特性是否自动转换为对应的约束。既然使用代码来创建约束,那么就不要让系统自带的转换过来的约束影响添加的自定义约束,所以该属性要设置为NO,为了保证效果,可以将视图以及视图的父视图的该属性都设置为NO
//关闭视图自身的翻译
button1.translatesAutoresizingMaskIntoConstraints = NO;
self.view.translatesAutoresizingMaskIntoConstraints = NO;
【Demo1_Autolayou_Code】
如:
- (void)viewDidLoad
{
[super viewDidLoad];
UIButton *button1 = [UIButton buttonWithType:UIButtonTypeSystem];
[button1 setTitle:@"button1" forState:UIControlStateNormal];
button1.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview:button1];
//关闭视图自身的翻译
button1.translatesAutoresizingMaskIntoConstraints = NO;
self.view.translatesAutoresizingMaskIntoConstraints = NO;
//创建约束
NSLayoutConstraint *c1 = [NSLayoutConstraint constraintWithItem:button1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:0 constant:20];
[self.view addConstraint:c1];
NSLayoutConstraint *c2 = [NSLayoutConstraint constraintWithItem:button1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:0 constant:20];
[self.view addConstraint:c2];
//高
NSLayoutConstraint *c3 = [NSLayoutConstraint constraintWithItem:button1 attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:0 constant:150];
[self.view addConstraint:c3];
}
方法二:一次创建多个约束,使用VFL(Visual Format Language)
+ (NSArray *)constraintsWithVisualFormat:(NSString *)format
options:(NSLayoutFormatOptions)opts 参数2:对齐方式
metrics:(NSDictionary *)metrics
views:(NSDictionary *)views 参数4:创建添加的对象
特点:功能强大
【Demo2_Autolayout_Code_VFL】
1.4 VFL(Visual Format Language)
a。是什么?
一个字符串,具有一定的格式,代表一些约束的含义
b。如何写VFL字符串?
| 代表父视图的边
V:| 代表垂直方向的父视图的上边
[ ] 代表一个子视图(或控件)
( ) 代表一个条件(== 、 >=、<=)==可以省略
- 代表间距,默认的8个点
-xxx- 代表间距是多少
[button2(button1)] :button1与button2的高度相同
如: |-20-[button1]-10-[button2(button1)]-10-[button3(button1)]-20-|
V:|-20-[button1]
c。metrics参数 (用于替换参数3)
用于指定VFL字符串中可以替换的值,是一个字典类型
如:NSDictionary *metrics = @{@"left":@20,@"space":@10,@"right":@20};
d。NSDictionaryOfVariableBinding()函数 (用于替换参数4)
NSDictionary *dictionary = NSDictionaryOfVariableBindings(b1,b2,b3);
生成的字典如下:
@{@"b1":b1,@"b2":b2,@"b3":b3}
如:
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//需求:屏幕上方 添加三个等宽的按钮
UIButton *b1 = [UIButton buttonWithType:UIButtonTypeSystem];
[b1 setTitle:@"button1" forState:UIControlStateNormal];
b1.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview:b1];
UIButton *b2 = [UIButton buttonWithType:UIButtonTypeSystem];
[b2 setTitle:@"button2" forState:UIControlStateNormal];
b2.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview:b2];
UIButton *b3 = [UIButton buttonWithType:UIButtonTypeSystem];
[b3 setTitle:@"button3" forState:UIControlStateNormal];
b3.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview:b3];
//1.添加约束的第一步:关闭自动翻译
b1.translatesAutoresizingMaskIntoConstraints = NO;
b2.translatesAutoresizingMaskIntoConstraints = NO;
b3.translatesAutoresizingMaskIntoConstraints = NO;
self.view.translatesAutoresizingMaskIntoConstraints = NO;
//下面的函数会将传入的多个引用构建成如下的字典形式:
//@{@"b1":b1,@"b2":b2,@"b3":b3}
NSDictionary *dictionary = NSDictionaryOfVariableBindings(b1,b2,b3);
NSDictionary *metrics = @{@"left":@20,@"space":@10,@"right":@20};
//2.第二步:创建约束
//2.1准备一个VFL
//NSString*hVFL=@"|-20-[button1]-10-[button2(==button1)]-10-[button3(==button1)]-20-|";
NSString *hVFL = @"|-left-[b1]-space-[b2(b1)]-space-[b3(b1)]-right-|";
//2.2创建约束
NSArray *cs = [NSLayoutConstraint constraintsWithVisualFormat:hVFL options:NSLayoutFormatAlignAllCenterY metrics:metrics views:dictionary];
//3.将约束添加到父视图中
[self.view addConstraints:cs];
NSString *vVFL = @"V:|-left-[button1]";
cs = [NSLayoutConstraint constraintsWithVisualFormat:vVFL options:0 metrics:metrics views:@{@"button1":b1}];
[self.view addConstraints:cs];
}
@end

=======================================================================
知识点
七、动画
1.动画 (Animation)
1.1 是什么?
“帧动画”:一帧 是一张静态的图片,一般情况下,1秒钟达到24,5帧的时候,人眼就分辨不出图片的切换过程,就有连续的效果产生
帧率(FPS)Frame Per Second
1.2 iOS中的动画
UIImage 类,自带一些方法,可以做简单动画
NSTimer 类,间隔指定时间,产生切换效果
UIView类 本来提供了动画的功能
底层的Core Animation 提供了动画的支持
在iOS7中增加一些动画功能:
UIKit Dynamic 动力
Motion Effects 特效
Sprite Kit (2D引擎)
2. UIImage动画
【Demo3_UIImage_Animation】
//duration:播放一组图片用的时间
UIImage *image = [UIImage animatedImageNamed:@"ship-anim" duration:1*5/30]; 1秒播放30帧,播放一张图片需要1/30,5张照片
self.imageView.image = image;
3. NSTimer 动画
4.1 是什么?
一个计时器类,用于定时向指定对象发消息
4.2 如何使用?
【Demo4_NSTimer】
1.//创建计时器
//使用schedule。。开头的方法时,定时器创建完毕就会启动
self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(doSomething:) userInfo:nil repeats:YES];
//使用timerWith。。。开头的方法创建timer
//定时器不会在创建完毕后启动
//需要使用代码将定时器添加到时间循环中才能启动
self.timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(doSomething:) userInfo:nil repeats:YES];
2.//启动定时器
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
3.//关闭定时器
[self.timer invalidate];
练习:【Demo5_NSTimer_Alpha】
使用NSTimer做一个图片淡入的效果(通过修改alpha透明度值来完成)
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property(nonatomic,strong)NSTimer *timer;
@property(nonatomic)NSInteger count;
@end
@implementation ViewController
#define FPS 30.0
#define DURATION 5.0
- (void)viewDidLoad
{
[super viewDidLoad];
self.imageView.alpha = 0;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1/FPS target:self selector:@selector(changeAlpha:) userInfo:nil repeats:YES];
}
//匀速动画的公式
//当前值 = 开始值+当前帧数*(结束值-开始值)/(帧率*动画时长)
-(void)changeAlpha:(NSTimer *)timer{
//count代表当前帧数
self.count++;
self.imageView.alpha = 0 + self.count*(1-0)/(FPS*DURATION);
if (self.count>=FPS*DURATION) {
[timer invalidate];
}
}
@end
4.3 匀速动画
公式:当前值= 开始值+当前帧数*(结束值-开始值)/(帧率*动画时长)
这个值可以是:center transform frame alpha
【Demo6_NSTimer_Animation】
4.4 变速动画
由快到慢、由慢到快、由慢到快再到慢
公式: 当前值 = 上一次的值+(目标值-上一次值)*渐进因子
渐进因子根据情况调节
【Demo7_NSTimer_Animation】
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *airCraft;
@end
@implementation ViewController
#define FPS 30.0
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (IBAction)start:(id)sender {
[NSTimer scheduledTimerWithTimeInterval:1/FPS target:self selector:@selector(move:) userInfo:nil repeats:YES];
}
//当前值=上一次的值+(目标值-上一次的值)*渐进因子
/*
400 + (100 - 400)*0.1 = 370 30p
370 + (100 - 370)*0.1 = 343 27p
343 + (100 - 343)*0.1 = 319.7 24.3
*/
-(void)move:(NSTimer *)timer{
CGPoint center = self.airCraft.center;
center.y = center.y+(50-center.y)*0.05;
self.airCraft.center = center;
if (center.y<=51.0) {
[timer invalidate];//结束
}
}
@end
5. UIView 动画:真正的动画,有专门的API
5.1 是什么
有UIKit专门制作动画的API,这些API的底层对Core Animation的封装,可以轻松实现动画,不用再计算每一帧的值来实现动画的效果
5.2 制作动画的步骤
step1:设置需要动画的视图的初始值
step2:给UIView类发消息,告诉UIView类需要什么样的动画
step3:将动画结束的状态(属性值),即变化的结果,写入到一个Bloack中
[UIView animateWithDuration:2.0 delay:0 options:UIViewAnimationOptionRepeat|UIViewAnimationOptionAutoreverse animations:^{
//设置动画结束时,被动画的那个视图的结束状态
self.airCraft.center = endCenter;
self.airCraft.transform = transform;
} completion:nil];
【Demo8_UIView_Animation】
5.3 动画的高级选项
先慢后快再慢
UIViewAnimationOptionCurveEaseInOut
越来越快
UIViewAnimationOptionCurveEaseIn
越来越慢
UIViewAnimationOptionCurveEaseOut
匀速
UIViewAnimationOptionCurveLinear
动画重复
UIViewAnimationOptionRepeat
反着执行动画,要配合Repeat选项
UIViewAnimationOptionAutoreverse
要求:启动后,从屏幕左边弹出label,从屏幕下边弹出image,然后点击按钮,飞机实现旋转飞行,并重复飞行
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *airCraft;
@property (weak, nonatomic) IBOutlet UILabel *welcomeLabel;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
CGRect endFrame = self.welcomeLabel.frame;
CGRect startFrame = endFrame;
startFrame.origin.x = -startFrame.size.width;
//1.设置需要动画的视图的初始属性
self.welcomeLabel.frame = startFrame;
self.welcomeLabel.alpha = 0.1;
//2.给UIView发消息
[UIView animateWithDuration:2.0 animations:^{
self.welcomeLabel.frame = endFrame;
self.welcomeLabel.alpha = 1.0;
}];
endFrame = self.airCraft.frame;
startFrame = endFrame;
startFrame.origin.y = self.view.bounds.size.height;
self.airCraft.frame = startFrame;
[UIView animateWithDuration:2.0 animations:^{
self.airCraft.frame = endFrame;
}];
}
- (IBAction)start:(id)sender {
//这是当前中心点
CGPoint endCenter = self.airCraft.center;
//计算结束位置的y值
endCenter.y -=300;
//旋转
CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI);
[UIView animateWithDuration:2.0 delay:0 options:UIViewAnimationOptionRepeat|UIViewAnimationOptionAutoreverse animations:^{
//设置动画结束时,被动画的那个视图的结束状态
self.airCraft.center = endCenter;
self.airCraft.transform = transform;
} completion:nil];
}
@end

作业
1.飞机放到屏幕上以后,点哪,飞哪儿
2.做一个购物车的动画
在屏幕的左上角有一个UIImageView,图片是一个商品。
当用户点击此商品时,商品会从上面掉下来,落入到下面的购物车中,
注意:商品掉下来时,屏幕左上角的图片不会消失
例:
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
#define FPS 30.0
#define MAX_SIZE 10
#define MAX_DURATION 10
- (void)viewDidLoad
{
[super viewDidLoad];
//启动定时器,创建雪花
[NSTimer scheduledTimerWithTimeInterval:1/FPS target:self selector:@selector(animate:) userInfo:nil repeats:YES];
}
-(void)animate:(NSTimer *)timer{
//1.创建一个雪花
UIImageView *snow = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"snow.png"]];
int viewWidth = self.view.bounds.size.width;
int viewHeight = self.view.bounds.size.height;
CGFloat size = MAX_SIZE+arc4random()%MAX_SIZE;
snow.frame = CGRectMake(arc4random()%viewWidth, -20, size, size);
[self.view addSubview:snow];
//2.创建动画
[UIView animateWithDuration:arc4random()%MAX_DURATION+2 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
//3. 设置动画结束时雪花的位置信息
int offset = arc4random()%100 - 50;
snow.center = CGPointMake(snow.center.x+offset, viewHeight-30);
}completion:^(BOOL finished) {
//4.落地动画结束时,开始融雪
[UIView animateWithDuration:arc4random()%MAX_DURATION delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
snow.alpha = 0;
} completion:^(BOOL finished) {
//5.融雪动画结束时,将图片移出父视图
[snow removeFromSuperview];
}];
}];
}
======================================================================
知识点
七、Core Animation
1.Core Animation
1.1是什么
是一个图形渲染和动画的底层框架,用于iOS和Mac OS X
1.2 能干什么
1)可以提供更多更强大的图形渲染(显示)效果
2)可以提供专业级的动画效果
3)是高层图形技术的基础
1.3 如何使用Core Animation
(内容较多,很庞大,只讲常用的)
通过CALayer类,直接对一个视图(UIView及子类)的 Core Animation层进行一些设置,达到需要的效果
1.4 如何获得CALayer这一层呢?
任何UIView及其子类都有一个属性叫layer
UIView CALayer
.layer
.frame .frame
.transform .transform3D
.autoresizing .autoresizing
.addSubView: .addSubLayer:
【Demo1_CoreAnimation】
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
CALayer *layer = self.view.layer;
layer.backgroundColor = [[UIColor orangeColor] CGColor];
layer.cornerRadius = 30.0;
self.imageView.layer.cornerRadius = 10.0;
//打开遮罩
self.imageView.layer.masksToBounds = YES;
// 加子层
CALayer *subLayer = [CALayer layer];
subLayer.backgroundColor = [[UIColor purpleColor]CGColor];
subLayer.frame = CGRectMake(30, 200, 100, 120);
subLayer.shadowColor = [[UIColor greenColor]CGColor];
subLayer.shadowOffset = CGSizeMake(2, 2);
subLayer.shadowRadius = 5.0;
subLayer.shadowOpacity = 0.8;阴影透明度,设置它阴影才会显示
subLayer.cornerRadius = 10.0;
[layer addSublayer:subLayer];
//有内容的子层
CALayer *imageLayer = [CALayer new];
imageLayer.frame = CGRectMake(180, 250, 100, 120);
imageLayer.contents = (id)[UIImage imageNamed:@"d.jpg"].CGImage;//添加图片
imageLayer.cornerRadius = 10;
imageLayer.masksToBounds = YES;
[layer addSublayer:imageLayer];
}
@end

1.5 CAAnimation 一个抽象的动画类型,很多时候不关心这个父类,而是使用它的子类来实现动画
1) CAKeyframeAnimation 关键帧动画
可以根据指定的路径进行动画
实现步骤:
step1:创建关键帧动画
使用animationWithKeyPath:方法创建,同时,最后一个字符串参数必须是以下几种选择:
position transform opacity
step2:设置动画属性
step3:将动画添加到视图的layer层上
2)CABasicAnimation可以实现缩放,旋转,透明度等动画
特点:设置动画属性主要为两个:fromValue 和 toValue
例:点击按钮,图片按曲线移动,并且移动的时候会缩小,并且会消失
#import "AnimationViewController.h"
@interface AnimationViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation AnimationViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (IBAction)start:(id)sender {
UIBezierPath *path = [UIBezierPath bezierPath];
[self createPath:path];
// 创建关键帧动画
CAKeyframeAnimation *moveAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
// 设置相关的属性
moveAnimation.path = path.CGPath;
//moveAnimation.duration = 2.0;
//[self.imageView.layer addAnimation:moveAnimation forKey:nil];
//创建缩放动画
CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
scaleAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
scaleAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.1, 0.1, 0.1)];
//scaleAnimation.duration = 2.0;
//[self.imageView.layer addAnimation:scaleAnimation forKey:nil];
//透明度动画
CABasicAnimation *alphaAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
alphaAnimation.fromValue = @1.0;
alphaAnimation.toValue = @0.0;
//alphaAnimation.duration = 2.0;
//[self.imageView.layer addAnimation:alphaAnimation forKey:nil];
//创建动画组,将所有动画对象添加到组中
//针对组设置的动画属性,会被应用到组中的每一个动画上面
CAAnimationGroup *group = [CAAnimationGroup animation];
group.animations = @[moveAnimation,scaleAnimation,alphaAnimation];
group.duration = 2.0;
group.delegate = self;
[self.imageView.layer addAnimation:group forKey:nil];
}
//图片消失
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
[self.imageView removeFromSuperview];
}
//生成路线
-(void)createPath:(UIBezierPath *)path{
[path moveToPoint:self.imageView.center];
CGPoint tartgetPoint = CGPointMake(self.view.bounds.size.width-self.imageView.frame.size.width-20, self.view.bounds.size.height-self.imageView.frame.size.height-20);
CGPoint control1 = CGPointMake(self.view.bounds.size.width-self.imageView.frame.size.width-20, self.imageView.frame.origin.y);
CGPoint control2 = CGPointMake(self.imageView.frame.origin.x, self.view.bounds.size.height-20-self.imageView.frame.size.height);
[path addCurveToPoint:tartgetPoint controlPoint1:control1 controlPoint2:control2];
}
@end

补充:动画停止
//停止
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
self.imageView.layer.transform = CATransform3DRotate(self.imageView.layer.transform,M_PI, 1.0, 1.0, 1.0);
}
1.6CATransform3D
1)是什么
是一个4X4的矩阵,一个结构体。描述了一个3D图片变形的数据
2)创建
CATransform3DMakeRotation/scale/Translation//这组方法在定值常量的基础上变形
CATransform3DScale/Rotate/Translate //这组方法是在传入值的基础上进行变形
=====================================================================
知识点
八、 UIKit Dynamic 动力特效
1. UIKit Dynamic 动力特效
1.1是什么
中文翻译:UIKit 动力、动力模型。。。。iOS7开始的技术。 提供了一个模拟真实世界中力学相关的动画和交互系统,比如,重力、碰撞、吸附等。UIKit Dynamic可以组合 可重用
1.2 UIKit Dynamic架构
a。核心部分:UIDynamicAnimator —>视图的坐标系
b。UIDynamic xx Behavior (行为)
重力 UIGravityBehavior (.magnitude 重力的强度,即加速度)
碰撞 UICollisionBehavior
//将场景视图的四周翻译成可碰撞的四个边
collision.translatesReferenceBoundsIntoBoundary = YES;
吸附 UIAttachmentBehavior
self.attachment.anchorPoint //吸附点 (一个坐标)
self.attachment.frequency=1;//设置吸附行为的频率(左右晃动的大小)
self.attachment.damping=0.1;//设置吸附行为的阻尼(上下弹跳的范围)
闪烁
推力
综合力
【Demo2_Dynamic】
*****给view添加背景色,显示小方格(方法就是重绘一下图形,利用重写初始化方法:然后设置self.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"BackgroundTile"]];
BackgroundView.h
#import <UIKit/UIKit.h>
@interface BackgroundView : UIView
@property(nonatomic,strong)UIBezierPath *path;
@end
BackgroundView.m
#import "BackgroundView.h"
@implementation BackgroundView
//当故事板创建此视图对象时调用此方法,初始化方法
- (id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if (self) {
self.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"BackgroundTile"]];
}
return self;
}
//绘制传入的障碍物图形
- (void)drawRect:(CGRect)rect
{
[[UIColor redColor]setFill];
[[UIColor greenColor]setStroke];
[self.path stroke];
[self.path fill];
}
@end
=======================================================================================================
重力 (点击下落就向下,点击停止即停止)
ViewController.h
ViewController.m
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property(nonatomic,strong)UIDynamicAnimator *animator;
@property(nonatomic,strong)UIGravityBehavior *gravityBehavior;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// 1.构建场景
UIDynamicAnimator *animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
self.animator = animator;
// 2.创建重力行为对象
UIGravityBehavior *gravity = [[UIGravityBehavior alloc]initWithItems:@[self.imageView]];
self.gravityBehavior = gravity;
}
- (IBAction)begin:(id)sender {
// 3.将重力行为添加到场景中
[self.animator addBehavior:self.gravityBehavior];
}
- (IBAction)stop:(id)sender {
[self.animator removeBehavior:self.gravityBehavior];//将重力行为从场景中移除
}
@end
碰撞(依靠重力下落,然后碰到矩形框,改变成红色然后停止)
CollisionViewController.h
CollisionViewController.m
#import "CollisionViewController.h"
#import "BackgroundView.h"
@interface CollisionViewController ()<UICollisionBehaviorDelegate>
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property(nonatomic,strong)UIDynamicAnimator *animator;
@end
@implementation CollisionViewController
- (void)viewDidLoad{
[super viewDidLoad];
self.imageView.transform = CGAffineTransformMakeRotation(M_PI_4);
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
UIDynamicAnimator *animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
self.animator = animator;
//重力行为
UIGravityBehavior *gravity = [[UIGravityBehavior alloc]initWithItems:@[self.imageView]];
//设置重力行为的强度
gravity.magnitude = 1;
[animator addBehavior:gravity];
//碰撞行为
UICollisionBehavior *collision = [[UICollisionBehavior alloc]initWithItems:@[self.imageView]];
//将场景视图的四周翻译成可碰撞的四个边
collision.translatesReferenceBoundsIntoBoundary = YES;
//添加一条矩形的障碍物
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(20, 360, 280, 30) cornerRadius:10.0];
BackgroundView *myView = (BackgroundView *)self.view;
myView.path = path;
[myView setNeedsDisplay];
[collision addBoundaryWithIdentifier:@"MyPath1" forPath:path];
collision.collisionDelegate = self;
[animator addBehavior:collision];
}
//添加代理实现方法 碰撞时方框实现变色
-(void)collisionBehavior:(UICollisionBehavior *)behavior beganContactForItem:(id<UIDynamicItem>)item withBoundaryIdentifier:(id<NSCopying>)identifier atPoint:(CGPoint)p{
//NSLog(@"...");
UIImageView *box = (UIImageView *)item;
box.tintColor = [UIColor redColor];
box.image = [box.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
}
@end
吸附(设置移动,并在移动到的点与图片的中心点绘制直线,然后移动到哪,图片就吸附到哪,然后有重力,图片还会摆动)
AttachmentViewController.h
AttachmentViewController.m
#import "AttachmentViewController.h"
#import "BackgroundView.h"
@interface AttachmentViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property(nonatomic,strong)UIDynamicAnimator *animator;
@property(nonatomic,strong)UIGravityBehavior *gravity;
@property(nonatomic,strong)UIAttachmentBehavior *attachment;
@end
@implementation AttachmentViewController
- (UIDynamicAnimator *)animator{
if (!_animator) {
_animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
}
return _animator;
}
- (UIGravityBehavior *)gravity{
if (!_gravity) {
_gravity = [[UIGravityBehavior alloc]initWithItems:@[self.imageView]];
}
return _gravity;
}
- (UIAttachmentBehavior *)attachment{
CGPoint attachmentAnchor = CGPointMake(self.imageView.center.x, self.imageView.center.y - 100);
if (!_attachment) {
_attachment = [[UIAttachmentBehavior alloc]initWithItem:self.imageView attachedToAnchor:attachmentAnchor];
}
return _attachment;
}
- (IBAction)tap:(UIPanGestureRecognizer *)sender {
CGPoint location = [sender locationInView:self.view];
//将手势滑动到的点作为吸附行为的锚点
self.attachment.anchorPoint = location;
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.animator addBehavior:self.gravity];
self.attachment.action = ^{
//绘制锚点到中心点的悬挂的线
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:self.attachment.anchorPoint];
[path addLineToPoint:self.imageView.center];
BackgroundView *bgView = (BackgroundView *)self.view;
bgView.path = path;
path.lineWidth = 5;
[bgView setNeedsDisplay];
};
[self.animator addBehavior:self.attachment];
}
@end
   
改:吸附类
移动的时候会出现绘图绿线和重力作用,当手势停止,一切动作都停止
AttachmentViewController.h
AttachmentViewController.m
#import "AttachmentViewController.h"
#import "BackgroundView.h"
@interface AttachmentViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property(nonatomic,strong)UIDynamicAnimator *animator;
@property(nonatomic,strong)UIGravityBehavior *gravity;
@property(nonatomic,strong)UIAttachmentBehavior *attachment;
@end
@implementation AttachmentViewController
- (UIDynamicAnimator *)animator{
if (!_animator) {
_animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
}
return _animator;
}
- (UIGravityBehavior *)gravity{
if (!_gravity) {
_gravity = [[UIGravityBehavior alloc]initWithItems:@[self.imageView]];
}
return _gravity;
}
- (UIAttachmentBehavior *)attachment{
//设置锚点
CGPoint attachmentAnchor = CGPointMake(self.imageView.center.x, self.imageView.center.y - 100);
if (!_attachment) {
_attachment = [[UIAttachmentBehavior alloc]initWithItem:self.imageView attachedToAnchor:attachmentAnchor];
}
return _attachment;
}
- (IBAction)tap:(UIPanGestureRecognizer *)sender {
if (sender.state==UIGestureRecognizerStateBegan) {
[self.animator addBehavior:self.gravity];
self.attachment.frequency=1;//设置吸附行为的频率(左右晃动的大小)
self.attachment.damping=0.1;//设置吸附行为的阻尼(上下弹跳的范围)
[self drawLine];
[self.animator addBehavior:self.attachment];
}else if(sender.state==UIGestureRecognizerStateChanged){
CGPoint location = [sender locationInView:self.view];
//将手势滑动到的点作为吸附行为的锚点
self.attachment.anchorPoint = location;
[self drawLine];
}else if(sender.state==UIGestureRecognizerStateEnded){
[self.animator removeBehavior:self.gravity];
[self.animator removeBehavior:self.attachment];
BackgroundView*view=(BackgroundView*)self.view;
view.path=nil;
[view setNeedsDisplay];
}
}
//执行绘图功能
-(void)drawLine{
self.attachment.action = ^{
//绘制锚点到中心点的悬挂的线
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:self.attachment.anchorPoint];
[path addLineToPoint:self.imageView.center];
BackgroundView *bgView = (BackgroundView *)self.view;
bgView.path = path;
path.lineWidth = 5;
[bgView setNeedsDisplay];
};
}
@end
****补充: 在block块中,只要用到self引用必须要用弱引用
-(void)drawLine{
//弱引用
__weak UIAttachmentBehavior* weakAttachment=self.attachment;
__weak UIImageView*weakImageView=self.imageView;
__weak BackgroundView*weakBgView=(BackgroundView*)self.view;
self.attachment.action = ^{
//绘制锚点到中心点的悬挂的线
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:weakAttachment.anchorPoint];
[path addLineToPoint:weakImageView.center];
BackgroundView *bgView = weakBgView;
bgView.path = path;
path.lineWidth = 5;
[bgView setNeedsDisplay];
};
}
4. 闪烁行为 UISnapBehavior
/*
闪烁其实就是快速移动到某一点,使用变形动画也可以做到这个效果,
但是,使用snapBehavior,物体在移动到某点以后
会晃动以下
注意:在animator中不能添加两种同样的特效,所以需要先移除以前的行为,再添加新的行为
*/
SnapViewController.h
SnapViewController.m
#import "SnapViewController.h"
@interface SnapViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property(nonatomic,strong)UIDynamicAnimator*dynamicAnimator;
@property(nonatomic,strong)UISnapBehavior*snapBehavior;
@end
@implementation SnapViewController
-(UIDynamicAnimator *)dynamicAnimator{
if (!_dynamicAnimator) {
_dynamicAnimator=[[UIDynamicAnimator alloc]initWithReferenceView:self.view];
}
return _dynamicAnimator;
}
- (IBAction)pan:(UIPanGestureRecognizer *)sender {
CGPoint point=[sender locationInView:self.view];
//先移除原有的闪烁行为 不然只能移一次
[self.dynamicAnimator removeBehavior:self.snapBehavior];
self.snapBehavior=[[UISnapBehavior alloc]initWithItem:self.imageView snapToPoint:point];
[self.dynamicAnimator addBehavior:self.snapBehavior];
}
@end
5. 推力行为 UIPushBehavior
//推力角度
self.pushBehavior.angle=M_PI_4;//右下角
//推力大小
self.pushBehavior.magnitude=2;
//激活力 push手势的特别之处:需要激活,否则不起作用
self.pushBehavior.active=YES;
如:借助移动手势来实现力的方向
#import "PushViewController.h"
@interface PushViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property(nonatomic,strong)UIDynamicAnimator *animator;
@property(nonatomic,strong)UIPushBehavior *pushBehavior;
@end
/*
push手势的特别之处:需要激活,否则不起作用
设置 .active = YES
*/
@implementation PushViewController
- (UIDynamicAnimator *)animator{
if (!_animator) {
_animator = [[UIDynamicAnimator alloc]initWithReferenceView:self.view];
}
return _animator;
}
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
// 添加碰撞行为,遇到边界则停止
UICollisionBehavior *collision = [[UICollisionBehavior alloc]initWithItems:@[self.imageView]];
[collision setTranslatesReferenceBoundsIntoBoundaryWithInsets:UIEdgeInsetsMake(self.topLayoutGuide.length, 0,self.bottomLayoutGuide.length, 0)
];
[self.animator addBehavior:collision];
//添加推力行为
self.pushBehavior = [[UIPushBehavior alloc]initWithItems:@[self.imageView] mode:UIPushBehaviorModeContinuous];
[self.animator addBehavior:self.pushBehavior];
}
- (IBAction)tap:(UITapGestureRecognizer *)sender {
CGPoint point = [sender locationInView: self.view];
CGPoint center = self.imageView.center;
CGFloat angle = atan2(point.y-center.y, point.x-center.x)+M_PI;
//powf()函数:求某数的某几次幂
//sqrt()函数:开平方
CGFloat distance = sqrt(powf((point.x-center.x), 2)+powf((point.y-center.y), 2));
self.pushBehavior.angle = angle;
self.pushBehavior.magnitude = distance/10;
self.pushBehavior.active = YES;
}
@end
   点击哪就从这个方向施力
练习:SpringMessage
弹簧效果短消息:
1.思路:
利用手指移动的距离,以及item离手指点的位置的远近,产生一个变化的值,用这个值来修改所有collectionView中的item的锚点
2.步骤
step1.首先计算scroll的距离scrollDelta
step2.为了得到每个item与触摸点的之间的距离,还需要知道触摸点的坐标touchLocation。
step3.可以根据距离对每个锚点进行设置了:简单地计算了原来锚点与触摸点之间的距离distanceFromTouch,并由此计算一个系数。
step4.接下来,对于当前的item,我们获取其当前锚点位置,然后将其根据scrollDelta的数值和刚才计算的系数,重新设定锚点的位置。
step5.最后需要告诉UIDynamicAnimator已经完成了对锚点的更新,现在可以开始更新物理计算,并随时准备collectionView来取LayoutAttributes的数据了。
SpringLayout.h
#import <UIKit/UIKit.h>
@interface SpringLayout : UICollectionViewFlowLayout
@property(nonatomic,strong)UIDynamicAnimator *animator;
@end
SpringLayout.m
#import "SpringLayout.h"
/*
1. 为每一个布局属性对象添加行为,让布局属性对象成为behavior的主体
2. 控制器再来询问布局属性对象(layoutAttribute)
时,返回具有行为特征的item
3. 每当屏幕滚动一点,就根据触点和各个item的位置的远近,计算出一个有一定变化规律的因子,根据这个因子,有规律的调整每一个布局属性对象的attachment Behavior的锚点,就可以实现弹簧效果
*/
@implementation SpringLayout
//1. 在vc即将跟布局对象要各个布局属性对象之前
//就将每一个布局属性对象都添加上 吸附 行为
-(void)prepareLayout{
if (!self.animator) {
self.animator = [[UIDynamicAnimator alloc]initWithCollectionViewLayout:self];
//得到整个collectionView的尺寸
CGSize contentSize = [self collectionViewContentSize];
NSArray *layoutAttributes = [super layoutAttributesForElementsInRect:CGRectMake(0, 0, contentSize.width, contentSize.height)];
//得到整个collectionView中的所有向的
//布局属性对象,从而添加 吸附 行为
for (UICollectionViewLayoutAttributes *attribute in layoutAttributes) {
UIAttachmentBehavior *spring = [[UIAttachmentBehavior alloc]initWithItem:attribute attachedToAnchor:attribute.center];
spring.damping = 0.6;
spring.frequency = 0.8;
[self.animator addBehavior:spring];
}
}
}
//2.当vc再来询问每一个布局属性对象时,返回
//修改过的 布局属性对象,或者说返回已经成为
//吸附行为主体 的 那个布局属性对象
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
//体系维护对象 按照指定的矩形区域
//返回该范围内的行为主体(布局属性对象)
return [self.animator itemsInRect:rect]
;
}
//3.根据vc传入的单元格的坐标,返回该单元格
//对应的那个用来布局这个单元格的部署属性对象
//-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
// return [self.animator layoutAttributesForCellAtIndexPath:indexPath];
//}
//4.当边界bounds发生变化时,是否更新边界
//思路:1.不更新边界
// 2.边界发生变化的原因,是因为有滚动事件发生了
// 虽然不更新边界,但要针对这个用户的操作
// 修改一下锚点,以此出现弹簧效果
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
UIScrollView *scrollView = self.collectionView;
//获取滚动的距离,其实就是坐标系变换的大小
CGFloat scrollDistance = newBounds.origin.y - scrollView.bounds.origin.y;
//手指所在的位置
CGPoint touchLocation = [scrollView.panGestureRecognizer locationInView:scrollView];
//计算和修改每一个行为的锚点
for (UIAttachmentBehavior *spring in self.animator.behaviors) {
//因为attribute是 behavior 行为的主体
//所以反过来,从行为上获取 与它绑定的主体
//就需要访问items属性,由于,该行为只关联
//了一个主体,所以取firstObject
UICollectionViewLayoutAttributes *attributes = [spring.items firstObject];
CGPoint center = attributes.center;
CGPoint anchorPoint = spring.anchorPoint;
//手指的触点和项的中心点的远近
//根据 项 离 手指触点的远近,产生一组
//不同大小的 新的anchor值
CGFloat distance = fabsf(touchLocation.y - anchorPoint.y);
// distance是变化的,所以除以最大距离
//也就是一屏幕的高度,取个整,算600
//到底一个小于1的可变化的值
//用这个值作为变化因子,从而使得滚动距离
//与项离触点远近的变化规律相同了
center.y += scrollDistance*(distance/600);
attributes.center = center;
// 更新体系内的每一个行为的状态
[self.animator updateItemUsingCurrentState:attributes];
}
return NO;
}
@end
ViewController.h
ViewController.m
#import "ViewController.h"
#import "SpringLayout.h"
@interface ViewController ()<UICollectionViewDataSource>
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
SpringLayout *layout = [[SpringLayout alloc]init];
layout.itemSize = CGSizeMake(300,40);
layout.sectionInset = UIEdgeInsetsMake(0, 10, 0, 10);
UICollectionView *collectionView = [[UICollectionView alloc]initWithFrame:self.view.frame collectionViewLayout:layout];
collectionView.showsHorizontalScrollIndicator = NO;
collectionView.showsVerticalScrollIndicator = NO;
[collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"MyCell"];
collectionView.dataSource = self;
[self.view addSubview:collectionView];
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return 50;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"MyCell" forIndexPath:indexPath];
cell.backgroundColor = [UIColor lightGrayColor];
return cell;
}
@end
 点击屏幕发生晃动
===================================================================
知识点
九、通知( NSNotification)
1. 通知(Notification)
1.1 是什么? 是一种观察者模式的具体体现
观察者模式:一个对象(A)想知道另一个对象(B)的状态是否发生了改变。思路就是,在对象B上注册一个观察者,当对象B的状态发生改变时,通知对象A,对象A收到通知后进行相关的处理的一种模式
其中观察者模式的一种解决方案叫做—广播
系统中的通知 就是广播的体现
1.2 好处
一个对象不需要知道消息的接收者是谁,就可以将一些消息发送给需要的接收者
有时,发送消息的对象无法知道有哪些对象,有多少对象接收消息,也不知道对象是否存在
有时,消息的接受者和发送者太远(远 指的不是距离,是关系,如 控制器和视图就很近,但Model离控制器就很远)
1.3 具体用法
1)收听者:找到通知中心
NSNotificationCenter,注册要收听的一个具体频道 addObserver
2)发送者:找到通知中心,创建通知对象(NSNotification),使用通知中心来发送这个消息(postNotification)
3)收听者 收到消息 处理消息(掉个方法)
4)停止收听,不需要收听时,找到通知中心,注销 removeObserver
1.4关键的类
NSNotificationCenter,是一个单例类,使用defaultCenter方法永远返回同一个对象,以此保证中心对象只有一个
NSNotification 通知类(封装通知的内容等信息)
【Demo2_Notification】创建控制台程序
Company.h
#import <Foundation/Foundation.h>
@interface Company : NSObject
-(void)broadcast;
@end
Company.m
#import "Company.h"
@implementation Company
-(void)broadcast{
NSNotificationCenter*center=[NSNotificationCenter defaultCenter];
//发消息
[center postNotificationName:@"videoUpdate" object:self userInfo:@{@"title": @"锦绣园",@"episode": @"第15集"}];
}
@end
Vap.h
Vap.m
#import "Vap.h"
@implementation Vap
-(id)init{
if ([super init]) {
NSNotificationCenter*center=[NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(updated:) name:@"videoUpdate" object:nil];
}
return self;
}
-(void)updated:(NSNotification*)notification{
NSDictionary*message=notification.userInfo;
NSLog(@"%@已经更新到%@",message[@"title"],message[@"episode"]);
}
-(void)dealloc{
[[NSNotificationCenter defaultCenter]removeObserver:self];
NSLog(@"dealloc执行了");
}
@end
main.m
#import <Foundation/Foundation.h>
#import "Company.h"
#import "Vap.h"
int main(int argc, const char * argv[])
{
@autoreleasepool {
Company*company=[[Company alloc]init];
Vap*vip=[[Vap alloc]init];
[company broadcast];
NSLog(@"Hello, World!");
}
return 0;
}
2.键盘通知
键盘弹起的通知名称:
UIKeyboardWillShowNotification
键盘收起的通知名称
UIKeyboardWillHideNotification
【Demo3_Keyboard_Notification】
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
// 注册监听
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(keyboardOpen:) name:UIKeyboardWillShowNotification object:nil];
[center addObserver:self selector:@selector(keyboardClosed:) name:UIKeyboardWillHideNotification object:nil];
}
-(void)keyboardOpen:(NSNotification *)notification{
NSDictionary *message = notification.userInfo;
//获取键盘的起始点(0,264)
NSValue *value = message[UIKeyboardFrameEndUserInfoKey];
CGRect rect = [value CGRectValue];
NSLog(@"%f,%f",rect.origin.x,rect.origin.y);
//NSLog(@"%@",message);
}
-(void)keyboardClosed:(NSNotification *)notification{
NSLog(@"close.....");
}
//点击右下角关闭键盘
- (IBAction)inputFinished:(UITextField *)sender {
[sender resignFirstResponder];
}
- (void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter]removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
@end
qq聊天程序
TRMessage.h
#import <Foundation/Foundation.h>
@interface TRMessage : NSObject
@property(nonatomic)BOOL fromMe;
@property(nonatomic,strong)NSString *content;
+(NSMutableArray *) demoData;
@end
TRMessage.m
#import "TRMessage.h"
@implementation TRMessage
+ (NSMutableArray *)demoData{
TRMessage *message = nil;
NSMutableArray *array = [NSMutableArray array];
message = [[TRMessage alloc]init];
message.fromMe = YES;
message.content = @"Hello 你好Hello 你好Hello 你好Hello 你好";
[array addObject:message];
message = [[TRMessage alloc]init];
message.fromMe = NO;
message.content = @"干嘛呢";
[array addObject:message];
message = [[TRMessage alloc]init];
message.fromMe = YES;
message.content = @"没干嘛你呢";
[array addObject:message];
message = [[TRMessage alloc]init];
message.fromMe = NO;
message.content = @"呵呵 呵呵呵";
[array addObject:message];
message = [[TRMessage alloc]init];
message.fromMe = YES;
message.content = @"今天的大新闻就是苹果手表终于发布了,好喜欢红色的,但是买不起";
[array addObject:message];
message = [[TRMessage alloc]init];
message.fromMe = NO;
message.content = @"就知道你买不起,那就别买了";
[array addObject:message];
message = [[TRMessage alloc]init];
message.fromMe = YES;
message.content = @"还是你送我吧";
[array addObject:message];
message = [[TRMessage alloc]init];
message.fromMe = NO;
message.content = @"做梦";
[array addObject:message];
return array;
}
@end
TRMessageCell.h
#import <UIKit/UIKit.h>
#import "TRMessage.h"
@interface TRMessageCell : UITableViewCell
@property(nonatomic,strong)TRMessage *message;
@end
TRMessageCell.m
#import "TRMessageCell.h"
@interface TRMessageCell ()
@property (weak, nonatomic) IBOutlet UIImageView *popImageView;
@property (weak, nonatomic) IBOutlet UILabel *label;
@end
@implementation TRMessageCell
#define CELL_MARGIN_TB 4.0 //气泡上下外边距
#define CELL_MARGIN_LR 10.0 //气泡左右外边距
#define CELL_CORNOR 18.0 //气泡圆角半径
#define CELL_TAIL_WIDTH 16.0 //气泡尾巴
#define MAX_WIDTH_OF_TEXT 200.0 //文字宽度限制
#define CELL_PADDING 8.0 //气泡内边距
- (void)setMessage:(TRMessage *)message{
_message = message;
self.label.text = self.message.content;
//根据消息的来源,对label和image进行定位
if (self.message.fromMe) {//蓝色气泡
//设置标签内容和图片视图中的气泡图片
self.label.textColor = [UIColor whiteColor];
UIImage *image = [UIImage imageNamed:@"message_i.png"];
image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(CELL_CORNOR, CELL_CORNOR, CELL_CORNOR, CELL_CORNOR+CELL_TAIL_WIDTH)];
self.popImageView.image = image;
//1。定位Label
// 先确定文本的高度
CGRect rectOfText = CGRectMake(0, 0, MAX_WIDTH_OF_TEXT, 999);
rectOfText = [self.label textRectForBounds:rectOfText limitedToNumberOfLines:0];
CGRect frameOfLabel = CGRectZero;
frameOfLabel.size = rectOfText.size;
frameOfLabel.origin.y = CELL_MARGIN_TB + CELL_PADDING;
frameOfLabel.origin.x = self.bounds.size.width - CELL_MARGIN_LR - CELL_TAIL_WIDTH - CELL_PADDING - rectOfText.size.width;
self.label.frame = frameOfLabel;
//2。定位popImageView的坐标
CGRect frameOfPop = frameOfLabel;
frameOfPop.origin.x -=CELL_PADDING;
frameOfPop.origin.y -=CELL_PADDING;
frameOfPop.size.width += 2 * CELL_PADDING + CELL_TAIL_WIDTH
;
frameOfPop.size.height += 2 * CELL_PADDING;
self.popImageView.frame = frameOfPop;
//3.设定单元格的bounds
CGRect bounds = self.bounds;
bounds.size.height = frameOfPop.size.height + CELL_MARGIN_TB * 2;
self.bounds = bounds;
}else{//灰色气泡
self.label.textColor = [UIColor darkGrayColor];
UIImage *image = [UIImage imageNamed:@"message_other.png"];
image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(CELL_CORNOR, CELL_CORNOR+CELL_TAIL_WIDTH, CELL_CORNOR, CELL_CORNOR)];
self.popImageView.image = image;
CGRect rectOfText = CGRectMake(0, 0, MAX_WIDTH_OF_TEXT, 999);
rectOfText = [self.label textRectForBounds:rectOfText limitedToNumberOfLines:0];
CGRect frameOfLabel = CGRectZero;
frameOfLabel.size = rectOfText.size;
frameOfLabel.origin.x = CELL_PADDING+CELL_MARGIN_LR+CELL_TAIL_WIDTH;
frameOfLabel.origin.y = CELL_MARGIN_TB + CELL_PADDING;
self.label.frame = frameOfLabel;
CGRect frameOfPop = frameOfLabel;
frameOfPop.origin.x -=(CELL_PADDING + CELL_TAIL_WIDTH);
frameOfPop.origin.y -=CELL_PADDING;
frameOfPop.size.width += CELL_PADDING*2+CELL_TAIL_WIDTH;
frameOfPop.size.height += CELL_PADDING*2;
self.popImageView.frame = frameOfPop;
CGRect bounds = self.bounds;
bounds.size.height = frameOfPop.size.height + CELL_MARGIN_TB *2;
self.bounds = bounds;
}
}
@end
TRMessageViewController.h
#import <UIKit/UIKit.h>
@interface TRMessageViewController : UIViewController
@property(nonatomic,strong)NSMutableArray *messages;
@end
TRMessageViewController.m
#import "TRMessageViewController.h"
#import "TRMessage.h"
#import "TRMessageCell.h"
@interface TRMessageViewController ()<UITableViewDataSource,UITableViewDelegate>
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (weak, nonatomic) IBOutlet UIView *inputTextView;
@property (weak, nonatomic) IBOutlet UITextField *textField;
@end
@implementation TRMessageViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = @"Message";
self.messages = [TRMessage demoData];
[self.tableView registerNib:[UINib nibWithNibName:@"TRMessageCell" bundle:nil] forCellReuseIdentifier:@"Cell"];
// 设置view的背景图
self.inputTextView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"ToolViewBkg_Black.png"]];
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.messages.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
TRMessageCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
cell.message = self.messages[indexPath.row];
return cell;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
//在生成cell的时候,在cell的内部曾经
//根据图片的大小,修改过cell的bounds属性
//随着修改bounds属性,cell的frame就自动被
//修改了,变成我们根据图片计算出来的高度
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
return cell.frame.size.height;
}
//注册监听系统键盘的弹起
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(keyboardAppear:) name:UIKeyboardWillShowNotification object:nil];
[center addObserver:self selector:@selector(keyboardDisappear:) name:UIKeyboardWillHideNotification object:nil];
}
//键盘弹起时
-(void)keyboardAppear:(NSNotification *)notification{
//1.获取键盘的坐标体系
CGRect frameOfKeyboard = [notification.userInfo[UIKeyboardFrameEndUserInfoKey]CGRectValue];
//2.计算输入框的结束的坐标信息
CGRect frameOfInputView = self.inputTextView.frame;
frameOfInputView.origin.y = frameOfKeyboard.origin.y - frameOfInputView.size.height;
//3.计算表格需要滚动的距离
CGPoint offset = self.tableView.contentOffset;
offset.y += frameOfKeyboard.size.height;
//4.为inputTextView添加动画
NSTimeInterval duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
UIViewAnimationOptions options = [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue];
[UIView animateWithDuration:duration delay:0.0 options:options animations:^{
//设置动画结束时 输入视图的新的位置
self.inputTextView.frame = frameOfInputView;
//动画结束时,修改表格的内容的位置
self.tableView.contentOffset = offset;
} completion:nil];
}
//键盘收起时
-(void)keyboardDisappear:(NSNotification *)notification{
CGRect frameOfInputView = self.inputTextView.frame;
frameOfInputView.origin.y = self.view.bounds.size.height - frameOfInputView.size.height;
CGPoint newOffSet = CGPointMake(0,self.tableView.contentSize.height - self.tableView.frame.size.height);
NSTimeInterval duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
UIViewAnimationOptions options = [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue];
[UIView animateWithDuration:duration delay:0.0 options:options animations:^{
self.inputTextView.frame = frameOfInputView;
self.tableView.contentOffset = newOffSet;
} completion:nil];
}
-(void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center removeObserver:self forKeyPath:UIKeyboardWillShowNotification];
[center removeObserver:self forKeyPath:UIKeyboardWillHideNotification];
}
// 点击键盘右下角的return按键
- (IBAction)send:(UITextField *)sender {
// NSLog(@"%f",self.tableView.contentSize.height);
if (![self.textField.text isEqualToString:@""]) {
TRMessage *message = [[TRMessage alloc]init];
message.fromMe = YES;
message.content = self.textField.text;
//清空文本框
self.textField.text = @"";
//message对象添加到数据源
[self.messages addObject:message];
//更新表视图显示新增加的消息
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.messages.count - 1 inSection:0];
[self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationBottom];
// NSLog(@"%f",self.tableView.contentSize.height);
}
//[self.textField resignFirstResponder];
}
@end
 
故事板TRMessageViewController TRMessageCell
 
注意:第一个检查器中关闭自动布局,xib中image的第五个检查器中,点亮的红线只有保持上左即可,其他的关闭就可以了。tableView中第四个检查器中,Separator改为None,去掉表格线。Selection 改为No Selection,实现运行的时候,去掉点击时显示的灰色区域
========================================================================
知识点
十、Search Bar 搜索栏
1.Search Bar(旧版本)
故事板中的添加了Search Bar 然后到第四个检查器 点击勾上Shows Scope Bar
单元格要记得在第四个检查器中注册identifier为cell
Product.h
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, ProductType){
ProductTypeDevice,
ProductTypeSoftware,
ProductTypeOther
};
/*定义商品类,包含名称和类别*/
@interface Product : NSObject
@property(nonatomic,strong)NSString *name;
@property(nonatomic)ProductType type;
+(NSArray *)demoData;
@end
Product.m
#import "Product.h"
@implementation Product
+ (NSArray *)demoData{
Product *p1 = [[Product alloc]init];
p1.name = @"iPhone4s";
p1.type = ProductTypeDevice;
Product *p2 = [[Product alloc]init];
p2.name = @"iPhone5s";
p2.type = ProductTypeDevice;
Product *p3 = [[Product alloc]init];
p3.name = @"iPhone6";
p3.type = ProductTypeDevice;
Product *p4 = [[Product alloc]init];
p4.name = @"iPhone6 Plus";
p4.type = ProductTypeDevice;
Product *p5 = [[Product alloc]init];
p5.name = @"OS X Yosemite";
p5.type = ProductTypeSoftware;
Product *p6 = [[Product alloc]init];
p6.name = @"Airport Time Capsule";
p6.type = ProductTypeOther;
return @[p1,p2,p3,p4,p5,p6];
}
@end
ProductViewController.h
#import <UIKit/UIKit.h>
@interface ProductViewController : UITableViewController
@property(nonatomic,strong)NSArray *products;
@end
ProductViewController.m
#import "ProductViewController.h"
#import "Product.h"
@interface ProductViewController ()<UISearchDisplayDelegate>
//声明一个数组,用于存储搜索到的结果内容
@property(nonatomic,strong)NSMutableArray *searchResult;
@end
@implementation ProductViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.products = [Product demoData];
//创建 searchResult 的实例
self.searchResult = [NSMutableArray array];
//为显示数据的表格注册单元格
[self.searchDisplayController.searchResultsTableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
}
/*
当前控制器已经是两个标题视图的 代理对象了。
一个表视图指的是TVC自带的表视图
另一个表视图指的是用于显示搜索结果数据展示的表视图
*/
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//需要区分参数tableView到底是self.tableView
//还是searchBar的resultTableView
//return self.products.count;
if (tableView == self.searchDisplayController.searchResultsTableView) {
return self.searchResult.count;
}else{
return self.products.count;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
Product *product = nil;
if (tableView == self.view) {
product = self.products[indexPath.row];
}else{
product = self.searchResult[indexPath.row];
}
cell.textLabel.text = product.name;
return cell;
}
//只要在搜索框中修改了搜索的内容,立即执行此方法
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString{
//根据搜索框内写的字符串进行比对
//生成搜索结果
//搜索需要两个数据:文本框中输入的+分段控件中选择的搜索类别
NSInteger type = self.searchDisplayController.searchBar.selectedScopeButtonIndex;
[self updateContentForProductName:searchString andType:type];
return YES;
}
//只要选择了搜索框下面的分段控件,该方法就执行
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption{
NSString *inputStr = self.searchDisplayController.searchBar.text;
[self updateContentForProductName:inputStr andType:searchOption];
return YES;
}
// 根据输入的文本和选择的类别进行匹配
-(void)updateContentForProductName:(NSString *)searchString andType:(NSInteger )type{
NSMutableArray *array = [NSMutableArray array];
for (Product *p in self.products) {
//查看字符串B在A中的位置及占用的长度
//ABCDE -> BCD location = 1 lenght = 3
NSRange range = [p.name rangeOfString:searchString];
if (range.length > 0 && p.type==type) {
[array addObject:p];
}
}
self.searchResult = array;
}
@end
  
2.iOS8 Search Bar (Xcode6)
参考【Demo3_SearchBar_iOS8】
思想:创建类:用于展示搜索结果的控制器的显示模型,在主控制器创建执行搜索动作的控制器并与 自己创建的类相联系,并设置搜索下边的三项分类,并且将searchResultsUpdater和searchBar分别设置为代理,遵守协议<UISearchResultsUpdating,UISearchBarDelegate>
Product.h
Product.m
同上
MainTableViewController.h
#import <UIKit/UIKit.h>
@interface MainTableViewController : UITableViewController
@property(nonatomic,strong)NSArray *products;
@end
MainTableViewController.m
#import "MainTableViewController.h"
#import "SearchTableViewController.h"
#import "Product.h"
@interface MainTableViewController ()<UISearchResultsUpdating,UISearchBarDelegate>
//增加属性:用于控制搜索结果显示的控制器
@property(nonatomic,strong)SearchTableViewController *searchResultViewController;
@property(nonatomic,strong)UISearchController *searchController;
@end
@implementation MainTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.products = [Product demoData];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
//创建用于展示搜索结果的控制器实例
self.searchResultViewController = [[SearchTableViewController alloc]init];
//创建执行搜索动作的控制器,并制定哪个控制器帮助它显示结果
self.searchController = [[UISearchController alloc]initWithSearchResultsController:self.searchResultViewController];
//设置搜索控制器的结果更新代理对象
self.searchController.searchResultsUpdater = self;
//设置显示的bar的大小和样式
[self.searchController.searchBar sizeToFit];
self.searchController.searchBar.scopeButtonTitles = @[@"设备",@"软件",@"其它"]; 显示几个按钮是根据给几个名字
//将搜索bar添加到表头视图
self.tableView.tableHeaderView = self.searchController.searchBar;
self.definesPresentationContext = YES;
self.searchController.searchBar.delegate = self;
}
- (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope{
[self updateSearchResultsForSearchController:self.searchController];
}
#pragma mark - UISearchResultUpdating
-(void)updateSearchResultsForSearchController:(UISearchController *)searchController{
//用户输入的要搜索的文本信息
NSString *searchText = searchController.searchBar.text;
//获取选择的scope按钮是哪个
NSInteger selectedScopeButton = searchController.searchBar.selectedScopeButtonIndex;
NSLog(@"%ld",selectedScopeButton);
NSMutableArray *searchResult = [NSMutableArray array];
for (Product *p in self.products) {
NSRange range = [p.name rangeOfString:searchText];
if (range.length > 0 && p.type==selectedScopeButton) {
[searchResult addObject:p];
}
}
self.searchResultViewController.resultArray = searchResult;
[self.searchResultViewController.tableView reloadData];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.products.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
Product *p = self.products[indexPath.row];
cell.textLabel.text = p.name;
return cell;
}
@end
SearchTableViewController.h
#import <UIKit/UIKit.h>
@interface SearchTableViewController : UITableViewController
@property(nonatomic,strong)NSArray *resultArray;
@end
SearchTableViewController.m
#import "SearchTableViewController.h"
#import "Product.h"
@interface SearchTableViewController ()
@end
@implementation SearchTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 注册cell
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell2"];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.resultArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell2" forIndexPath:indexPath];
Product *p = self.resultArray[indexPath.row];
cell.textLabel.text = p.name;
return cell;
}
@end
1.Size Classes
随着苹果设备的增多,屏幕尺寸越来越多样化,为了解决适配不同设备屏幕的问题,从iOS8开始,推出了一项配合Auto layout一起用的Size Classes技术
核心理念:抛弃屏幕尺寸的概念,将不同种类的设备划分到不同的组合内,制作界面时,关注这一个组别,就等同于对这一个组别下的所有设备进行设计界面。运行时,系统会根据当前设备,判断属于哪个组别,然后找到对应组别下的AutoLayout原则,依据此原则计算坐标
有哪些组别?
划分组别的标准: 紧凑型 any 标准型
根据右下角确定,当选到any,即可以包括,
4.应用程序间的通信
4.1 什么是应用程序间的通讯?
一个应用给另一个应用发点信息过去,但很少,如果说,打开图片库、点击分享、打印、共享
4.2 使用场景
将一个字符串或图片发到微博、微信等应用上
4.3 如何做?
使用一个叫做UIActivityViewController控制器完成任务
4.4 Activity
把共享时要操作的项目叫做Activity,比如说,拷贝、打印——是 Activity中的操作,微信、微博这种Activity叫做分享
【Demo4_UIActivityViewController】
ViewController.h
ViewController.m
#import "ViewController.h"
#import "StringReverseActivity.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *textField;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (IBAction)shared:(UIButton *)sender {
NSString *text = self.textField.text;
//第一步 创建Activity控制器
//activityItems:要传递的信息
//applicationActivities:写nil,系统会
//列出支持的所有activity
UIActivityViewController *avc = [[UIActivityViewController alloc]initWithActivityItems:@[text] applicationActivities:nil];
//第二步:设置排除的activity
avc.excludedActivityTypes = @[UIActivityTypeMail];
//第三步:显示出VC
[self presentViewController:avc animated:YES completion:nil];
}
- (IBAction)customActivity:(UIButton *)sender {
NSArray *itemToShare = @[@"Hello",@"World",@12345];
//第一步创建自定义的Activity的对象
StringReverseActivity *srActivity = [[StringReverseActivity alloc]init];
//第二步 添加activity到Activity控制器上
UIActivityViewController *avc = [[UIActivityViewController alloc]initWithActivityItems:itemToShare applicationActivities:@[srActivity]];
//第三步 推出avc
[self presentViewController:avc animated:YES completion:nil];
}
@end
4.5自定义的Activity
step1:自己写一个类,继承UIActivity
step2:实现类内的方法
类内的6个方法必须实现
【Demo4_UIActivityViewController】
接上面的编写
StringReverseActivity.h
StringReverseActivity.m
#import "StringReverseActivity.h"
@interface StringReverseActivity ()<UIAlertViewDelegate>
@property(nonatomic,strong)NSMutableArray *activityItems;
@end
@implementation StringReverseActivity
//第一个:返回自己的Activity的类型,只要唯一即可
-(NSString *)activityType{
//获得应用程序所在的沙箱的完整路径
//NSStringFromClass是根据类,获取类的名称
return [[NSBundle mainBundle].bundleIdentifier stringByAppendingFormat:@".%@",NSStringFromClass([self class])];
}
//第二个:不要求唯一,但希望短一点
//返回activity的名称
-(NSString *)activityTitle{
return @"反转";
}
//第三个:显示的图片 Retina: 86 X 86 ipad: 110 X 110
-(UIImage *)activityImage{
return [UIImage imageNamed:@"icon80"]; 图片尺寸不行,所以不显示
}
//第四个:将共享的item传过的数据进行检验,看是否可以反转
-(BOOL)canPerformWithActivityItems:(NSArray *)activityItems{
for (id object in activityItems) {
if ([object isKindOfClass:[NSString class]]) {
//只要有一个string就调用反转方法
return YES;
}
}
return NO;
}
//第5个:查找activity的条目,只要有一个item能用就会到达这个方法
- (void)prepareWithActivityItems:(NSArray *)activityItems{
//把所有能反转的item挑出来,放到一个数组中
NSMutableArray *stringObjects = [NSMutableArray array];
for (id object in activityItems) {
if ([object isKindOfClass:[NSString class]]) {
[stringObjects addObject:object];
}
}
self.activityItems = stringObjects;
}
//第6个:执行activity,编写逻辑---反转
-(void)performActivity{
// 为了保存每一个反转后的string
NSMutableString *reverseString = [[NSMutableString alloc]init];
//反转
for (NSString
*string in self.activityItems) {
[reverseString appendString:[self myReverseString:string]];
[reverseString appendString:@"\n"];
}
//显示反转的结果
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"字符串反转" message:reverseString delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil];
[alert show];
}
//第7个:点击alert中的确定按钮后,通知activity动作结束
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
[self activityDidFinish:YES];
}
//自定义方法:执行字符串的反转
-(NSString *)myReverseString:(NSString *)paramString
{
NSMutableString *reversed = [[NSMutableString alloc]init];
for (NSInteger index = paramString.length-1; index>=0; index--) {
[reversed appendFormat:@"%c",[paramString characterAtIndex:index]];
}
return [reversed copy];
}
@end
系统默认,去掉了邮件 自定义,多增加了反转 点击反转,只要是字符串就倒过来显示
   
5.横竖屏的判断
【Demo5_All_Orientation】
1.设置屏幕支持的方向
2.获取即将要旋转到的某个朝向
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *greenVIew;
@property (weak, nonatomic) IBOutlet UIButton *button1;
@property (weak, nonatomic) IBOutlet UIButton *button2;
@property (weak, nonatomic) IBOutlet UIButton *button3;
@property (weak, nonatomic) IBOutlet UIButton *button4;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
//1.设置屏幕支持的方向
- (NSUInteger)supportedInterfaceOrientations{
//设置支持的设备方向
return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscape;
}
//2.获取即将要旋转到的某个朝向
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
//判断即将到达的朝向,决定选中何种布局
if (UIInterfaceOrientationIsPortrait(toInterfaceOrientation)) {
//竖屏
[self layoutPortrait];
}else{
//横屏
[self layoutLandscape];
}
}
//自定义的方法,用于竖屏时布局
-(void)layoutPortrait{
self.greenVIew.frame = CGRectMake(20, 20, self.view.bounds.size.width-20-20, self.view.bounds.size.height-20*4-35*2);
CGRect frame = CGRectMake(20, self.view.bounds.size.height-20*2-35*2, 130, 35);
self.button1.frame = frame;
frame.origin.x +=130+20;
self.button2.frame = frame;
frame.origin.y += (35+20);
self.button4.frame = frame;
frame.origin.x -= (130+20);
self.button3.frame = frame;
}
//自定义的方法,用于横屏时布局
-(void)layoutLandscape{
}
//界面显示前判断好方向,界面显示的时候布置
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
//判断出来的那一刻的方向
UIApplication *app = [UIApplication sharedApplication];
UIInterfaceOrientation orientation = app.statusBarOrientation;
if (UIInterfaceOrientationIsPortrait(orientation)) {
[self layoutPortrait];
}else{
[self layoutLandscape];
}
}
@end