赞
踩
11. iOS 类(class)和结构体(struct)有什么区别?
Swift 中,类是引用类型,结构体是值类型。值类型在传递和赋值时将进行复制,而引用类型则只会使用引用对象的一个"指向"。所以他们两者之间的区别就是两个类型的区别。
举个简单的例子,代码如下
class Temperature { var value: Float = 37.0}class Person { var temp: Temperature? func sick() { temp?.value = 41.0 }}let A = Person()let B = Person()let temp = Temperature()A.temp = tempB.temp = temp
A.sick()
上面这段代码,由于 Temperature 是 class ,为引用类型,故 A 的 temp 和 B 的 temp指向同一个对象。A 的 temp修改了,B 的 temp 也随之修改。这样 A 和 B 的 temp 的值都被改成了41.0。如果将 Temperature 改为 struct,为值类型,则 A 的 temp 修改不影响 B 的 temp。
内存中,引用类型诸如类是在堆(heap)上,而值类型诸如结构体实在栈(stack)上进行存储和操作。相比于栈上的操作,堆上的操作更加复杂耗时,所以苹果官方推荐使用结构体,这样可以提高 App 运行的效率。
class有这几个功能struct没有的:
class可以继承,这样子类可以使用父类的特性和方法
类型转换可以在runtime的时候检查和解释一个实例的类型
可以用deinit来释放资源
一个类可以被多次引用
struct也有这样几个优势:
结构较小,适用于复制操作,相比于一个class的实例被多次引用更加安全。
无须担心内存memory leak或者多线程冲突问题
12. KVC /KVO的底层原理和使用场景
KVC 常用的方法
(1)赋值类方法- (void)setValue:(nullable id)value forKey:(NSString *)key;- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;(2)取值类方法// 能取得私有成员变量的值- (id)valueForKey:(NSString *)key;- (id)valueForKeyPath:(NSString *)keyPath;- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;
KVC 底层实现原理
一个对象调用setValue:forKey: 方法时,方法内部会做以下操作: 1.判断有没有指定key的set方法,如果有set方法,就会调用set方法,给该属性赋值 2.如果没有set方法,判断有没有跟key值相同且带有下划线的成员属性(_key).如果有,直接给该成员属性进行赋值 3.如果没有成员属性_key,判断有没有跟key相同名称的属性.如果有,直接给该属性进行赋值 4.如果都没有,就会调用 valueforUndefinedKey 和setValue:forUndefinedKey:方法
KVC 的使用场景
一、赋值
(1) KVC 简单属性赋值
Person *p = [[Person alloc] init];// p.name = @"jack";// p.money = 22.2;使用setValue: forKey:方法能够给属性赋值,等价于直接给属性赋值[p setValue:@"rose" forKey:@"name"];[p setValue:@"22.2" forKey:@"money"];
(2) KVC复杂属性赋值
//给Person添加 Dog属性 Person *p = [[Person alloc] init]; p.dog = [[Dog alloc] init]; // p.dog.name = @"阿黄";1)setValue: forKeyPath: 方法的使用 //修改p.dog 的name 属性 [p.dog setValue:@"wangcai" forKeyPath:@"name"]; [p setValue:@"阿花" forKeyPath:@"dog.name"];2)setValue: forKey: 错误用法 [p setValue:@"阿花" forKey:@"dog.name"]; NSLog(@"%@", p.dog.name);3)直接修改私有成员变量[p setValue:@"旺财" forKeyPath:@"_name"];
(3) 添加私有成员变量
Person 类中添加私有成员变量_age[p setValue:@"22" forKeyPath:@"_age"];
二、字典转模型
(1)简单的字典转模型 +(instancetype)videoWithDict:(NSDictionary *)dict{ JLVideo *videItem = [[JLVideo alloc] init]; //以前// videItem.name = dict[@"name"];// videItem.money = [dict[@"money"] doubleValue] ; //KVC,使用setValuesForKeysWithDictionary:方法,该方法默认根据字典中每个键值对,调用setValue:forKey方法 // 缺点:字典中的键值对必须与模型中的键值对完全对应,否则程序会崩溃 [videItem setValuesForKeysWithDictionary:dict]; return videItem;}(2)复杂的字典转模型注意:复杂字典转模型不能直接通过KVC 赋值,KVC只能在简单字典中使用,比如: NSDictionary *dict = @{ @"name" : @"jack", @"money": @"22.2", @"dog" : @{ @"name" : @"wangcai", @"money": @"11.1", } }; JLPerson *p = [[JLPerson alloc]init]; // p是一个模型对象 [p setValuesForKeysWithDictionary:dict];内部转换原理:// [p setValue:@"jack" forKey:@"name"];// [p setValue:@"22.2" forKey:@"money"];// [p setValue:@{// @"name" : @"wangcai",// @"money": @"11.1",//// } forKey:@"dog"]; //给 dog赋值一个字典肯定是不对的(3)KVC解析复杂字典的正确步骤 NSDictionary *dict = @{ @"name" : @"jack", @"money": @"22.2", @"dog" : @{ @"name" : @"wangcai", @"price": @"11.1", }, //人有好多书 @"books" : @[ @{ @"name" : @"5分钟突破iOS开发", @"price" : @"19.8" }, @{ @"name" : @"3分钟突破iOS开发", @"price" : @"24.8" }, @{ @"name" : @"1分钟突破iOS开发", @"price" : @"29.8" } ] }; XMGPerson *p = [[XMGPerson alloc] init]; p.dog = [[XMGDog alloc] init]; [p.dog setValuesForKeysWithDictionary:dict[@"dog"]]; //保存模型的可变数组 NSMutableArray *arrayM = [NSMutableArray array]; for (NSDictionary *dict in dict[@"books"]) { //创建模型 Book *book = [[Book alloc] init]; //KVC [book setValuesForKeysWithDictionary:dict]; //将模型保存 [arrayM addObject:book]; } p.books = arrayM;备注: (1)当字典中的键值对很复杂,不适合用KVC; (2)服务器返还的数据,你可能不会全用上,如果在模型一个一个写属性非常麻烦,所以不建议使用KVC字典转模型
三、取值
(1) 模型转字典
Person *p = [[Person alloc]init]; p.name = @"jack"; p.money = 11.1; //KVC取值 NSLog(@"%@ %@", [p valueForKey:@"name"], [p valueForKey:@"money"]); //模型转字典, 根据数组中的键获取到值,然后放到字典中 NSDictionary *dict = [p dictionaryWithValuesForKeys:@[@"name", @"money"]]; NSLog(@"%@", dict);
(2) 访问数组中元素的属性值
Book *book1 = [[Book alloc] init];book1.name = @"5分钟突破iOS开发";book1.price = 10.7;Book *book2 = [[Book alloc] init];book2.name = @"4分钟突破iOS开发";book2.price = 109.7;Book *book3 = [[Book alloc] init];book3.name = @"1分钟突破iOS开发";book3.price = 1580.7;// 如果valueForKeyPath:方法的调用者是数组,那么就是去访问数组元素的属性值// 取得books数组中所有Book对象的name属性值,放在一个新的数组中返回 NSArray *books = @[book1, book2, book3]; NSArray *names = [books valueForKeyPath:@"name"]; NSLog(@"%@", names);//访问属性数组中元素的属性值Person *p = [[Person alloc]init];p.books = @[book1, book2, book3];NSArray *names = [p valueForKeyPath:@"books.name"];NSLog(@"%@", names);
KVO (Key Value Observing)
KVO 的底层实现原理
(1)KVO 是基于 runtime 机制实现的(2)当一个对象(假设是person对象,对应的类为 JLperson)的属性值age发生改变时,系统会自动生成一个继承自JLperson的类NSKVONotifying_JLPerson,在这个类的 setAge 方法里面调用 [super setAge:age]; [self willChangeValueForKey:@"age"]; [self didChangeValueForKey:@"age"]; 三个方法,而后面两个方法内部会主动调用 -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context方法,在该方法中可以拿到属性改变前后的值.
KVO的作用
作用:能够监听某个对象属性值的改变
// 利用KVO监听p对象name 属性值的改变 Person *p = [[XMGPerson alloc] init]; p.name = @"jack"; /* 对象p添加一个观察者(监听器) Observer:观察者(监听器) KeyPath:属性名(需要监听哪个属性) */ [p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"123"]; /** * 利用KVO 监听到对象属性值改变后,就会调用这个方法 * * @param keyPath 哪一个属性被改了 * @param object 哪一个对象的属性被改了 * @param change 改成什么样了 */- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{ // NSKeyValueChangeNewKey == @"new" NSString *new = change[NSKeyValueChangeNewKey]; // NSKeyValueChangeOldKey == @"old" NSString *old = change[NSKeyValueChangeOldKey]; NSLog(@"%@-%@",new,old);}
13. Objective-C中通知与协议的区别?
what is difference between NSNotification and protocol? (通知和协议的不同之处?)
我想大家都知道这个东西怎么用,但是更深层次的思考可能就比较少了吧,众所周知就是代理是一对一的,但是通知是可以多对多的.但是为什么是这个样子,有没有更深的思考过这个问题?
今天看了下网上的几个视频教程,KVO、KVC、谓词、通知,算是开发中的高级点的东西了。通知和协议都是类似于回调一样,于是就在思考通知和协议到底有什么不同,或者说什么时候该用通知,什么时候该用协议。
下面是网上摘抄的一段解释:
协议有控制链(has-a)的关系,通知没有。首先我一开始也不太明白,什么叫控制链(专业术语了~)。但是简单分析下通知和代理的行为模式,我们大致可以有自己的理解简单来说,通知的话,它可以一对多,一条消息可以发送给多个消息接受者。代理按我们的理解,到不是直接说不能一对多,比如我们知道的明星经济代理人,很多时候一个经济人负责好几个明星的事务。只是对于不同明星间,代理的事物对象都是不一样的,一一对应,不可能说明天要处理A明星要一个发布会,代理人发出处理发布会的消息后,别称B的发布会了。但是通知就不一样,他只关心发出通知,而不关心多少接收到感兴趣要处理。因此控制链(has-a从英语单词大致可以看出,单一拥有和可控制的对应关系。
1.通知:
通知需要有一个通知中心:NSNotificationCenter,自定义通知的话需要给一个名字,然后监听。
优点:通知的发送者和接受者都不需要知道对方。可以指定接收通知的具体方法。通知名可以是任何字符串。
缺点:较键值观察(KVO)需要多点代码,在删掉前必须移除监听者。
2.协议
通过setDelegate来设置代理对象,最典型的例子是常用的TableView.
优点:支持它的类有详尽和具体信息。
缺点:该类必须支持委托。某一时间只能有一个委托连接到某一对象。
相信看到这些东西,认真思考一下,就可以知道在那种情况下使用通知,在那种情况下使用代理了吧.
14. iOS 应用有哪些方式保存本地数据?他们都应用在哪些场景?
iOS本地缓存数据方式有五种:
1.直接写文件方式:可以存储的对象有NSString、NSArray、NSDictionary、NSData、NSNumber,数据全部存放在一个属性列表文件(*.plist文件)中。
2. NSUserDefaults(偏好设置),用来存储应用设置信息,文件放在perference目录下。
3.归档操作(NSkeyedArchiver),不同于前面两种,它可以把自定义对象存放在文件中。
4.coreData:coreData是苹果官方iOS5之后推出的综合型数据库,其使用了ORM(Object Relational Mapping)对象关系映射技术,将对象转换成数据,存储在本地数据库中。coreData为了提高效率,甚至将数据存储在不同的数据库中,且在使用的时候将本地数据放到内存中使得访问速度更快。我们可以选择coreData的数据存储方式,包括sqlite、xml等格式。但也正是coreData 是完全面向对象的,其在执行效率上比不上原生的数据库。除此之外,coreData拥有数据验证、undo等其他功能,在功能上是几种持久化方案最多的。
5.FMDB:FMDB是iOS平台的SQLite数据库框架,FMDB以OC的方式封装了SQLite的C语言API,使用起来更加面向对象,省去了很多麻烦、冗余的C语言代码,对比苹果自带的Core Data框架,更加轻量级和灵活,提供了多线程安全的数据库操作方法,有效地防止数据混乱。
15. iOS内存的使用和优化的注意事项
如UITableViewCells、UICollectionViewCells、UITableViewHeaderFooterViews
设置正确的reuseIdentifier,充分重用;
当opque为NO的时候,图层的半透明取决于图片和其本身合成的图层为结果,可提高性能;
载入时就会将XIB/storyboard需要的所有资源,
包括图片全部载入内存,即使未来很久才会使用。
那些相比纯代码写的延迟加载,性能及内存就差了很多;
学会选择对业务场景最合适的数组结构是写出高效代码的基础。
比如,数组: 有序的一组值。
使用索引来查询很快,使用值查询很慢,插入/删除很慢。
字典: 存储键值对,用键来查找比较快。
集合: 无序的一组值,用值来查找很快,插入/删除很快。
当从服务端下载相关附件时,可以通过gzip/zip压缩后再下载,使得内存更小,下载速度也更快。
对于不应该使用的数据,使用延迟加载方式。
对于不需要马上显示的视图,使用延迟加载方式。
比如,网络请求失败时显示的提示界面,可能一直都不会使用到,因此应该使用延迟加载。
对于cell的行高要缓存起来,使得reload数据时,效率也极高。
而对于那些网络数据,不需要每次都请求的,应该缓存起来,
可以写入数据库,也可以通过plist文件存储。
一般在基类统一处理内存警告,将相关不用资源立即释放掉
一些objects的初始化很慢,
比如NSDateFormatter和NSCalendar,但又不可避免地需要使用它们。
通常是作为属性存储起来,防止反复创建。
许多应用需要从服务器加载功能所需的常为JSON或者XML格式的数据。
在服务器端和客户端使用相同的数据结构很重要;
在某些循环创建临时变量处理数据时,自动释放池以保证能及时释放内存;
16. UIViewController的完整生命周期
UIViewController的完整生命周期
-[ViewControllerinitWithNibName:bundle:];
-[ViewControllerinit];
-[ViewControllerloadView];
-[ViewControllerviewDidLoad];
-[ViewControllerviewWillDisappear:];
-[ViewControllerviewWillAppear:];
-[ViewControllerviewDidAppear:];
-[ViewControllerviewDidDisappear:];
1、 alloc 创建对象,分配空间
2、init(initWithNibName) 初始化对象,初始化数据
3、loadView 从nib载入视图 ,通常这一步不需要去干涉。除非你没有使用xib文件创建视图
4、viewDidLoad 载入完成,可以进行自定义数据以及动态创建其他控件
5、viewWillAppear 视图将出现在屏幕之前,马上这个视图就会被展现在屏幕上了
6、viewDidAppear 视图已在屏幕上渲染完成
当一个视图被移除屏幕并且销毁的时候的执行顺序,这个顺序差不多和上面的相反
1、viewWillDisappear 视图将被从屏幕上移除之前执行
2、viewDidDisappear 视图已经被从屏幕上移除,用户看不到这个视图了
3、dealloc 视图被销毁,此处需要对你在init和viewDidLoad中创建的对象进行释放
ViewController 的 loadView,、viewDidLoad,、viewDidUnload 分别是在什么时候调用的?
viewDidLoad在view从nib文件初始化时调用,loadView在controller的view为nil时调用。
此方法在编程实现view时调用,view控制器默认会注册memory warning notification,当view controller的任何view没有用的时候,viewDidUnload会被调用,在这里实现将retain的view release,如果是retain的IBOutlet view属性则不要在这里release,IBOutlet会负责release。
17. 谈谈队列和多线程的使用原理
在iOS中队列分为以下几种:
串行队列:队列中的任务只会顺序执行;
dispatch_queue_tq = dispatch_queue_create("...", DISPATCH_QUEUE_SERIAL);
并行队列:队列中的任务通常会并发执行;
dispatch_queue_tq = dispatch_queue_create("......",DISPATCH_QUEUE_CONCURRENT);
全局队列:是系统的,直接拿过来(GET)用就可以;与并行队列类似;
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
主队列:每一个应用程序对应唯一主队列,直接GET即可;在多线程开发中,使用主队列更新UI;
dispatch_queue_tq = dispatch_get_main_queue();
18. iOS SQLite 数据库使用及常用SQL语句
一、SQLite 简介
SQL 是指结构化查询语言。使我们有能力访问数据库,SQL的功能有:面向数据库执行查询,取出数据库中数据,插入数据,查询数据,更新数据,删除数据,创建新数据库,创建新表等。
二、SQLite 使用
如果你要使用SQLite数据库,必须导入系统的libsqlite3.0.tbd文件
1、创建数据库
在使用数据库之前你必须在本地有一个数据库,可以通过NSSearchPathForDirectoriesInDomains()函数来创建一个文件,为了方便查看数据库中的数据一般以.sqlite为文件后缀。 NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject]stringByAppendingPathComponent:@"Student.sqlite"];
2、打开数据库
首先在自定义类中创建一个sqlite3类型的变量。然后调用sqlite3_open()函数就可以打开数据库了。 - (void)openDB { // 如果数据库已经打开,没有必要再打开一次. if (db) { NSLog(@"数据库已经打开"); return; } // 创建数据库文件的路径 NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject]stringByAppendingPathComponent:@"Student.sqlite"]; NSLog(@"%@",filePath); // 参数1是C字符串,参数2是**,所传入的参数是地址 int result = sqlite3_open(filePath.UTF8String, &db); // 根据函数返回值,判断执行是否正确 if (result == SQLITE_OK) { NSLog(@"数据库打开成功!"); } else { NSLog(@"数据库打开失败,错误码:%d", result); }}
3、创建表格
在对数据进行增删改查之前,你要有一个表格来存放数据,就如同书架对书进行分类、整理一样,数据在数据库中也不能随意放置,表格可以使数据更加的有序。
注意:对数据进行增删改查操作,一般会用到两个函数:sqlite3_exec()和sqlite3_prepare_v2()。
(1) sqlite3_exec()函数一般用于你对数据库进行操作,而数据库只向你回馈你的操作是否成功,不返回数据库中的数据信息时。表格的创建就是如此,因为我们只关心表格是否创建成功,而不需要知道表格中到底有什么。
(2) sqlite3_prepare_v2()函数用于你需要数据库对你反馈数据时,如查询数据,因为我们必须得到数据查询的结果,而不只是它是否成功的查询。
创建表格的sql语句:CREATE TABLE IF NOT EXISTS xxxTABLE (ID_S INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, sex TEXT, age INTEGER).
a. CREATE TABLE 是创建表格的关键词,
b. IF NOT EXISTS 是告诉系统当这个表格不存在是创建,这个词可以不写。
c. xxxTABLE 是你给创建的表起的名字。
d. 括号内是你表格内的每一个元素,用逗号链接。
4、插入数据
有了表格后,就可以在表格中添加数据了,添加数据和创建表格除了sql语句不同外,其它都一样,我们只关心数据是否添加进去。- (void)insertModel:(ModelOfStudent *)stu{ // 插入数据的SQL语句 NSString *sql = [NSString stringWithFormat:@"INSERT INTO dls160101 (name, sex, age) VALUES ('%@', '%@', '%ld')", stu.name, stu.sex, stu.age]; int result = sqlite3_exec(db, sql.UTF8String, nil, nil, nil); if (result == SQLITE_OK) { NSLog(@"插入数据成功"); } else { NSLog(@"插入数据失败,错误码:%d", result); }}
5、更新数据
更新数据是在原有数据基础上对数据改动,其结果是在原来的位置上修改了原来的数据,因此代码与之前没什么不同- (void)updateModel:(ModelOfStudent *)stu id:(NSInteger)ID{ // 更新数据 SQL语句 NSString *sql = [NSString stringWithFormat:@"UPDATE dls160101 SET name = '%@', sex = '%@', age = '%ld' WHERE ID_S = '%ld'", stu.name, stu.sex, stu.age, ID]; int reslut = sqlite3_exec(db, sql.UTF8String, nil, nil, nil); if (reslut == SQLITE_OK) { NSLog(@"更新数据成功"); } else { NSLog(@"更新数据失败,错误码:%d", reslut); }}
6、删除数据
删除数据我们也只关心数据是否删除成功,与之前也没什么不同- (void)deleteModelWithId:(NSInteger)ID{ // 删除数据 NSString *sql = [NSString stringWithFormat:@"DELETE FROM dls160101 WHERE ID_S = '%ld'", ID]; int result = sqlite3_exec(db, sql.UTF8String, nil, nil, nil); if (result == SQLITE_OK) { NSLog(@"删除数据成功"); } else { NSLog(@"删除数据失败,错误码:%d", result); }}
7、检索数据
/** 检索数据 API: sqlite3_stmt */- (NSArray *)selectWithSex:(NSString *)sex { NSMutableArray *arr = [NSMutableArray array]; // 查找SQL语句 NSString *sql = [NSString stringWithFormat:@"SELECT * FROM dls160101 WHERE sex = '%@'", sex]; // 创建一个准备好的语句对象 sqlite3_stmt *stmt = nil; // 为准备好的语句对象赋值(SQL语句内容)。 int result = sqlite3_prepare_v2(db, sql.UTF8String, -1, &stmt, nil); if (result == SQLITE_OK) { NSLog(@"查询中..."); // 当有下一行数据时,继续执行检索。 while (sqlite3_step(stmt) == SQLITE_ROW) { // 查询条件匹配 使用函数簇将需要的列值取出来 const unsigned char *name = sqlite3_column_text(stmt, 1); const unsigned char *sex = sqlite3_column_text(stmt, 2); int age = sqlite3_column_int(stmt, 3); // 创建model赋值 ModelOfStudent *stu = [[ModelOfStudent alloc]init]; stu.name = [NSString stringWithUTF8String:(const char *)name]; stu.sex = [NSString stringWithUTF8String:(const char *)sex]; stu.age = age; // 添加到数组 [arr addObject:stu]; } NSLog(@"查询结果,有%ld条记录匹配", arr.count); sqlite3_step(stmt); } else { NSLog(@"未能启动查询过程,错误码:%d", result); } // 销毁stmt对象(内存管理) sqlite3_finalize(stmt); return arr;}
19. iOS frame和Bounds 以及frame和bounds区别
最近和大家交流的时候发现很多初学者,当然也有一些有经验的iOS开发者对view的frame和bounds了解的都不是很透彻;尤其是bounds很多朋友都糊了,bounds确实比较难理解,今天就给大家说说frame和bounds。
frame是每个view必备的属性,代表的是当前视图
的位置和大小,没有设置他,当前视图
是看不到的。
位置需要有参照物才能确定,数学中我们用坐标系来确定坐标系中的某个点的位置,iOS中有他特有的坐标系,如下图:
在iOS坐标系中以左上角为坐标原点,往右
为X正方向,往下
是Y正方向
frame中的位置是以父视图的坐标系
为标准来确定当前视图的位置
同样的默认情况下,本视图的左上角
就是子视图
的坐标原点
更改frame中位置,则当前视图的位置会发生改变
更改frame的大小,则当前视图以当前视图
的左上角
为基准的进行大小的修改
bounds是每个View都有的属性,这个属性我们一般不进行设置,他同样代表位置和大小;
每个视图都有自己的坐标系,且这个坐标系默认以自身的左上角
为坐标原点,所有子视图以这个坐标系的原点
为基准点。
bounds的位置代表的是子视图
看待当前视图左上角
的位置;bounds的大小代表当前视图的大小;
更改bounds中的位置对于当前视图没有影响,相当于更改了当前视图的坐标系,对于子视图来说当前视图的左上角
已经不再是(0,0), 而是改变后的坐标,坐标系改了,那么所有子视图
的位置
也会跟着改变
更改bounds的大小,bounds的大小代表当前视图的长和宽,修改长宽后,中心点
继续保持不变, 长宽进行改变;通过bounds修改长宽看起来就像是以中心点为基准点对长宽两边同时进行缩放;
有如下图:
View A是最顶层视图, 因此他的信息如下:frame
origin:0, 0
size :550 * 400
bounds
origin:0, 0
size 550 * 400
因为是View A是顶层视图
,所以其实相当于覆盖
在框架上,因此位置从父视图的(0, 0)开始,大小为550*400
默认情况下,本视图的坐标系
是没有发生改变的即当前视图(view A)的左上角
就是所有子视图
的原点,大小就是当前视图的大小.
View B是View A的子视图,因此他的信息如下:
frame
origin:200, 100
size :200 * 250
bounds
origin:0, 0
size :200 * 250
因为View B是View A的子视图,所以View B的frame位置需要以View A的左上角
为参照,因此位置为(200, 100), 大小为200*250
bounds在默认情况下本视图的坐标系是没有发生改变的即当前视图(view B)的左上角
就是当前视图所有子视图的原点.
上面这种是普通的情况,也就是没有更改bounds的时候,下面我们来看更改bounds的例子, 如下图:
在上一个例子的基础上,我们更改了view A的bounds后,view B看待View A的左上角就已经发生改变了;这个时候我们看待View A的左上角就不是坐标原点了,而是我们通过bounds设置后的坐标,如图也就是(0, 100);
在View B的frame没有保存不变的基础上,我们View B的位置向上移动了100
在第一例的基础上,更改了bounds中X后,效果图如下:
1、frame不管对于位置还是大小,改变的都是自己
本身
2、frame的位置是以父视图
的坐标系为参照,从而确定当前视图在父视图中的位置
3、frame的大小改变时,当前视图的左上角
位置不会发生改变,只是大小
发生改变
2、bounds改变位置时,改变的是子视图
的位置,自身
没有影响;其实就是改变了本身的坐标系原点,默认本身坐标系的原点是左上角
3、bounds的大小改变时,当前视图的中心点
不会发生改变,当前视图的大小
发生改变,看起来效果就想缩放
一样
20. UIView的Touch事件
处理事件的方法
UIView是UIResponder的子类,可以覆盖下列4个方法处理不同的触摸事件
//一根或者多根手指开始触摸view - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event //一根或者多根手指在view上移动 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event //一根或者多根手指离开view - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event //触摸结束前,某个系统事件(例如电话呼入)会打断触摸过程 - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
UITouch对象
当用户用一根手指触摸屏幕时,会创建一个与手指相关联的UITouch对象 一根手指对应一个UITouch对象
UITouch的作用:
保存着跟手指相关的信息,比如触摸的位置、时间、阶段
当手指移动时,系统会更新同一个UITouch对象,使之能够一直保存该手指在的触摸位置
当手指离开屏幕时,系统会销毁相应的UITouch对象
UITouch的常见属性
//触摸产生时所处的窗口@property(nonatomic,readonly,retain) UIWindow *window;//触摸产生时所处的视图@property(nonatomic,readonly,retain) UIView *view;//短时间内点按屏幕的次数,可以根据tapCount判断单击、双击或更多的点击@property(nonatomic,readonly) NSUInteger tapCount;//记录了触摸事件产生或变化时的时间,单位是秒@property(nonatomic,readonly) NSTimeInterval timestamp;//当前触摸事件所处的状态@property(nonatomic,readonly) UITouchPhase phase;
UITouch的常见方法
//返回值表示触摸在view上的位置 //这里返回的位置是针对view的坐标系的(以view的左上角为原点(0, 0)) //调用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置 - (CGPoint)locationInView:(UIView *)view; // 该方法记录了前一个触摸点的位置 - (CGPoint)previousLocationInView:(UIView *)view;
UIEvent对象
每产生一个事件,就会产生一个UIEvent对象
UIEvent : 称为事件对象,记录事件产生的时刻和类型
常见属性 :
//事件类型 //@property(nonatomic,readonly) UIEventType type; //@property(nonatomic,readonly) UIEventSubtype subtype; //事件产生的时间 @property(nonatomic,readonly) NSTimeInterval timestamp; UIEvent还提供了相应的方法可以获得在某个view上面的触摸对象(UITouch)
事件的产生和传递
发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中
UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow)
主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件,这也是整个事件处理过程的第一步
找到合适的视图控件后,就会调用视图控件的touches方法来作具体的事件处理
touchesBegan…
touchesMoved…
touchedEnded…
这些touches方法的默认做法是将事件顺着响应者链条向上传递(不实现touches方法,系统会自动向上一个响应者传递),将事件交给上一个响应者进行处理
如果一个事件既想自己处理也想交给上一个响应者处理,那么自己实现touches方法,并且调用super的touches方法,[super touches、、、];
如何找到最合适的控件来处理事件
自己是否能接收触摸事件?
触摸点是否在自己身上?
从后往前遍历子控件,重复前面的两个步骤
如果没有符合条件的子控件,那么就自己最适合处理
如果父控件不能接收触摸事件,那么子控件就不可能接收到触摸事件(掌握)
UIView不接收触摸事件的三种情况:
不接收用户交互 : userInteractionEnabled = NO
隐藏 : hidden = YES
透明 : alpha = 0.0 ~ 0.01
UIImageView的userInteractionEnabled默认就是NO,因此UIImageView以及它的子控件默认是不能接收触摸事件的
响应者链条:是由多个响应者对象连接起来的链条
作用:能很清楚的看见每个响应者之间的联系,并且可以让一个事件多个对象处理。
响应者对象:能处理事件的对象
先将事件对象由上往下传递(由父控件传递给子控件),找到最合适的控件来处理这个事件。
调用最合适控件的touches….方法
如果调用了[super touches….];就会将事件顺着响应者链条往上传递,传递给上一个响应者
接着就会调用上一个响应者的touches….方法
如何判断上一个响应者:
如果当前这个view是控制器的view,那么控制器就是上一个响应者
如果当前这个view不是控制器的view,那么父控件就是上一个响应者
如果view的控制器存在,就传递给控制器;如果控制器不存在,则将其传递给它的父视图
在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理
如果window对象也不处理,则其将事件或消息传递给UIApplication对象
如果UIApplication也不能处理该事件或消息,则将其丢弃
当事件传递给控件的时候,就会调用控件的这个方法,去寻找最合适的view
point:当前的触摸点,point这个点的坐标系就是方法调用者
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
pointInside方法
作用:判断当前这个点在不在方法调用者(控件)上
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
hitTest:withEvent:的实现原理
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{ // 1.判断当前控件能否接收事件 if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil; // 2. 判断点在不在当前控件 if ([self pointInside:point withEvent:event] == NO) return nil; // 3.从后往前遍历自己的子控件 NSInteger count = self.subviews.count; for (NSInteger i = count - 1; i >= 0; i--) { UIView *childView = self.subviews[i]; // 把当前控件上的坐标系转换成子控件上的坐标系 CGPoint childP = [self convertPoint:point toView:childView]; UIView *fitView = [childView hitTest:childP withEvent:event]; if (fitView) { // 寻找到最合适的view return fitView; } } // 循环结束,表示没有比自己更合适的view return self;}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。