iOS开发经验(1)
目录:
- 在字符串查看指定字符串
- UILabel自适应
- 服务器数据处理
- copy解释
- 对象及可变字典赋值取值方法
- nil NSNULL NULL解释
- 字面量
- UILabel 黑线问题
- nullable和nonnull
1. 在Objective-C
中怎么检查一个字符串中是否还有另外一个字符串.
- iOS8或
OS X Yosemite
之后:
- (BOOL)containsString:(NSString *)str NS_AVAILABLE(10_10, 8_0);
之前:
NSString *string = @"hello bla bla";
if ([string rangeOfString:@"bla"].location == NSNotFound)
{
NSLog(@"string does not contain bla");
} else {
NSLog(@"string contains bla!");
}
2. UILabel自适应
UILabel
不能设置竖直方向的排列布局,可以通过sizeToFit
改变label
的frame
来实现曲线救国。- 适用于根据字体计算出文本单行的长度和高度(宽度和高度),注意是单行,首先来看单行文本的问题:对于单行文本来说,计算
CGSize
就比较简单了
CGSize size = [s.text sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:10]}];
- 多行文本的显示:
首先UILabel
的numberOfLines
设置为0
,其次通过方法来计算CGSize
,具体代码如下:button.titleLabel
亦如此
NSDictionary *attribute = @{NSFontAttributeName: font};
height = [text boundingRectWithSize:CGSizeMake(width, 0) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading | NSStringDrawingTruncatesLastVisibleLine attributes:attribute context:nil].size.height;
- UITextView多行文本的显示:
boundingRectWithSize:options:attributes
来计算。
嗯确实,这是个利器。其本上能正确返回字体的rect。但对于UITextView 似乎使用此方法计算出来的结果比实际显示的要小:UITextView
在上下左右分别有一个8px的padding
,需要将UITextView.contentSize.width
减去16像素(左右的padding 2x8px)。同时返回的高度中再加上16像素(上下的padding),这样得到的才是UITextView真正适应内容的高度。如代码中
CGSizeMake(width -16.0, CGFLOAT_MAX)
return sizeToFit.height + 16.0。
UILable中则不用.
- 通用(推荐)
CGSize sizeToFit = [textView sizeThatFits:CGSizeMake(width, MAXFLOAT)];
3.后台数据处理
- 系统请求方法
接受到的数据类型为NSData
类型,需要json解析
,解析后整个json字符串
为字典类型,里面的数据类型可以为数组
,字典
,NSNumber
(一般数据类型
?BOOL
),字符串
。
NSDictionary *responsObj = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
- AFN请求方法
通过指定接受到的数据格式为text/json
,可自动将数据解析 - ** 通过以上两种方法获取数据并解析后。要对数据做容错处理。**
通常我们以约定好的数据类型,定义对象去接受并使用数据,但要注意,有时候后台返回的并不是我们在客户端定义的数据类型,所以以下要做容错处理。
由于数据造成app crash
原因包括:获取的对象跟我们使用的方法不一致,比如字符串对象调用了字典的方法,空对象可以调用任何方法,这是系统设定的,不会crash
。
所以一般判断步骤为:首先以约定好的数据类型接受后台数据,然后判断对象是否为空(nil,NULL)
,再判断是什么类型,特别是嵌套包含的数据,要一步步判断,切勿偷懒直接用字面量方法获取。
4. 三种方式 copy, mutableCopy, =
-
copy
mutableCopy
统称为copy
共同特点:对原对象改变,副本不改变;对副本改变,原对象不改变,互不影响。
作用:在改变原有对象的时候,不会改变新对象的值
原因在本小段末尾解释. -
copy :
创建的对象是不可变副本(如NSString
、NSArray
、NSDictionary
)。
不可变对象调用copy
,原对象与副本对象地址一样,没有生成新对象,是浅拷贝;
可变对象调用copy
,原对象与副本对象地址不一样,生成了新的对象,是深拷贝。 -
mutableCopy:
创建的对象是可变副本也是新对象(如NSMutableString
、NSMutableArray
、NSMutableDictionary
)。
不论是可变不可变对象调用mutableCopy方法
,都属于深拷贝,生成一个新的对象,地址不一样。 -
=:
同一个对象,改变任何一个,它俩都随之改变. -
解释改变其中一个另一个却不改变的原因:
当对象为可变的对象,调用copy
或者mutableCopy
都会生成新的对象;
当对象为不可变的时候,使用copy
,不能对copy
后的对象进行操作;使用mutableCopy
,生成了新的对象.copy&mutableCopy.png
5. setvalue 与set object 字面量
setObject:ForKey:
是NSMutableDictionary特有的;setValue:ForKey:是KVC的主要方法;setObject:ForKey:
中object
对象不能为nil
,不然会报错;key
的参数只要是对象就可以,一般是NSString
;setValue:ForKey:
中Value
值可以为nil
,此时会自动调用removeObject:forKey:
方法;key
的参数只能是NSString
类型;setValue:ForKey:
是在NSObject
对象中创建的,即所有的对象都有这个方法,可以用于任何类(方法调用者是对象的时候);- 注意:
[imageDictionary setObject:[NSNullnull] forKey:indexNumber];
[NSNull null
]表示的是一个空对象,并不是nil,- 首先不可变字典可以调起
setValue:forKey:
,但不能真正的进行操作,这取决与不可变字典不可增删改的特性。
setValue:forKey:
与setValue:forKeyPath:
- 动态设置:
setValue:属性值 forKey:属性名
(用于简单路径)、setValue:属性值 forKeyPath:
属性路径(用于复合路径,例如Person有一个Account类型的属性,那么person.account
就是一个复合属性) - 动态读取:
valueForKey:属性名
、valueForKeyPath:属性名
(用于复合路径)
Amodel *modelA = [[Amodel alloc]init];
SubAmodel *modela = [[SubAmodel alloc]init];
modela.str = @"qq.com";
modelA.submodel = modela;
NSLog(@"%@",modelA.submodel.str);
[modelA setValue:@"QQ.com" forKeyPath:@"submodel.str"];
NSLog(@"%@",modelA.submodel.str);
- 建议在
NSDictionary
下只用objectForKey:
来取值。
6. NSNull NULL nil
- nil:指向oc中对象的空指针,针对对象。指针为空,不会发送消息的;
return NO
Nil:指向oc中类的空指针,针对类。
NULL:指向其他类型的空指针,如一个c类型的内存指针,基本数据类型为空,基本类型。
null,占位空对象,即野指针,指向垃圾内存,造成crash
。抛出异常NSException
NSNull:类名,它的实例就是null,空值对象。
NSArray *arr1 = [NSNull null];
NSArray * arr2 = nil;
NSLog(@"%@", arr1); <null>
NSLog(@"%@", arr2); (null)
NSLog(@"%p", arr1); 0x10cf4bd80
NSLog(@"%p", arr2); 0x0
if ([arr1 isKindOfClass:[NSArray class]]) {
}
if (arr1 == NULL) {
}
if (arr1 == nil) {
}
if ([arr1 isKindOfClass:[NSNull class]]) {
//arr1 只走这个+1
}
if ([arr2 isKindOfClass:[NSArray class]]) {
}
if (arr2 == NULL) {
//arr2 走这个+1
}
if (arr2 == nil) {
//arr2 走这个+2
}
if ([arr2 isKindOfClass:[NSNull class]]) {
}
[arr1 count]; //会crash。
[arr2 count];//不会crash。
僵尸对象
:被释放的对象为僵尸对象, 已经被销毁的对象(不能再使用的对象).野指针
: 指向僵尸对象(不可用内存
)的指针 给野指针发消息会报EXC_BAD_ACCESS
错误空指针
: 没有指向存储空间的指针(里面存的是nil
, 也就是0
) 给空指针发消息是没有任何反应的,空指针是把指针为nil
.
为了避免野指针错误的常见办法: 在对象被销毁之后, 将指向对象的指针变为空指针
接受null
数据并打印并不会造成crash
,造成crash
的原因是发送消息- 服务器返回的数据,有
null
,还有nil
,iOS
是使用的OC
语言, 跟C语言略有不同, 在OC中打印出(nill)
和<null>
两种情况是不同的,<null>
的判断方法是
- (id) setNoNull:(id)aValue{
if (aValue == nil) {
aValue = @"";//为null时,直接赋空
} else if ((NSNull *)aValue == [NSNull null]) {
aValue = @"";
if ([aValue isEqual:nil]) {
aValue = @"";
}
}
return aValue;
}
既然它老出bug ,为什么还在oc里还有它的存在NSNull
与nil
以及NULL
不同,因为它是一个实际的对象,而不是一个零值。nil
null
其实就是0
NSNull
在Foundation
和其它框架中被广泛的使用,以解决如NSArray
和NSDictionary
之类的集合不能有nil
值的缺陷。
你可以将NSNull
理解为有效的将NULL
或者nil
值封装boxing,以达到在集合中使用它们的目的:
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionary];
mutableDictionary[@"someKey"] = [NSNull null]; // Sets value of NSNull singleton for `someKey`
NSLog(@"Keys: %@", [mutableDictionary allKeys]); // @[@"someKey"]
7. 字面量
- 字面数值
需要把整数、浮点数、布尔值封入到对象里。通常情况下会用到如下方法:
NSNumber *number = [NSNumber numberWithInt:8];
使用字面量语法后,不仅语法更简洁,还有很多好处。
NSNumber *number = @(8);
能够用以NSNumber实例表示的所有数据类型,都可以使用字面量语法。。。 - 字面量数组
数组的常用创建方法如下:
NSArray *array = [NSArray arrayWithObjects:@"obj1", @"obj2", nil];
而使用字面量语法则是:
NSArray *array = @[@"obj1", @"obj2"];
数组的取下标也有字面量语法:
NSString *obj = [array objectAtIndex:1];
使用字面量:
NSString *obj = array[1];
不过使用字面量数组时,要注意不要把nil加入到数组中,否则会抛出异常。
-
字面量字典
常用创建方式如下:
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:@"obj1", @"value1", @"obj2", @"value2", nil];
而使用字面量语法,就比较简洁了。
NSDictionary *dictionary = @{@"obj1": @"value1",
@"obj1": @"value1"};
字面量语法清晰表示出了,key和value的一一对应关系。但是与数组一样,字面量字典的value不能为nil,否则会出现异常。 -
字面量可变数组与字典
对于可变的数组与字典,同样可以使用自变量语法对自变量数组,字典进行操作。
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:@{@"obj1": @"value1", @"obj1": @"value1"}];mutableDictionary[@"obj3"] = @"value3"; -
** 注意用字面量语法创建数组及字典时要注意,若数组元素对象中有nil,则会抛出异常**
使用字面量语法创建出来的字符串、数组、字典对象都是不可变的(immutable
)。若想要可变版本的对象,则需复制一份:
NSMutableArray *mutableArray = [@[@1, @2, @3, @4, @5]mutableCopy];
mutableArray[3] = @33;
NSMutableDictionary* mutableDic = [@{@"name":@"song",
@"age":@"28",
@"tel":@"1234567"}mutableCopy];
mutableDic[@"age"]=@"30";
- 总结
Foundation
框架 是在iOS开发
中 用到的最频繁的基础框架,它提供了几个最基本的类:NSString
、NSNumber
、NSArray
、NSDictionary
. 在这个框架下,尽量使用对象字面量语法创建字符串, 数字, 数组和字典等.其他自定义的对象不可使用字面量。- 在数组和字典中, 要使用关键字和索引做下标来获取数据
- 使用对象字面量语法时, 容器类里不可是nil, 否则运行时贵抛出异常.
8. UILabel 黑线问题
偶尔发现UILabel右边缘出现黑线,iPhone6P、6sP最为明显:
使用循环计算label尺寸, 循环创建label时有可能出现右边缘黑线的问题, 且有时在iPhone5s一下机型不会出现, 只在iPhone6以上出现 ,这是因为计算出得size可能的值会是30.31123323…… 这样的数。
猜想: 而像素值显示的时候不可能出现显示半个像素的情况, 那么不足一个像素的值就会被忽略掉, 在分辨率较低的机型上不会出现, 而分辨率较高的则不会忽略, 就出现了黑线。
解决方法
计算出来的UILabel尺寸,向上取整
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc]init];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping ;
[paragraphStyle setLineSpacing:4];
NSDictionary *attributes = @{NSFontAttributeName:kDesFont, NSParagraphStyleAttributeName:paragraphStyle.copy};
CGSize size = [text boundingRectWithSize:CGSizeMake(cellWidth, 0) options:NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:attributes context:nil ].size ;
size.width = ceil(size.width);
size.height = ceil(height);
9. nullable和nonnull
Xcode 6.3新特性:Nullability Annotations,这一新特性的核心是两个新的类型注释:nullable和nonnull。从字面上我们可以猜到,nullable表示对象可以是NULL或nil,而nonnull表示对象不应该为空。当我们不遵循这一规则时,编译器就会给出警告。
事实上,在任何可以使用const关键字的地方都可以使用nullable和nonnull,不过这两个关键字仅限于使用在指针类型上。而在方法的声明中,我们还可以使用不带下划线的nullable和nonnull,如下所示:
- (nullable id)itemWithName:(NSString * nonnull)name
在属性声明中,也增加了两个相应的特性,因此上例中的items属性可以如下声明:
@property (nonatomic, copy, nonnull) NSArray * items;
也可以用以下这种方式:
@property (nonatomic, copy) NSArray * __nonnull items;
如果需要每个属性或每个方法都去指定nonnull和nullable,是一件非常繁琐的事。苹果为了减轻我们的工作量,专门提供了两个宏:NS_ASSUME_NONNULL_BEGIN,NS_ASSUME_NONNULL_END。在这两个宏之间的代码,所有简单指针对象都被假定为nonnull,因此我们只需要去指定那些nullable的指针。
NS_ASSUME_NONNULL_BEGIN
@interface TestNullabilityClass ()
@property (nonatomic, copy) NSArray * items;
- (id)itemWithName:(nullable NSString *)name;
@end
NS_ASSUME_NONNULL_END
作者:Ryan___
链接:https://www.jianshu.com/p/98a656d16c36
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
iOS开发经验(2)
目录
- KVC
- 观察者模式:KVO与通知
- 精度问题
- 三目运算符
- 点语法
1. KVC(Key-value coding)键值编码。用字符串动态去操作对象
其实现方法是使用字符串描述要更改的对象状态部分。通过Key
名直接访问对象的属性,或者给对象的属性赋值。这样就可以在运行时动态在访问和修改对象的属性。而不是在编译时确定。
所有的对象都可以使用KVC
键统一是字符串,而值是不支持基本数据类型的,必须将值转换为NSNumber或者NSValue类型
操作对象的属性和对象属性的属性,访问变量的属性,即使该属性没有get,set方法也可以调用
Human *human = [[Human alloc]init];
//将name属性设置为"holydancer"
[human setValue:@"holydancer" forKey:@"name"];
//将human中的name属性取出
NSString *nameOfHuman=[human valueForKey:@"name"];
KVC集合运算符
ProductModel *model = [[ProductModel alloc]init];
model.name = @"iMac";
model.price = @18888;
ProductModel *model1 = [[ProductModel alloc]init];
model1.name = @"iphone";
model1.price = @6999;
NSArray *array = @[model,model1];
简单类型的集合操作符:返回 strings, numbers, dates,简单集合操作符作用于 array 或者 set 中相对于集合操作符右侧的属性。包括 @avg, @count, @max, @min, @sum.
NSString *name = [array valueForKeyPath:@"@count"];//返回集合中对象总数的 NSNumber 对象。操作符右边没有键路径。
NSNumber *price_max = [array valueForKeyPath:@"@max.price"];//比较由操作符右边的键路径指定的属性值,并返回比较结果的最大值。最大值由指定的键路径所指对象的 compare: 方法决定
NSString *price_min = [array valueForKeyPath:@"@min.price"];//返回的是集合中的最小值
NSNumber *price_sum = [array valueForKeyPath:@"@sum.price"];//属性值的总和
NSNumber *price_avg = [array valueForKeyPath:@"@avg.price"];//转换为 double, 计算其平均值,返回该平均值的 NSNumber 对象。当均值为 nil 的时候,返回 0.
提示:你可以简单的通过把 self 作为操作符后面的 key path 来获取一个由 NSNumber 组成的数组或者集合的总值,例如对于数组 @[@(1), @(2), @(3)] 可使用 valueForKeyPath:@"@max.self" 来获取最大值。
NSLog(@"%@,%@,%@,%@,%@",name,price_max,price_min,price_sum,price_avg);
对象操作符,返回 NSArray 对象实例:对象操作符包括 @distinctUnionOfObjects 和 @unionOfObjects, 返回一个由操作符右边的 key path 所指定的对象属性组成的数组。其中 @distinctUnionOfObjects 会对数组去重,而 @unionOfObjects 不会。
NSArray *unionOfObjects = [array valueForKeyPath:@"@unionOfObjects.name"]; // 1.
NSArray *distinctUnionObjects = [array valueForKeyPath:@"@distinctUnionOfObjects.name"]; //2.
NSLog(@"%@,%@",unionOfObjects,distinctUnionObjects);
数组和集合操作符,返回的是一个 array 或者 set 对象
数组和集合操作符作用对象是嵌套的集合,也就是说,是一个集合且其内部每个元素是一个集合。数组和集合操作符包括 @distinctUnionOfArrays,@unionOfArrays,@distinctUnionOfSets:
@distinctUnionOfArrays / @unionOfArrays 返回一个数组,其中包含这个集合中每个数组对于这个操作符右面指定的 key path 进行操作之后的值。 distinct 版本会移除重复的值。
@distinctUnionOfSets 和 @distinctUnionOfArrays 差不多, 但是它期望的是一个包含着 NSSet 对象的 NSSet ,并且会返回一个 NSSet 对象。因为集合不能包含重复的值,所以它只有 distinct 操作。
NSArray *array1 = @[model,model1];
NSArray *totalArray = @[array,array1];
NSArray *distinctUnionOfArrays = [totalArray valueForKeyPath:@"@distinctUnionOfArrays.name"];
NSArray *unionOfArrays = [totalArray valueForKeyPath:@"@unionOfArrays.name"];
NSLog(@"%@,%@",distinctUnionOfArrays,unionOfArrays);
注意: 如果操作符右侧 key path 指定的对象为 nil,那么返回的数组中会包含 NSNull 对象.
- 四个主要方法
必须手动将值类型转换成NSNumber
或者NSValue
类型,才能设置为value
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
valueForKey:
总是返回一个id对象,如果原本的变量类型是值类型或者结构体,返回值会封装成NSNumber或者NSValue对象。
- (nullable id)valueForKey:(NSString *)key;
- (nullable id)valueForKeyPath:(NSString *)keyPath;
-
嵌套数据,
KEY
为数组或者字典- 对于有序的容器,可以用下面的方法:
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
- 对于无序的容器,可以用下面的方法:
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
-
键路径:嵌套数据中,使用
KeyPath
-
在
KVC
中处理异常情况- 如果某对象有一个常用数据类型,比如
bool
,在用setvalue:
设置value
的时候,需要实现setNilValueForKey:(NSString *)key
- 如果某对象有一个常用数据类型,比如
-
在用
set value
: 设置value的时候,如果该对象不存在该属性,比如bool
,需要实现- (void)setValue:(id)value forUndefinedKey:(NSString *)key
-
KVC使用场景
- 动态地取值和设值:利用KVC动态的取值和设值是最基本的用途了。
-
用KVC来访问和修改私有变量:利用KVC可以随意修改一个对象的属性和变量(即使是私有变量)
对于类里的私有属性,Objective-C
是无法直接访问的,但是KVC
是可以的。 -
Model和字典转换
-
利用KVC集合运算符,KVC可以通过运算符层次查找对象的属性;KVC获取值不仅可以返回一个数据,还可以将某一个属性的所有值,数据归类出来(B不一定是类,也可以是数组)
-
利用KVC可以修改系统的只读变量,修改一些控件的内部属性
这也是iOS开发中必不可少的小技巧。众所周知很多UI控件都由很多内部UI控件组合而成的,但是Apple度没有提供这访问这些空间的API
,这样我们就无法正常地访问和修改这些控件的样式。而KVC
在大多数情况可下可以解决这个问题。最常用的就是个性化UITextField
中的placeHolderText
了。
因为数据造成crash
的原因大概几点:
- 使用字面量创建数组、字典,
value
为nil
- 使用KVC方法给数组字典赋值为
nil
null
发送方法。其他的诸如null
的判断方法及给控件赋值都不会引起crash。
2. 观察者模式:KVO与通知
iOS的一种设计模式, 观察者设计模式,依赖于 Objective-C 强大的 Runtime。观察者模式包含:
1.通知机制(notification)
2.KVO机制【可参考iOS--KVO的实现原理与具体应用】
- 通知机制:
委托机制是代理“一对一”的对象之间的通信,而通知机制是广播“一对多”的对象之间的通信。
//A类获取通知中心,并发送通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"statusBarHidden" object:nil userInfo:dic];
//B类注册通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarHidden:) name:@"statusBarHidden" object:nil];
//释放所有通知
- (void)removeObserver:(id)observer;
//释放名称为aName的通知
- (void)removeObserver:(id)observer name:(nullable NSString *)aName object:(nullable id)anObject;
- KVO
KVO提供一种机制,指定一个被观察对象,当对象某个属性发生更改时,对象会获得通知,并作出相应处理。KVO这种编码方式使用起来很简单,很适用与model修改后,引发的UIVIew的变化这种情况,当更改属性的值后,监听对象会立即得到通知;当指定的对象的属性被修改后,对象就会接受到通知,****前提是执行了setter方法、或者使用了KVC赋值****。
当指定的对象的属性被修改后,则对象就会接受到通知。简单的说就是每次指定的被观察的对象的属性被修改后,KVO就会自动通知相应的观察者了。
原理:
- 当一个object有观察者时,动态创建这个object的类的子类
- 对于每个被观察的property,重写其set方法
- 在重写的set方法中调用- willChangeValueForKey:和- didChangeValueForKey:通知观察者
- 当一个property没有观察者时,删除重写的方法
- 当没有observer观察任何一个property时,删除动态创建的子类
- 注册,指定被观察者的属性
[objc addObserver:self forKeyPath:@"title" options:
NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
- 实现回调方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{}
- 移除观察
[self removeObserver:self forKeyPath:@"title" context:nil];
- 两者区别:
notification比KVO多了发送通知的一步。两者都是一对多,但是对象之间直接的交互,notification明显多,需要notificationCenter来做为中间交互。
notification的优点是监听不局限于属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,例如键盘、前后台等系统通知的使用也更显灵活方便.
3. 精度问题
- 原因:
NSNumber
的description
方法不够严谨,在调用NSNumber的description方法打印数值时,会发生精度损失。 - 建议 :
- 如果是double类型,处理精度有关的数据用
double
。建议把NSNumber转换成double再进行输出(NSString)或计算(CGFloat); - 有关浮点型数据,后台传字符串的格式,防止丢失精度.
- 如果是double类型,处理精度有关的数据用
NSNumber *value=dic[@"number"];
NSLog(@"value:%@", value);
输出:value:81.59999999999999
如果是double类型,建议把NSNumber转换成double再进行输出或计算 。
CGFloat numberValue = [self doubleValue];
NSString *value=[NSString stringWithFormat:@"%g",[dic[@"number"] doubleValue]];
4. 三目运算符
- 基本格式 : (关系表达式) ? 表达式1 : 表达式2;
- 执行流程 : 关系表达式为 真 返回表达式1 关系表达式为假 返回表达式2
int num1=8,num2=3,result=0;
result= num1>num2?num1:num2;
//因为num1>num2 成立 所以最后结果为num1的值
5. 点语法
- 点语法的本质还是方法调用,当使用点语法时,编译器会自动展开成相应的方法,而不是访问成员变量.
- 切记点语法的本质是转换成相应的对
setter
和getter
方法调用,如果没有set
和get
方法,则不能使用点语法。 - 不要在
getter
与setter
方法中使用本属性的点语法
作者:Ryan___
链接:https://www.jianshu.com/p/9430dd501ff0
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
iOS开发经验(3)-NavigationBar&TabBar&StatusBar
目录:
- NavigationBar
- 由导航栏引起的零点坐标问题
- TabBar
- StatusBar
- NSAttributedString
- 文本自适应
1. NavigationBar
- 父子关系:UINavigationController->
UINavigationbar(设置tintColor/barTintColor/图像)
UINavigationItem(设置左右按钮/title/titleview)( 上下平级) - translucent 管理半透明效果
YES为开启 NO为关闭 iOS7之后默认为YES。当为YES时ViewController上的View的原点坐标会以navigationBar以下的坐标为原点,就是在view 上创建视图将不在考虑navigationBar的高度 - titleTextAttributes 是UINavigationBar的一个属性,通过此属性可以设置title部分的字体。有时我们在开发过程中会遇到当我们导航栏的颜色是比较暗的颜色是我们设置的文字不容易看见,这个时候我们用titleTextAttributes可以设置title字体颜色。
setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor whiteColor]} - title 和 navigationItem.title 的区别当你的项目中没有tabBarController时.title和navigationItem.title效果是一样的当你的项目中有设置 tabBarController 时设置self.title会显示在TabBarItem上和navigationBar上。但如果你只需要TabBarItem上和NavigationBar上显示的不一样的话这些都需要单独设置。
- navigationBarHidden,为了app的界面更好看通常我们都会给viewController一个背景图片,这个时候navigationBar在不是必须要的情况下navigationBar会影响界面的美观我们通常会将navigationBar隐藏起来再需要的时候将其显示出来。
self.navigationController.navigationBarHidden = NO;
self.navigationController.navigationBar.hidden = NO;
两种方法不一样
两种方法都是可以隐藏导航栏的,隐藏之后依然可以使用push和pop方法。
但是如果用navigationBar.hidden隐藏导航栏,我们可以继续使用navigationBarHidden提供的滑动pop效果.
如果用navigationBarHidden,这个操作将无效;
但前者navigationBar.hidden没有系统自动的动画效果。
- barStyle设置navigationBar的颜色,系统只支持这两种格式
self.navigationController.navigationBar.barStyle = UIBarStyleBlack;UIBarStyleDefault = 0,
//白色默认UIBarStyleBlack = 1
//黑色
- barTintColor 背景色; tintColor分别是item的颜色;titleTextAttributes可以设置title字体颜色。
self.navigationController.navigationBar.barTintColor = [UIColor redColor];self.navigationController.navigationBar.tintColor = [UIColor whiteColor];
- UINavigationItem自定义view大小一般为w=h=30;用btn.imageEdgeInsets = UIEdgeInsetsMake(0, -20, 0, 0)指定位置,视图的x和y无效
//设置导航标题视图,就是这一块可以加载任意一种视图,视图上下左右居中显示在标题的位置,视图的x和y无效
UIView *textView1=[[UIView alloc]initWithFrame:CGRectMake(10, 10, 50, 30)];
textView1.backgroundColor=[UIColor whiteColor];
[self.navigationItem setTitleView:textView1];
- 导航栏透明
[self.navigationController.navigationBar setBackgroundImage:[UIImage new]
forBarMetrics:UIBarMetricsDefault];
//shadowImage,是导航栏下面的那根细线,如果不设置则会看到一根线。
self.navigationController.navigationBar.shadowImage = [UIImage new];
- 导航栏透明渐变
self.barImageView = self.navigationController.navigationBar.subviews.firstObject;
对self.barImageView.alpha 做出改变
- [UINavigationBar appearance]类方法
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[UINavigationBar appearance].tintColor = [UIColor orangeColor];
[[UINavigationBar appearance] setBackgroundImage:[UIImage imageNamed:@"m_nav64"] forBarMetrics:UIBarMetricsDefault];
return YES;
}
2. 由导航栏引起的零点坐标问题
屏幕原点的改变(只要是VC中的控件,都是从设备左上角的(0,0)开始算的)。iOS 7之后都是从Statusbar左上角(0,0)开始布局的,但是有时,我们也会遇到在 NavigationController 中是以(0,64)布局的,此处又是什么情况呢?先来看一下下面三个属性:
- edgesForExtendedLayout
表示视图是否覆盖到四周的区域,默认是UIRectEdgeAll,即上下左右四个方向都会覆盖,那么为让顶部不进行延伸到导航栏覆盖的区域,我们可以把顶部区域延伸去掉。这个属性是UIExtendedEdge类型,用来制定视图的哪条边需要扩展。默认是UIRectEdgeAll,也就是全屏布局(iOS7中鼓励这样,这样可以透过半透明的bar看到一些模模糊糊的内容),如果设置为UIExtendedEdgeNone,view就不会延伸到bar的后面了 - extendedLayoutIncludesOpaqueBars默认值NO,这个属性指定了当Bar使用了不透明图片时,视图是否延伸至Bar所在区域; 但是Bar的默认属性是透明的。也就是说只有在不透明下才有用;因此,如果我们自定义了nav bar背景图片,view会从导航栏下面开始布局。
- automaticallyAdjustsScrollViewInsets默认值是YES,如果视图里面存在唯一一个UIScrollView或其子类View,那么它会自动设置相应的内边距(如果有navbar的时候,这个内边距是64,这样scrollview可以占满屏幕,内容在64像素以下,不会被遮到,滑动scrollview,可以透过半透明效果看到scrollview上面的内容)。设置改变的是inset,而不是frame。
- VC中的view默认会对UIScrollView做一个适应导航栏的处理,由此推测,其实只要是VC中的控件,都是从设备左上角的(0,0)开始算的,只是对于UIScrollView,VC会自动调整一下内容的位置而已。
在有导航的情况下,可视范围的Y坐标就是从64开始的,除了UIScrollView的控件,定位的时候,都应当以(0,64)为原点;而UIScrollView如果是全屏的,那么无所谓,如果不是全屏的,请注意是否需要设置VC的automaticallyAdjustsScrollViewInsets。 - 所以说有时,我们发现原点位置变化了,就可以看看上述几个属性是否有设置改动的。经常我们用到
tableView
或collectionView
的时候就需要设置self.automaticallyAdjustsScrollViewInsets = NO
, 不让其自动调整。 - 这几个属性当使用的时候互相影响互相有联系,对原点改变的影响力:navigationBarHidden> edgesForExtendedLayout> translucent> extendedLayoutIncludesOpaqueBars
3. 定制TabBar
*父子关系:UITabBarController->UITabbar(设置tintColor/barTintColor/图像)->UITabBarItem(设置titile/image/badgeValue ; 每个tabBarItem对应一个viewController)
-
图片渲染
-
隐藏tabbar
第一种
vc.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:vc animated:YES];
第二种
//隐藏
self.hidesBottomBarWhenPushed = YES;
//显示
self.hidesBottomBarWhenPushed = NO;
第三种
//隐藏TabBar
- (void)hideTabBar {
if (self.tabBarController.tabBar.hidden == YES) {
return;
}
UIView *contentView;
if ( [[self.tabBarController.view.subviews objectAtIndex:0] isKindOfClass:[UITabBar class]] )
contentView = [self.tabBarController.view.subviews objectAtIndex:1];
else
contentView = [self.tabBarController.view.subviews objectAtIndex:0];
contentView.frame = CGRectMake(contentView.bounds.origin.x, contentView.bounds.origin.y, contentView.bounds.size.width, contentView.bounds.size.height + self.tabBarController.tabBar.frame.size.height);
self.tabBarController.tabBar.hidden = YES;
}
//显示TabBar
- (void)showTabBar {
if (self.tabBarController.tabBar.hidden == NO)
{
return;
}
UIView *contentView;
if ([[self.tabBarController.view.subviews objectAtIndex:0] isKindOfClass:[UITabBar class]])
contentView = [self.tabBarController.view.subviews objectAtIndex:1];
else
contentView = [self.tabBarController.view.subviews objectAtIndex:0];
contentView.frame = CGRectMake(contentView.bounds.origin.x, contentView.bounds.origin.y, contentView.bounds.size.width, contentView.bounds.size.height - self.tabBarController.tabBar.frame.size.height);
self.tabBarController.tabBar.hidden = NO;
}
4. StatusBar:系统提供了2种管理状态栏的方式
- 通过UIViewController管理(每一个UIViewController都可以拥有自己不同的状态栏),默认是交给控制器来管理的.直接重写这个方法
在控制器当中设置状态栏样式
-(UIStatusBarStyle)preferredStatusBarStyle{
return UIStatusBarStyleLightContent;
}
-(BOOL)prefersStatusBarHidden{
return YES;
}
- 由程序来管理隐藏及style,通过UIApplication管理(一个应用程序的状态栏都由它统一管理)
前提:通常在开发当中都是应用程序来管理状态栏的.来做统一管理,不然的话, 会有很多个控制器.会非常的麻烦.想要让应用程序管理状态栏,要在info.plist
当中进行配置,
添加一个key
值:是最后一个,View controller-based status bar appearance
设置为NO.就是应用程序来管理了.
UIApplication *app = [UIApplication sharedApplication];
[app setStatusBarHidden:YES withAnimation:UIStatusBarAnimationFade];
[app setStatusBarStyle:UIStatusBarStyleLightContent animated:YES];
- 既然两种都可以对状态栏进行管理,那么什么时候该用什么呢?
如果状态栏的样式只设置一次,那就用UIApplication来进行管理;
如果状态栏是否隐藏,样式不一样那就用控制器进行管理。
UIApplication来进行管理有额外的好处,可以提供动画效果。
5. NSAttributedString-富文本
- 普通的文本属性已经无法满足需求,就需要我们学习和使用更加灵活的富文本。可实现图文混排及生成链接(在 UILabel 和 UITextField 中是无法使用该属性的。更准确点说是在UILabel 和 UITextField 中无法实现点击链接启动浏览器打开一个URL地址,因为在此过程中用到了一个代理函数。只能用在 UITextView 中。)
AttributedString
可以分为NSAttributedString
和NSMutableAttributedString
两种。在使用中通过将AttributedString
赋值给控件的attributedText
属性来添加文字样式。有此属性的控件有UILabel
、UITextField
和UITextView
.- 两种用法
//初始化NSMutableAttributedString
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]init];
//设置字体格式和大小
NSString *str = @"设置字体格式和大小";
NSDictionary *dictAttr = @{NSFontAttributeName:[UIFont systemFontOfSize:14]};
NSAttributedString *attr = [[NSAttributedString alloc]initWithString:str attributes:dictAttr];
[attributedString appendAttributedString:attr];
NSString *str = @"人生若只如初见,何事悲风秋画扇。\n等闲变却故人心,却道故人心易变。\n骊山语罢清宵半,泪雨霖铃终不怨。\n何如薄幸锦衣郎,比翼连枝当日愿。";
// 创建 NSMutableAttributedString
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:str];
// 设置字体和设置字体的范围
[attrStr addAttribute:NSFontAttributeName
value:[UIFont systemFontOfSize:30.0f]
range:NSMakeRange(0, 3)];
6. 文本自适应
- sizeToFit
当 写上label.numberOfLines = 0,宽度不变,高度变化
当 没有写上label.numberOfLines = 0,宽度自适应,高度变化仍是单行
- boundingRectWithSize:这个方法是 iOS7 以后根据宽高属性计算字符串宽度跟高度的一个方式.不同的属性会计算出不同的值.具体的可以谷歌下
NSStringDrawingOptions
.
计算高度:给定具体宽度值,高度值为0;
计算宽度:给定具体高度值,宽度值为0;
NSDictionary *attributeDic = @{NSFontAttributeName: [UIFont systemFontOfSize:14]};
CGRect rect = [info boundingRectWithSize:CGSizeMake(0, 30) options:
NSStringDrawingUsesLineFragmentOrigin|
NSStringDrawingUsesFontLeading
attributes: attributeDic context:nil];
// 参数1: 自适应尺寸,提供一个宽度(高度),去自适应高度(宽度)
// 参数2:自适应设置 (以行为矩形区域自适应,以字体字形自适应)
// 参数3:文字属性,通常这里面需要知道是字体大小
// 参数4:绘制文本上下文,做底层排版时使用,填nil即可
NSStringDrawingTruncatesLastVisibleLine : 如果文本内容超出指定的矩形限制,文本将被截去并在最后一个字符后加上省略号 . 如果三选项没有选择, 忽略此选项.
NSStringDrawingUsesLineFragmentOrigin : 整个文本将以每行组成的矩形为单位计算整个文本的尺寸.
NSStringDrawingUsesFontLeading : 以字体间的行距(leading,行距:从一行文字的底部到另一行文字底部的间距。)来计算高度。
NSStringDrawingUsesDeviceMetrics : 计算布局时使用图像符号边界, 而不是排版的边界 .
注意:当计算NSMutableAttributedString
时, 必须设置富文本的字体样式(NSMutableParagraphStyle),才可以计算正确
sizeWithAttributes
:计算宽度
适用于根据字体计算出文本单行的长度和高度(宽度和高度),注意是单行,所以你返回的高度是一个定值。
CGSize size = [s.text sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:10]}];
作者:Ryan___
链接:https://www.jianshu.com/p/6ed00e778a9f
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
iOS开发经验(4)-UIApplication& UIWindow
目录
- [[UIApplication sharedApplication]
- UIWindow
1. [[UIApplication sharedApplication]
- UIApplication对象是应用程序的象征.
每一个应用都有自己的UIApplication对象,这个对象是系统自动帮我们创建的, 它是一个单例对象.
一个iOS程序启动后创建的第一个对象就是UIApplication对象
我们只能通过[UIApplication sharedApplication]获得这个单例对象,不能够手动去创建它. - UIApplication作用
利用UIApplication对象,能进行一些应用级别的操作.(openURL:)
可以设置应用程序图标右上角的红色提醒数字
设置联网指示器的可见性
可以设置应用程序的状态栏
进行应用之间的跳转. - UIApplication Delegate
UIApplication会在程序一启动时候创建一个遵守UIApplicationDelegate的代理.
这个就是我们程序一创建时的AppDelegate类。AppDelegate就是遵守了UIApplicationDelegate协议。 在这个类中定义很多监听系统事件的方法.同时也定义了一些应用程序的生命周期方法.
delegate可处理的事件包括:
应用程序的生命周期事件(如程序启动和关闭)
系统事件(如来电)
内存警告
2. UIWindow
- UIWindow是一种特殊的UIView,通常在一个app中至少会有一个UIWindow
iOS程序启动完毕后,创建的第一个视图控件就是UIWindow,接着创建控制器的view,最后将控制器的view添加到UIWindow上,于是控制器的view就显示在屏幕上了。UIWindow可以手动创建,且可以是多个。
一个iOS程序之所以能显示到屏幕上,完全是因为它有UIWindow
也就说,没有UIWindow,就看不见任何UI界面 - 之前想要拿到app的窗口,我们通常的写法是:
[UIApplication sharedApplication].keyWindow
这样写是不安全的,如果应用程序没有跳转,这种写法还算是可行的,但是如果应用程序出现了跳转(分享跳转到其他APP,访问系统相册等),这时返回原APP,你会发现加载原窗口上的视图位置会发生明显偏移,查阅了一些资料,发现如果写成[[[UIApplication sharedApplication]delegate]window]
就不会出现上述问题 - 更换RootviewController
作者:Ryan___
链接:https://www.jianshu.com/p/71fd884099c4
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
iOS开发经验(5)-数据类型及编码解码(NSData、NSString..)
目录
- 关于字符串编码
- NSData
- UIEdgeInsets、contentInset、contentEdgeInsets
- ios中常用的遍历运算方法
- 排序
1. 关于字符串编码
ASCII码跟Unicode没有本质的区别。只不过Unicode表示范围比ASCII大。ASCII可以表示127个英文字母,其中每个英文字母都有一个十进制编码,并且通过这个十进制编码转化成二进制数(编码)存入到内存当中(占1字节)。 而在Unicode中,英文字母的编码与其在ASCII中没有不同。只是Unicode每个字符占2个字节,于是转化为二进制时就变成‘000000 ASCII’。
unicode是一个字符集,utf8是在这个字符集基础上的一种具体的编码方案。
- 中文解码
诸如\u82f1\u6587
NSString* strA = [@"%E4%B8%AD%E5%9B%BD" stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *strB = [@"中国" stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
- URL编码:有的时候会碰见字符串里有一些特殊字符在转成URL的时候 会出现转换不了的情况,这个时候需要对字符串进行编码
首先来看下什么样的是URL编码(字符串中带有%22 类似这样的)
NSString *str = @"[http://m.tuniu.com/api/home/data/index/c/%7B%22v%22%3A%227.1.0%22%2C%22ct%22%3A20%2C%22dt%22%3A1%2C%22p%22%3A11210%2C%22cc%22%3A2500%7D/d/%7B%22clientModel%22%3A%22HONOR+H30-L01%22%2C%22width%22%3A720%7D](http://m.tuniu.com/api/home/data/index/c/%7B%22v%22:%227.1.0%22,%22ct%22:20,%22dt%22:1,%22p%22:11210,%22cc%22:2500%7D/d/%7B%22clientModel%22:%22HONOR+H30-L01%22,%22width%22:720%7D%10)"
URL编码: ios中http请求遇到汉字的时候,需要转化成UTF-8
//(iOS9.0(包括9.0)以上使用)
NSString *str3 =[str2 stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSetURLQueryAllowedCharacterSet]];
//(iOS9.0以下使用)
NSString *str3 =[str2 stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncodind];
URL解码:请求后,返回的数据,如何显示的是这样的格式:%3A%2F%2F,此时需要我们进行UTF-8解码,用到的方法是
//(iOS9.0(包括9.0)以上使用):
NSString *str2 = [str stringByRemovingPercentEncoding];
//(iOS9.0以下使用)
NSString *str2 =[str stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
- 密码加密:修改代码保存密码不能使用明文,使用base64进行加密。提交数据到服务器不能使用明文,使用base64进行加密。
#pragma mark - Base64
编码:
-(NSString*)base64Encode:(NSString *)string
{
//1.将字符串转换成二进制数据
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
//2.利用ios7.0的方法,直接 base64 编码
return [data base64EncodedStringWithOptions:0];
}
解码:
- (NSString*)base64Decode:(NSString *)string
{
//1.将base64编码后的字符串,解码成二进制数据
//这里不能使用注释掉的方法转换成二进制,因为 string 是已经编码过的字符串
//NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
NSData *data = [[NSData alloc]initWithBase64EncodedString:string options:0];
//2.返回解码的字符串
return [[NSString alloc] initWithData:dataencoding:NSUTF8StringEncoding];
}
2. NSData
NSData介绍
NSData主要是提供一块原始数据的封装,用来包装数据的,NSData存储的是二进制数据,方便数据的封装与流动,屏蔽了数据之间的差异,文本、音频、图像等数据都可用NSData来存储。比较常见的是NSString/NSImage数据的封装与传递。在应用中,最常用于访问存储在文件中、网络资源中的数据以及在多媒体开发时,比较常用,例如拼接音频、图片。
应用场景:
-
存储本地文本、文件等数据:
使用archiveRootObject: toFile:
方法可以将一个对象直接写入到一个文件中,但有时候可能想将多个对象写入到同一个文件中,那么就要使用NSData来进行归档对象.NSData可以为一些数据提供临时存储空间,以便随后写入文件,或者存放从磁盘读取的文件内容。
可以使用[NSMutableData data]创建可变数据空间。
NSKeyedArchiver:- 如果对象是NSString、NSDictionary、NSArray、NSData、NSNumber 等类型,可以直接用NSKeyedArchiver进行归档和恢复.
- 但不是所有的对象都可以直接用这种方法进行归档,只有遵守了NSCoding协议的对象才可以.
-
网络资源中的数据:
在进行网络数据通信的时候,数据在网络中是二进制形式传输,加密与解密配套时可以正确解码二进制流信息。经常会遇到NSData类型的数据。在该数据是dictionary结构的情况下,可以用NSJSONSerialization
来解析转换成dictionary。 -
多媒体开发(音频、图片):
通常用于上传图片及音频。
说明:NSData根本不管传递的内容到底是什么,仅仅是传递一块内存 —— 仅需内存的起始地址和长度
1. 数据相互转换
NSData与字符串的相互转换:
//将字符串转化为NSData
+(NSData *)toNSData:(NSString *)str{
NSError *error = nil;
NSData *aData = [str dataUsingEncoding:NSUTF8StringEncoding];
if (aData.length && error ==nil) {
return aData;
}else{
return nil;
}
}
//将NSData转化为字符串
+(NSString *)transformData:(NSData *)data{
NSError *error = nil;
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (string.length && error == nil) {
return string;
}else{
return nil;
}
}
NSData与UIImage的相互转换:
//将NSData转UIImage
UIImage *image = [UIImage imageWithData:data];
//将UIImage转NSData
NSData *data = UIImageJPEGRepresentation(image, 1.0);
NSDictionary -> NSData
// 方法1:NSKeyedArchiver
NSDictionary *dict = @{
@"key1": @"value1",
@"key1": @"value2"
};
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:dict];
// 方法2:NSJSONSerialization
NSError *writeError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict
options:NSJSONWritingPrettyPrinted
error:&writeError];
if (writeError != nil) {
NSLog(@"Convent to JSON failed: %@", [writeError localizedDescription]);
return;
}
NSData -> NSDictionary
// 方法1:NSKeyedUnarchiver
NSDictionary *myDictionary = (NSDictionary*) [NSKeyedUnarchiver unarchiveObjectWithData:myData];
// 方法2:NSJSONSerialization
NSDictionary *dictFromData = [NSJSONSerialization JSONObjectWithData:dataFromDict
options:NSJSONReadingAllowFragments
NSArray -> NSData
// 方法1:NSKeyedArchiver
NSArray *array = [NSArray arrayWithObjects:@"1",@"2",@"3",nil];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:arr];
// 方法2:NSJSONSerialization
NSError *writeError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:array
options:NSJSONWritingPrettyPrinted
error:&writeError];
if (writeError != nil) {
NSLog(@"Convent to JSON failed: %@", [writeError localizedDescription]);
return;
}
NSData -> NSArray
// 方法1:NSKeyedUnarchiver
NSArray *data2arry = [NSKeyedUnarchiver unarchiveObjectWithData:data];
// 方法2:NSJSONSerialization
NSArray *arrayFromData = [NSJSONSerialization JSONObjectWithData:dataFromArray
options:NSJSONReadingAllowFragments
error:&error];
总结说明:无论使用NSKeyedUnarchiver还是NSJSONSerialization,其实它接受的参数类型都是id,所以我们也可以将继承自NSObject的自定义类转换为NSData,但该对象必须遵循NSCoding协议,即实现:
- (instancetype)initWithCoder:(NSCoder *)aDecoder;
- (void)encodeWithCoder:(NSCoder *)aCoder;
2. NSData数据加密
#import <Foundation/Foundation.h>
@interface NSData (LSCore)
#pragma mark - 加密
// md5
- (NSString*)md5;
// sha
- (NSString *)sha1;
- (NSString *)sha256;
- (NSString *)sha384;
- (NSString *)sha512;
// base64
- (NSString *)base64Encode;
- (NSString *)base64Decode;
// des
- (NSString *)encryptWithKey:(NSString *)key;
- (NSString *)decryptWithKey:(NSString *)key;
// Add libz.dylib to your project.
#pragma mark - gzip
extern NSString* const GzipErrorDomain;
- (NSData*)gzip:(NSError**)error;
@end
3. UIEdgeInsets 、contentInset、contentEdgeInsets
4. ios中常用的遍历运算方法
想到循环遍历数组、字典这些常见的集合,大家脑子里第一反应就是for循环和快速遍历。
ios中常用的遍历运算方法
遍历的目的是获取集合中的某个对象或执行某个操作,所以能满足这个条件的方法都可以作为备选:
- 经典for循环(常用)
- for in (NSFastEnumeration)(常用)
- makeObjectsPerformSelector
- kvc集合运算符(效率低)
- enumerateObjectsUsingBlock
- enumerateObjectsWithOptions(NSEnumerationConcurrent)
- dispatch_apply
** 1.第一种方式:经典for循环**
Objective-C是基于C语言的,自然可以使用for循环.
遍历数组:
NSArray *iosArray = @[@"L", @"O", @"V", @"E", @"I", @"O", @"S"];
for (int i = 0; i < iosArray.count; i++) {
//处理数组中数据
NSLog(@"%@", iosArray[i]);
}
遍历数组很简单没问题,下面遍历字典.
遍历字典:
NSDictionary *dict = @{@"1":@"11", @"2":@"22", @"3":@"33"};
NSArray *keysArray = [dict allKeys];
for (int i = 0; i < keysArray.count; i++) {
//根据键值处理字典中的每一项
NSString *key = keysArray[i];
NSString *value = dict[key];
NSLog(@"%@", value);
}
我们知道字典和set是无序的,所以我们无法根据特定的整数下标来直接访问其中的值,于是需要先获取字典中的键或者set中的所有对象,这样就可以在获取到的有序数组上进行遍历了。然而创建数组是要额外的开销的,还会多创建出一个数组对象,他会保留collection中的所有对象,占用了内存。
总结优缺点:
优点:被广泛使用,容易接受,操作简单;
缺点:遍历字典和set是比较繁琐,会占用比较多的系统资源。
** 2.第二种方式:NSEnumerator**
NSEnumerator是一个抽象基类。其中nextObject是关键方法,它返回枚举里的下一个对象。每次调用该方法,其内部结构都会更新,使得下一次调用方法时能返回下一个对象。等到枚举中全部的对象都已经返回之后,在调用就会返回nil,表示达到了枚举的末端。
Foundation框架中的collection都实现了这种遍历方式,例如:
NSArray *array = @[@"L", @"O", @"V", @"E", @"I", @"O", @"S"];
NSEnumerator *enumerator = [array objectEnumerator];//正向遍历
NSEnumerator *enumerator = [array reverseObjectEnumerator];//反向遍历
id object;
while ((object = [enumerator nextObject]) != nil) {
//处理枚举器中的数据
NSLog(@"%@", object);
}
字典和set实现的方式相似,不同的是字典中有key和value,要根据具体的key取出value。同时提供了正向遍历和反向遍历。
总结优缺点:
优点:
代码更易读,不需要定义额外的数组;
缺点:
1、无法直接获取遍历操作的下标,需要另外声明变量记录;
2、需要自行创建NSEnumerator对象,稍显麻烦。
** 2.第二种方式:快速遍历**
O快速遍历与NSEnumerator差不多,然而语法更为简洁,它为for循环开设了in关键字,简化了遍历collection所需的语法,例如:
遍历数组
NSArray *iosArray = @[@"L", @"O", @"V", @"E", @"I", @"O", @"S"];
for (NSString *obj in iosArray) {
//处理数组中的数据
NSLog(@"%@", obj);
}
遍历字典也同样简单:
NSDictionary *dict = @{@"1":@"11", @"2":@"22", @"3":@"33"};
for (NSString *key in dict) {
//处理字典的键值
NSString *value = dict[key];
NSLog(@"%@", value);
}
反向遍历可以使用:
for (NSString *obj in [iosArray reverseObjectEnumerator])
总结优缺点
优点:
语法简洁,使用方便,效率高;
缺点:
- 无法方便获取当前遍历的下标;
- 无法在遍历过程中修改被遍历的collection,否则会导致崩溃。
** 4.第四种方式:基于块的遍历方式**
遍历数组
NSArray *iosArray = @[@"L", @"O", @"V", @"E", @"I", @"O", @"S"];
[iosArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"%@", obj);
if ([obj isEqualToString:@"E"]) {
*stop = YES;
}
}];
参数说明:
obj表示数组中的元素;
idx表示元素的下标;
stop可以控制遍历何时停止,在需要停止时令stop = YES即可(不要忘记前面的)。
这种方法清晰明了,数组元素,下标都可直接获取,就连何时停止都很容易实现,break都可以退休了,遍历字典也同样简单。
遍历字典
NSDictionary *dict = @{@"1":@"11", @"2":@"22", @"3":@"33"};
[dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
NSLog(@"%@", obj);
if ([obj isEqualToString:@"22"]) {
*stop = YES;
}
}];
你没有看错,就是这么简单,block直接把字典的key和value都给我们了,再也不用书写直白而繁琐的代码了。
注意:
若已知collection里对象的数据类型,可以修改块签名,知道对象的精确类型后,编译器就可以检测开发者是否调用了该对象所不具有的方法,并在发现问题时报错。
NSDictionary *dict = @{@"1":@"11", @"2":@"22", @"3":@"33"};
[dict enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL * _Nonnull stop) {
NSLog(@"%@", obj);
if ([obj isEqualToString:@"22"]) {
*stop = YES;
}
}];
如代码,直接把key和value的类型修改成NSString类型。
反向遍历
反向遍历也同样方便,调用另外一个方法即可:
NSArray *iosArray = @[@"L", @"O", @"V", @"E", @"I", @"O", @"S"];
[iosArray enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSString *obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"%@", obj);
if ([obj isEqualToString:@"E"]) {
*stop = YES;
}
}];
这个方法相对于正向遍历多了一个枚举类型的参数NSEnumerationReverse,打开这个选项就可以反向遍历了。
并发遍历
顺着这个枚举类型的参数,就会引出块枚举的另一大优势:并发遍历,参数是:NSEnumerationConcurrent,也就是可以同时遍历collection中的几个元素,具体数量根据系统资源而定。这样会充分利用系统资源,高效快捷的完成collection的遍历,系统底层会通过GCD来处理并发事宜,开发者不需要担心内存和线程,其他方式若要实现高效的并发遍历十分有难度。通过块枚举遍历,改变collection并不会引起崩溃,代码如下:
NSArray *iosArray = @[@"L", @"O", @"V", @"E", @"I", @"O", @"S"];
NSMutableArray *iosMutableArray = [NSMutableArray arrayWithArray:iosArray];
[iosMutableArray enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(NSString *obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj = [NSString stringWithFormat:@"_%@", obj];
[iosMutableArray replaceObjectAtIndex:idx withObject:obj];
NSLog(@"%@", obj);
if ([obj isEqualToString:@"_I"]) {
*stop = YES;
}
}];
优缺点总结
优点:
- 可以完美实现for循环的所有功能;
- 可以方便获取集合中的每一项元素;
- 提供了循环遍历的参数,NSEnumerationReverse用来实现倒序循环。NSEnumerationConcurrent用来实现并发遍历,两个参数可以同时使用;
- 这种循环方式效率高,能够提升程序性能,开发者可以专注于业务逻辑,而不必担心内存和线程的问题;
- 当开启NSEnumerationConcurrent选项时,可以实现for循环和快速遍历无法轻易实现的并发循环功能,系统底层会通过GCD处理并发事宜,这样可以充分利用系统和硬件资源,达到最优的遍历效果;
- 可以修改块签名,当我们已经明确集合中的元素类型时,可以把默认的签名id类型修改成已知类型,比如常见的NSString,这样既可以节省系统资源开销,也可以防止误向对象发送不存在的方法是引起的崩溃。
缺点:
- 很多开发者不知道这种遍历方式;
- 这里使用了block,需要注意在block里容易引起的保留环问题,比如使用self调用方法时,把self转化成若引用即可打破保留环。如:
weak `typeof(self)weakSelf = self
或者
__weak MyController *weakSelf = self;
在block里使用weakSelf即可。
注意
使用基于块的遍历时是可以修改遍历的元素的,不会导致崩溃,但是如果要删除遍历的元素会导致后面的元素无法遍历而崩溃,解决办法有2种:
一种是复制一份原集合的副本,对副本进行操作,找出所要操作的元素后再处理原集合;
使用反向遍历,反向遍历删除元素后不会导致崩溃。
5. 排序
数组是有序容器,因此集合中只有数组才能排序。
不可变数组排序
方法1
NSArray *arr = @[@"aa",@"rr",@"pp",@"hh",@"xx",@"vv"];
//用系统的方法进行排序,系统缺少两个元素比较的方法.
//selector方法选择器.
NSArray *sortArr = [arr sortedArrayUsingSelector:@selector(compare:)];
NSLog(@"%@",sortArr);
方法2:block块语法
[arr sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
return [(NSString *)obj1 compare:(NSString *)obj2];
}];
NSLog(@"%@",arr);
}
可变数组排序
方法1
NSMutableArray *arr = [@[@54 ,@33,@12,@23,@65] mutableCopy];
[arr sortUsingSelector:@selector(compare:)];//compare数组中两个元素比较的方法
NSLog(@"%@",arr);
方法2
[arr sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
return [(NSNumber *)obj1 compare:(NSNumber *)obj2];
}];
NSLog(@"%@",arr);
}
作者:Ryan___
链接:https://www.jianshu.com/p/0e95b992e061
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
iOS开发经验(6)-Transform
目录(transform)
- 基础及矩阵概念
- 2D仿射
- 3D仿射
1. 基础及矩阵概念
仿射变换.png
- 在iOS的动画效果中,变换是很常见的,包括仿射变换和3D变换等。变换的终极原理就是矩阵的乘法运算,3D变换涉及到三维透视投影的一些原理知识。
transform
基本概念transform
在矩阵变换的层面上改变视图的显示效果,完成旋转、形变、平移等等操作。在它被修改的同时,视图的frame
也会被真实改变。有两个数据类型用来表示transform
,分别是CGAffineTransform
和CATransform3D
。前者作用于UIView
,后者为layer
层次的变换类型。基于后者可以实现更加强大的功能。UIView
可以通过设置transform
属性做变换,但实际上它只是封装了内部图层的变换。CALayer
同样也有一个transform
属性,但它的类型是CATransform3D
,而不是CGAffineTransform
。CALayer
对应于UIView
的transform
属性叫做affineTransform
,- 矩阵概念
矩阵 × 坐标:当你用矩阵乘以一个坐标时,它们的乘积就是一个变换后的新坐标。
齐次坐标:为了把二维图形的变化统一在一个坐标系里,引入了齐次坐标的概念,即把一个图形用一个三维矩阵表示,其中第三列总是(0,0,1)
(为了能让矩阵做乘法),用来作为坐标系的标准。所以所有的变化都由前两列完成。
为了能让矩阵做乘法:左边矩阵的列数一定要和右边矩阵的行数个数相同,所以要给矩阵填充一些标志值,使得既可以让矩阵做乘法,又不改变运算结果,并且没必要存储这些添加的值,因为它们的值不会发生变化,但是要用来做运算。因此,通常会用3×3(而不是2×3)
的矩阵来做二维变换.
当对图层应用变换矩阵,图层矩形内的每一个点都被相应地做变换,从而形成一个新的四边形的形状。CGAffineTransform
中的“仿射”的意思是无论变换矩阵用什么值,图层中平行的两条线在变换之后任然保持平行,CGAffineTransform
可以做出任意符合上述标注的变换
单位矩阵
CGAffineTransformIdentity
-
仿射变换的原理和计算
仿射变化原理是数学中的矩阵原理(线性代数-矩阵分析),要弄明白仿射矩阵对作用点的影响,还得先看看矩阵的乘法怎么计算。基础-矩阵的乘法
计算规则:- 不符合交换律(A和B是矩阵,AB不一定等于BA)
- 当矩阵A的列数等于矩阵B的行数是,才可以计算
- 计算的结果矩阵C的行数等于A的行数,列数等于B的列数(如A是m×n矩阵和B是n×p矩阵,它们的乘积C是一个m×p矩阵 )
- 结果矩阵C的第 i 行第 j 列的元素Cij 等于矩阵A的第 i 行的元素与矩阵B的第 j 列对应元素乘积之和
矩阵A =
[1 1]
[2 0]
矩阵B =
[0 2 3]
[1 1 2]
计算过程:
矩阵C = A * B =
[(1 * 0 + 1 * 1) (1 * 2 + 1 * 1) (1 * 3 + 1 * 2)]
[(2 * 0 + 0 * 1) (2 * 2 + 0 * 1) (2 * 3 + 0 * 2)]
矩阵C =
[1 3 5]
[0 4 6]
- 仿射变换的矩阵计算
仿射计算中,(以二维坐标为例,坐标点为x,y)我们设我们的坐标点矩阵为
坐标点矩阵
A = [x y 1]
仿射变换基础矩阵为:
B =
[a b 0]
[c d 0]
[tx ty 1]
根据矩阵计算规则我们知道A x B的结果是一个1行3列的矩阵,设A x B得到的新矩阵C ,那么C的矩阵应该为
C = [(a*x+c*y+tx) (b*x+d*y+ty) 1]
设C为 = [x' y' 1] , 那么可以得到
x' = a*x + c*y + tx
y' = b*x + d*y + ty
2. 2D仿射
- 仿射是什么
仿射变换可以理解为- 对坐标进行放缩,旋转,平移后取得新坐标的值。
- 经过对坐标轴的放缩,旋转,平移后原坐标在在新坐标领域中的值。
- transform是一个矩阵,变换后的点坐标等于之前的点坐标乘以矩阵
当操纵一个变换的时候,初始生成一个什么都不做的变换很重要--也就是创建一个CGAffineTransform
类型的空值,矩阵论中称作单位矩阵
,Core Graphics同样也提供了一个方便的常量:CGAffineTransformIdentity
iOS封装了几个好用的CGAffineTrans的API去实现仿射变换的效果
注意:带Make的,起点固定,每次控制的事件只针对起点。不带Make的:为一个变换再加上平移,针对上一个位置,不针对起点。
//位移仿射
CGAffineTransformMakeTranslation
CGAffineTransformTranslate
//旋转仿射
CGAffineTransformMakeRotation
CGAffineTransformRotate
//缩放仿射
CGAffineTransformMakeScale
CGAffineTransformScale
//叠加仿射效果
CGAffineTransformConcat
//仿射矩阵方法,可以直接做效果叠加
CGAffineTransformMake (sx,shx,shy,sy,tx,ty)
带make的方法是直接返回一个仿射变换效果,不带make方法是用来给已有的仿射效果叠加效果
类似于CGAffineTransformConcat方法的作用
CATransform3D也对应一组相似的api,这个后面在研究到它的时候仔细说说.
在大多数情况下用这几个封装好的仿射方法基本就能实现大多数的需求。但是既然是研究仿射,可以看CGAffineTransform最基础的那个仿射方法的使用,能理解这个方法的使用,基本上就知道仿射是个什么意思了。
CGAffineTransformMake(CGFloat a,CGFloat b,CGFloat c,CGFloat d,CGFloat tx,CGFloat ty)
这个方法的6个参数可以拼出一个矩阵
//仿射矩阵
[a b 0]
[c d 0]
[tx ty 1]
仿射变化的定义有点复杂,我自己理解就是: 点p(以二维坐标为例)通过仿射矩阵C 后变成新的点p' 。
以上面的缩放仿射变化CGAffineTransformScale为例。就是view中每一个点p通过矩阵C后,view中的每一个新的点p'组成的新的图形,相比之前的view有了缩放的效果。
这步很关键。根据这个公式,那么仿射矩阵就可以分成5种分别对应:平移(Translation),缩放(Scale),翻转(Flip),旋转(Rotation),剪切(Shear)
平移演化
设a,d=1 c,b = 0 那么
x' = a*x + c*y + tx
y' = b*x + d*y + ty
就变成了
x' = x + tx
y' = y + ty
这个不就是新的点P'(x' y') 是根据原来的P在x和y分别添加了一个常量就可以得到了嘛? 所以这个就是仿射中的平移效果了。把a,b,c,d带入仿射的基础矩阵,就得了仿射的位移矩阵
//仿射基础矩阵
[a b 0]
[c d 0]
[tx ty 1]
//仿射位移矩阵
[1 0 0]
[0 1 0]
[tx ty 1]
再来看看代码
向右移动300的仿射效果
CGAffineTransform translate = CGAffineTransformMakeTranslation(300, 0)
使用仿射基础方法
CGAffineTransform translate = CGAffineTransformMake(1,0,0,1,300,0)
缩放演化
x' = a*x + c*y + tx
y' = b*x + d*y + ty
设 c,b,tx,ty = 0 ,得到
x' = a*x
y' = d*y
新坐标(x',y')和原坐标是倍数关系,这个就可以用来做缩放效果了。所以得到缩放矩阵
//仿射缩放矩阵
[a 0 0]
[0 d 0]
[0 0 1]
//也可以写成
[sx 0 0]
[0 sy 0]
[0 0 1]
代码
//x和y都放大1倍
CGAffineTransform scaleAffine = CGAffineTransformMakeScale(2, 2)
//使用仿射基础方法
CGAffineTransform scaleAffine = CGAffineTransformMake(2,0,0,2,0,0)
剪切演化
x' = a*x + c*y + tx
y' = b*x + d*y + ty
设 a,d = 1 tx,ty = 0 ,得到
x' = x + cy
y' = y + bx
x和y的变化总是相互关联,这个就是剪切(Shear)
//仿射剪切矩阵
[1 b 0]
[c 1 0]
[0 0 1]
//也可以写成
[1 shx 0]
[shy 1 0]
[0 0 1]
代码
//使用仿射基础方法CGAffineTransformMake,设置x和y都为0.5的斜切
CGAffineTransform shearAffine = CGAffineTransformMake(1,0.5,0.5,1,0,0)
剪切效果只能用CGAffineTransformMake实现,但是通过CATransform3DMakeRotation可以有类似的效果
旋转演化
//仿射旋转矩阵
[cosa sina 0]
[-sina cosa 0]
[0 0 1]
代码
CGAffineTransform rotation = CGAffineTransformMake(CGFloat( cos(M_PI_4) ), CGFloat( sin(M_PI_4) ), -CGFloat( sin(M_PI_4) ), CGFloat( cos(M_PI_4) ), 0, 0)
翻转演化
我不知道使用CGAffineTransformMake()如何设置矩阵可以实现翻转效果。我阅读的所有Flip效果都是使用CATransform3D实现的,代码如下
//Flip仿射,要是有3D去实现
CATransform3D flip = CATransform3DMakeRotation(angle, x, y, z)
//示例
CATransform3D flipX = CATransform3DMakeRotation(CGFloat(M_PI), 1, 0, 0)
CATransform3D flipY = CATransform3DMakeRotation(CGFloat(M_PI), 0, 1, 0)
CATransform3D flipZ = CATransform3DMakeRotation(CGFloat(M_PI), 0, 0, 1)
总结一下CATransform3DMakeRotation方法的6个参数
在不考虑旋转时,CATransform3DMakeRotation6个参数可以写成
//sx,sy:缩放因子
//shx,shy:斜切因子
//tx,ty:移动因子
CGAffineTransformMake (sx,shx,shy,sy,tx,ty)
这个是一个初始化矩阵,带入矩阵算法计算后的结构会得到
x'=x , y'=y
它的作用是清除之前对矩阵设置的仿射效果,或者用来初始化一个原始无效果的仿射矩阵
矩阵初始值。[ 1 0 0 1 0 0 ]
[ 1 0 0 ]
[ 0 1 0 ]
[ 0 0 1 ]
方法:
//用来连接两个变换效果并返回。返回的t = t1 * t2
CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2)
//矩阵初始值。[ 1 0 0 1 0 0 ]
CGAffineTransformIdentity
//检查是否有做过仿射效果
CGAffineTransformIsIdentity(transform)
//检查2个仿射效果是否相同
CGAffineTransformEqualToTransform(transform1,transform2)
//仿射效果反转(反效果,比如原来扩大,就变成缩小)
CGAffineTransformInvert(transform)
- 应用
放射矩阵一个常用的情形就是根据用户的手势来相应的改变视图的变换UIPanGestureRecognizer
对应位移UIPinchGestureRecognizer
对应缩放UIRotationGestureRecognizer
对应旋转
通常如果需要看到实时的手指移动视图就相应的变换的技巧就是,每次接收到对应的gesture
时间就相应的改变view的transform
,然后吧这个gesture
对应的translation
、scale
、rotation
置为初始值。
3. 3D仿射
- 三维坐标系:视角垂直与屏幕而言,x轴向右,y轴向下,z轴垂直屏幕向外。和
UIView
严格的二维坐标系不同,CALayer存在于一个三维空间当中。CALayer
还有另外两个属性,zPosition
和anchorPoint
最实用的功能就是改变图层的显示顺序。
self.greenView.layer.zPosition=1.0f;
- 锚点 anchorPoint
图层的锚点anchorPoint:是一个CGPoint值,x,y取值范围(0~1),默认为(0.5,0.5) 对于图层本身而言,顾名思义,锚点就用来定位图层的点。锚点有两个职能:
(1)与position一同确定图层相对于父图层的位置;
(2)通过设置该属性来实现视图围绕不同位置旋转。记得在视图添加到父视图之后在进行设置,因为frame也是相对于锚点的。
作为图层旋转、平移、缩放的中心
self.menuViewController.view.layer.anchorPoint = CGPointMake(1.0,0);
当图层发生变换时,这个点永远位于图层变换之前anchorPoint的位置。当改变一个图层的position,你也改变了它的锚点,做3D变换的时候要时刻记住这一点,当你视图通过调整m34来让它更加有3D效果,应该首先把它放置于屏幕中央,然后通过平移来把它移动到指定位置(而不是直接改变它的position),这样所有的3D图层都共享一个锚点。
- 关于锚点(anchorPoint)与position
- position是决定了layer显示在父控件的哪个位置,默认position是该layer的中心点.
- anchorPoint决定了layer自身显示在position的哪个位置.
- position与锚点的默认点为中心点
- position总是与锚点显示在同一位置
- layer是否发生偏移有锚点决定
- 3D仿射矩阵
3D仿射矩阵类似于2D仿射,3D仿射也有一个基础矩阵,并且比2D的多一个维度
CGAffineTransform transform = CGAffineTransformIdentity;
结果:(CGAffineTransform) transform = (a = 1, b = 0, c = 0, d = 1, tx = 0, ty = 0)
[m11 m12 m13 m14]
[m21 m22 m23 m24]
[m31 m32 m33 m34]
[m41 m42 m43 m44]
矩阵的计算过程和2D类似,最后也能得到矩阵中每个位置的值对3D仿射效果的作用。我直接把结果贴出来。
平移因子: m41(x位置) m42(y位置) m43(z位置)
缩放因子: m11(x位置) m22(y位置)
切变因子: m21(x位置) m12(y位置)
旋转因子: m13(x位置) m31(y位置)
透视因子: m34(有旋转才能看出效果)
m34的默认值是0,我们可以通过设置m34为(-1.0 / d)来应用透视效果,d代表了视角相机和屏幕之间的距离,以像素为单位,那应该如何计算这个距离呢?大概估算一个就行了。
因为视角相机实际上并不存在,所以可以根据屏幕上的显示效果自由决定它放置的位置。通常500-1000就已经很好了,但对于特定的图层有时候更小后者更大的值会看起来更舒服,减少距离的值会增强透视效果,所以一个非常微小的值会让它看起来更加失真,然而一个非常大的值会让它基本失去透视效果
3D仿射常用的方法
//位移3D仿射
CATransform3DMakeTranslation
CATransform3DTranslation
//旋转3D仿射:x-y-z轴的有个确定的范围(介于-1 和+1之间)
CATransform3DMakeRotation
CATransform3DRotation
angle 是所需要变化的角度
x y z 设置为零表示不围绕该轴做旋转,如果设置为1,则绕该轴做旋转。
//缩放3D仿射
CATransform3DMakeScale
CATransform3DScale
//叠加3D仿射效果
CATransform3DConcat
//仿射基础3D方法,可以直接做效果叠加
CGAffineTransformMake (sx,shx,shy,sy,tx,ty)
矩阵初始值:[ 1 0 0 0 1 0 0 0 1 0 0 0]
这个是一个初始化矩阵,带入矩阵算法计算后的结构会得到
x'=x , y'=y , z'=z
它的作用是清除之前对矩阵设置的仿射效果,或者用来初始化一个原始无效果的仿射矩阵
矩阵初始值:
[ 1 0 0 0 ]
[ 0 1 0 0 ]
[ 0 0 1 0 ]
[ 0 0 0 1 ]
//矩阵初始值。[ 1 0 0 0 1 0 0 0 1 0 0 0]
CATransform3DIdentity
//检查是否有做过仿射3D效果
CATransform3DIsIdentity(transform)
//检查是否是一个仿射3D效果
CATransform3DIsAffine(transform)
//检查2个3D仿射效果是否相同
CATransform3DEqualToTransform(transform1,transform2)
//3D仿射效果反转(反效果,比如原来扩大,就变成缩小)
CATransform3DInvert(transform)
//2D仿射转换3D仿射
CATransform3DGetAffineTransform(transform)
CATransform3DMakeAffineTransform(transform)
CATransform3D与CGAffineTransform的转换
//将一个CGAffinrTransform转化为CATransform3DCATransform3D
CATransform3DMakeAffineTransform (CGAffineTransform m);
//判断一个CATransform3D是否可以转换为CAAffineTransformbool
CATransform3DIsAffine (CATransform3D t);
//将CATransform3D转换为CGAffineTransformCGAffineTransform
CATransform3DGetAffineTransform (CATransform3D t);
当我们改变过一个view.transform
属性或者view.layer.transform
的时候需要恢复默认状态的话,记得先把他 们重置为:
view.transform = CGAffineTransformIdentity;
view.layer.transform = CATransform3DIdentity;
CALayer
的transform
属性是是个CATransform3D
类型的数据,默认值为CATransform3DIdentity
。需要注意的是,CALayer
是有隐式动画的,如果你想关掉隐式动画,用
[CATransaction setDisableActions:YES];
另外,要特地说明一下矩阵中的一个参数m34,m34影响透视效果.当然,z方向上得有变化才会有透视效果,数值越大,透视效果越明显.正值/负值都有意义,导致透视方向的不同。d越大,效果越不明显,d越小,效果越明显甚至导致失真。d的一个推荐的值是500-1000之间。
- 透视效果
#define RADIANS_TO_DEGREES(x) ((x)/M_PI*180.0)
CATransform3D transform = CATransform3DIdentity;
// 透视效果
transform.m34 = 0.0005;
transform = CATransform3DRotate(transform,(M_PI/180*40), 0, 1, 0);
[layer setTransform:transform];
注意我们使用的旋转常量是`M_PI_4`,而不是你想象的`45`,因为iOS的变换函数使用弧度而不是角度作为单位。
弧度用数学常量`pi`的倍数表示,一个`pi`代表`180`度,所以四分之一的`pi`就是`45`度。
第二行一定要写在第三行的前面;m34:透视效果m34= -1/M,M越小,透视效果越明显,必须在有旋转效果的前提下,才会看到透视效果。
- 合并两个CATransform3D(CATransform3DConcat)
CATransform3D CATransform3DConcat ( CATransform3D a, CATransform3D b );
- CATransform3DInvert效果反转(反效果,比如原来扩大,就变成缩小)
CATransform3D CATransform3DInvert ( CATransform3D t );
- CATransform3D与CGAffineTransform的转换
//将一个CGAffinrTransform转化为CATransform3DCATransform3D
CATransform3DMakeAffineTransform (CGAffineTransform m);
//判断一个CATransform3D是否可以转换为CAAffineTransformbool
CATransform3DIsAffine (CATransform3D t);
//将CATransform3D转换为CGAffineTransformCGAffineTransform
CATransform3DGetAffineTransform (CATransform3D t);
- 减轻锯齿-
shouldRasterize
属性的默认值是NO
,可将其设置为YES
来减轻锯齿的效果。记得动画结束时将其设置为NO
。
self.menuViewController.view.layer.shouldRasterize = NO;
- content
设置CALayer
的contents
属性为一个image
来设置图片,设置contentGravity
来指定图层内容的拉伸方式。iOS CATransform3D
,旋转之后出现的锯齿处理方法
layer.shouldRasterize = YES;
作者:Ryan___
链接:https://www.jianshu.com/p/ba3b5fd1f80b
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。