ios yymodel 将字典转数组模型_2020年iOS面试题大全(二)(附答案)

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,充分重用;

尽量把views设置为不透明:

当opque为NO的时候,图层的半透明取决于图片和其本身合成的图层为结果,可提高性能;

不要使用太复杂的XIB/Storyboard:

载入时就会将XIB/storyboard需要的所有资源,

包括图片全部载入内存,即使未来很久才会使用。

那些相比纯代码写的延迟加载,性能及内存就差了很多;

选择正确的数据结构:

学会选择对业务场景最合适的数组结构是写出高效代码的基础。

比如,数组: 有序的一组值。

使用索引来查询很快,使用值查询很慢,插入/删除很慢。

字典: 存储键值对,用键来查找比较快。

集合: 无序的一组值,用值来查找很快,插入/删除很快。

gzip/zip压缩:

当从服务端下载相关附件时,可以通过gzip/zip压缩后再下载,使得内存更小,下载速度也更快。

延迟加载:

对于不应该使用的数据,使用延迟加载方式。

对于不需要马上显示的视图,使用延迟加载方式。

比如,网络请求失败时显示的提示界面,可能一直都不会使用到,因此应该使用延迟加载。

数据缓存:

对于cell的行高要缓存起来,使得reload数据时,效率也极高。

而对于那些网络数据,不需要每次都请求的,应该缓存起来,

可以写入数据库,也可以通过plist文件存储。

处理内存警告:

一般在基类统一处理内存警告,将相关不用资源立即释放掉

重用大开销对象:

一些objects的初始化很慢,

比如NSDateFormatter和NSCalendar,但又不可避免地需要使用它们。

通常是作为属性存储起来,防止反复创建。

避免反复处理数据:

许多应用需要从服务器加载功能所需的常为JSON或者XML格式的数据。

在服务器端和客户端使用相同的数据结构很重要;

使用Autorelease Pool:

在某些循环创建临时变量处理数据时,自动释放池以保证能及时释放内存;

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

frame是每个view必备的属性,代表的是当前视图的位置和大小,没有设置他,当前视图是看不到的。
位置需要有参照物才能确定,数学中我们用坐标系来确定坐标系中的某个点的位置,iOS中有他特有的坐标系,如下图:

4e7ff9fd0a91ec0168f91fe8049c21a7.png

  • 在iOS坐标系中以左上角为坐标原点,往为X正方向,往是Y正方向

  • frame中的位置是以父视图的坐标系为标准来确定当前视图的位置

  • 同样的默认情况下,本视图的左上角就是子视图的坐标原点

  • 更改frame中位置,则当前视图的位置会发生改变

  • 更改frame的大小,则当前视图以当前视图左上角为基准的进行大小的修改

bounds

bounds是每个View都有的属性,这个属性我们一般不进行设置,他同样代表位置和大小;
每个视图都有自己的坐标系,且这个坐标系默认以自身的左上角为坐标原点,所有子视图以这个坐标系的原点为基准点。
bounds的位置代表的是子视图看待当前视图左上角的位置;bounds的大小代表当前视图的大小;

  • 更改bounds中的位置对于当前视图没有影响,相当于更改了当前视图的坐标系,对于子视图来说当前视图的左上角已经不再是(0,0), 而是改变后的坐标,坐标系改了,那么所有子视图位置也会跟着改变

  • 更改bounds的大小,bounds的大小代表当前视图的长和宽,修改长宽后,中心点继续保持不变, 长宽进行改变;通过bounds修改长宽看起来就像是以中心点为基准点对长宽两边同时进行缩放;

frame和bounds

有如下图:

7edfe98d3752c6aba5e2aed41e08c625.png

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的例子, 如下图:

2f8960c863d3dd9f438f9f5467addedb.png

在上一个例子的基础上,我们更改了view A的bounds后,view B看待View A的左上角就已经发生改变了;这个时候我们看待View A的左上角就不是坐标原点了,而是我们通过bounds设置后的坐标,如图也就是(0, 100);

在View B的frame没有保存不变的基础上,我们View B的位置向上移动了100

在第一例的基础上,更改了bounds中X后,效果图如下:

8883d4e3b047e21d7809646e97858b31.png

总结

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以及它的子控件默认是不能接收触摸事件的

响应者链

  • 响应者链条:是由多个响应者对象连接起来的链条

  • 作用:能很清楚的看见每个响应者之间的联系,并且可以让一个事件多个对象处理。

  • 响应者对象:能处理事件的对象

f91dd3231e5a8756c817e48e367737dc.png

事件传递的完整过程

  • 先将事件对象由上往下传递(由父控件传递给子控件),找到最合适的控件来处理这个事件。

  • 调用最合适控件的touches….方法

  • 如果调用了[super touches….];就会将事件顺着响应者链条往上传递,传递给上一个响应者

  • 接着就会调用上一个响应者的touches….方法

  • 如何判断上一个响应者:

    • 如果当前这个view是控制器的view,那么控制器就是上一个响应者

    • 如果当前这个view不是控制器的view,那么父控件就是上一个响应者

响应者链的事件传递过程

  • 如果view的控制器存在,就传递给控制器;如果控制器不存在,则将其传递给它的父视图

  • 在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理

  • 如果window对象也不处理,则其将事件或消息传递给UIApplication对象

  • 如果UIApplication也不能处理该事件或消息,则将其丢弃

hitTest方法&pointInside方法

hitTest方法
  • 当事件传递给控件的时候,就会调用控件的这个方法,去寻找最合适的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;}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值