当前位置:首页 > 实时新闻 > 正文

Core Animation 第四章 视觉效果

摘要: CoreAnimation第四章视觉效果最佳答案53678位专家为你答疑解惑CoreAnimation第四章视觉效果往期回顾:序...

Core Animation 第四章 视觉效果

最佳答案 53678位专家为你答疑解惑

Core Animation 第四章 视觉效果

往期回顾:序章第一章 - 图层树第二章 - 寄宿图第三章 - 图层几何项目中使用的代码

在这一章我们主要会讨论一些通过CALayer属性实现的视觉效果。

圆角

CALayer有一个叫做cornerRadius的属性,他可以帮助我们不借助PS等工具轻松的搞定圆角矩形,这个属性调整的是图层角的曲率或者说是圆角半径,默认为0,也就是直角,而且曲率值只会影响背景颜色,而不会影响背景图片或者子图层。不过将masksToBounds为YES的话,图层里面所有的东西都会被截取。下面来做一个简单的例子。

两个白色的大视图,里面都包含了一个红色的小视图.png在书中这里提到使用IB(Interface Builder)构建视图的时候IB编辑界面会自动剪裁掉超出子视图的部分,不过这个现象在新的XCode中已经不会出现了。

然后我们设置两个白色视图的圆角半径为 20,并且对第二个白色视图设置masksToBounds为YES,来看一下效果。

@interface ViewController ()@property (weak, nonatomic) IBOutlet UIView *layerView1;@property (weak, nonatomic) IBOutlet UIView *layerView2;@end@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];        //设置圆角半径    self.layerView1.layer.cornerRadius=20.0f;    self.layerView2.layer.cornerRadius=20.0f;        //设置自动剪裁    self.layerView2.layer.masksToBounds=YES;}@end
设置过圆角后的两个白色视图.png

可以看到两个白色视图都表现出了圆角,但是只有第二个设置了masksToBounds的白色视图剪裁掉了子视图。

图层边框

CALayer另外连个常用的属性就是borderWidthborderColor,两个共同定义了图层边缘的绘制样式,这条线(stroke)沿着图层的bounds绘制,同时也包含图层的角。其中 borderWidth 默认为0, borderColor 默认为黑色。

borderColorCGColorRef类型,由于他不是Cocoa的内置对象,所以即便CGColorRef的属性是强引用也只能通过assign关键字来声明边框是绘制在图层内部的,而且在所有的子图层之前。

下面我们来为白色视图添加边框

- (void)viewDidLoad {    [super viewDidLoad];        //设置圆角半径    self.layerView1.layer.cornerRadius=20.0f;    self.layerView2.layer.cornerRadius=20.0f;        //设置边框    self.layerView1.layer.borderWidth=5.0f;    self.layerView2.layer.borderWidth=5.0f;        //设置自动剪裁    self.layerView2.layer.masksToBounds=YES;    }
为图层添加边框.png可以看到边框并不会计算自图层的位置和形状而只是沿着图层的边界来绘制。边框并不会根据图层里面的内容变化.png阴影

阴影是以前iOS中十分常用的一种设计,用来突出图层的优先级,或者装饰图层,不过随着扁平化的盛行,阴影已经慢慢的被人们所抛弃了。shadowOpacity属性负责控制阴影的显示,值在0.0 ~ 1.0之间,0.0为阴影不可见,1.0为完全不透明,此外CALayer还有三个属性来协助表现阴影的样子:shadowColor, shadowOffset, shadowRadiusshadowColor 控制阴影的颜色,也是一个CGColorRef类型的属性,shadowOffset控制阴影的位置,是一个CGSize类型的值,默认为{0, -3}即阴影默认Y轴向上偏移三个单位。关于这个默认值书中的解释为:

尽管Core Animation是从图层套装演变而来(可以 认为是为iOS创建的私有动画框架),但是呢,它却是在Mac OS上面世的,前面有 提到,二者的Y轴是颠倒的。这就导致了默认的3个点位移的阴影是向上的。在Mac 上, shadowOffset 的默认阴影向下的,这样你就能理解为什么iOS上的阴影方向是向上的了。

shadowRadius用来控制阴影的模糊程度,值越大,阴影越模糊。

低shadowRadius 与 高shadowRadius阴影剪裁

与边框不容,阴影是可以继承图层内容的形状的,包括子视图和寄宿图的形状。

阴影会自动计算寄宿图的形状

结合上面说的内容会出现一个问题,那就是我们设置了阴影的同时打开了masksToBounds

- (void)viewDidLoad {    [super viewDidLoad];        //设置圆角半径    self.layerView1.layer.cornerRadius=20.0f;    self.layerView2.layer.cornerRadius=20.0f;        //设置边框    self.layerView1.layer.borderWidth=5.0f;    self.layerView2.layer.borderWidth=5.0f;        //设置剪裁    self.layerView2.layer.masksToBounds=YES;        //设置阴影    self.layerView1.layer.shadowOpacity=0.8f;    self.layerView2.layer.shadowOpacity=0.8f;        self.layerView1.layer.shadowOffset=CGSizeMake(0, 3);    self.layerView2.layer.shadowOffset=CGSizeMake(0, 3);        self.layerView1.layer.shadowRadius=5.0f;    self.layerView2.layer.shadowRadius=5.0f;}
阴影被masksToBounds剪裁掉了

如果我们既需要masksToBounds同时也想要一个印象效果的话,可以通过一个比较tricky的方法来实现,也就是在当前视图的下面添加一个同样大小的透明视图,使用它来显示阴影。

当前图层树的结构
@interface ViewController ()@property (weak, nonatomic) IBOutlet UIView *layerView1;@property (weak, nonatomic) IBOutlet UIView *layerView2;@property (weak, nonatomic) IBOutlet UIView *shadowView;@end@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];        //设置圆角半径    self.layerView1.layer.cornerRadius=20.0f;    self.layerView2.layer.cornerRadius=20.0f;        //设置边框    self.layerView1.layer.borderWidth=5.0f;    self.layerView2.layer.borderWidth=5.0f;        //设置剪裁    self.layerView2.layer.masksToBounds=YES;        //设置阴影    self.shadowView.layer.shadowOpacity=0.8f;    self.layerView2.layer.shadowOpacity=0.8f;        self.shadowView.layer.shadowOffset=CGSizeMake(0, 3);    self.layerView2.layer.shadowOffset=CGSizeMake(0, 3);        self.shadowView.layer.shadowRadius=5.0f;    self.layerView2.layer.shadowRadius=5.0f;}@end
同时拥有masksToBounds和阴影shadowPath

上面已经提到过阴影会自动计算自图层和寄宿图的形状,但是当自图层很多的时候,这种计算必然会十分消耗性能,所以如果你知道当前图层需要的阴影的形状,可以使用shadowPath传入一个CGPathRef类型的值来进行优化。

- (void)viewDidLoad {    [super viewDidLoad];        self.layerView1.layer.contents=(__bridge id _Nullable)([UIImage imageNamed:@"pica"].CGImage);    self.layerView2.layer.contents=(__bridge id _Nullable)([UIImage imageNamed:@"pica"].CGImage);        //显示阴影    self.layerView1.layer.shadowOpacity=0.8f;    self.layerView2.layer.shadowOpacity=0.8f;        //绘制一个矩形阴影    CGMutablePathRef squarePath=CGPathCreateMutable();    CGPathAddRect(squarePath, NULL, self.layerView1.bounds);    self.layerView1.layer.shadowPath=squarePath;    CGPathRelease(squarePath);    //绘制一个圆形阴影    CGMutablePathRef circlePath=CGPathCreateMutable();    CGPathAddEllipseInRect(circlePath, NULL, self.layerView2.bounds);    self.layerView2.layer.shadowPath=circlePath;    CGPathRelease(circlePath);}
使用shadowPath自己绘制阴影形状这里使用了CGMutablePathRef来进行绘制,由于CGMutablePathRef并不是一个Cocoa对象,所以需要使用CGPathRelease进行手动释放,如果使用UIKit中提供的UIBezierPath进行操作则不需要手动释放。图层蒙版

有些时候我们需要一个不规则的容器来展现我们需要的内容,比如,你想展示一个有星形框架的图片,又或者想让一些古卷文字慢慢渐变成背景色,而不是一个突兀的边界。CALayer中有一个属性叫做mask。这个属性本身也是CALayer类型,类似于子图层,与子图层不同的是mask定义了父图层可以显示的区域,可以脑补一下 神奇宝贝 里面 我是谁 的那个过场。效果如下:

使用mask制作一个红色的皮卡丘.png
@interface MaskViewController ()@property (weak, nonatomic) IBOutlet UIView *layerView1;@property (weak, nonatomic) IBOutlet UIView *layerView2;@property (weak, nonatomic) IBOutlet UIView *layerView3;@end@implementation MaskViewController- (void)viewDidLoad {    [super viewDidLoad];    UIImage *redImage=[UIImage imageNamed:@"redImage"];    UIImage *pica=[UIImage imageNamed:@"pica"];    self.layerView1.layer.contents=(__bridge id)redImage.CGImage;    self.layerView2.layer.contents=(__bridge id)pica.CGImage;        //设置蒙版    self.layerView3.layer.contents=self.layerView1.layer.contents;    CALayer *maskLayer=[CALayer layer];    maskLayer.frame=self.layerView3.bounds;    maskLayer.contents=self.layerView2.layer.contents;    self.layerView3.layer.mask=maskLayer;}@end

而且蒙版真正强大的地方在于蒙版是可以通过代码或者动画来生成的,而不是局限于静态的图片。

拉伸过滤

这块我之前没有什么积累,就都直接引用作者的原话好了,不过代码示例我会为大家准备好的。接下来要说的两个属性分别是minificationFiltermagnificationFilter。总得来讲,当我们视图显示一个图片的时候,都应该正确地显示这个图片(意即:以 正确的比例和正确的1:1像素显示在屏幕上)。原因如下:

能够显示最好的画质,像素既没有被压缩也没有被拉伸。能更好的使用内存,因为这就是所有你要存储的东西。最好的性能表现,CPU不需要为此额外的计算。

但很多时候图片的大小并不能很好的和视图的大小保持一致,这个时候有一种叫做拉伸过滤的算法就起到作用了。它作用于原图的像素上并根据需要生成新的像素显示在屏幕上。CALayer 为此提供了三种拉伸过滤方法,他们是:

kCAFilterLinearkCAFilterNearestkCAFilterTrilinearminification(缩小图片)和magnification(放大图片)默认的过滤器都是 kCAFilterLinear ,这个过滤器采用双线性滤波算法,它在大多数情况下都表现良好。双线性滤波算法通过对多个像素取样最终生成新的值,得到一个平滑的表现不错的拉伸。但是当放大倍数比较大的时候图片就模糊不清了。kCAFilterTrilinearkCAFilterLinear 非常相似,大部分情况下二者都看不出来有什么差别。但是,较双线性滤波算法而言,三线性滤波算法存储了多个大小情况下的图片(也叫多重贴图),并三维取样,同时结合大图和小图的存储进而得到最后的结果。这个方法的好处在于算法能够从一系列已经接近于最终大小的图片中得到想要的结果,也就是说不要对很多像素同步取样。这不仅提高了性能,也避免了小概率因舍入错误引起的取样失灵的问题。对于大图来说,双线性滤波和三线性滤波表现得更出色

kCAFilterNearest 是一种比较武断的方法。从名字不难看出,这个算法(也叫最近过滤)就是取样最近的单像素点而不管其他的颜色。这样做非常快,也不会使图片模糊。但是,最明显的效果就是,会使得压缩图片更糟,图片放大之后也显得块状或是马赛克严重。

对于没有斜线的小图来说,最近过滤算法要好很多

总的来说,对于比较小的图或者是差异特别明显,极少斜线的大图,最近过滤算法会保留这种差异明显的特质以呈现更好的结果。但是对于大多数的图尤其是有很多斜线或是曲线轮廓的图片来说,最近过滤算法会导致更差的结果。换句话说,线性过滤保留了形状,最近过滤则保留了像素的差异。

下面来做一个简单的小时钟,

@interface FilterViewController ()@property (strong, nonatomic) IBOutletCollection(UIView) NSArray *digitViews;@property (weak, nonatomic) NSTimer *timer;@end@implementation FilterViewController- (void)viewDidLoad {    [super viewDidLoad]; //get spritesheet image    UIImage *digits=[UIImage imageNamed:@"numbers"];        //set up digit views    for (UIView *view in self.digitViews) {        //set contents        view.layer.contents=(__bridge id)digits.CGImage;        view.layer.contentsRect=CGRectMake(0, 0, 0.1, 1.0);        view.layer.contentsGravity=kCAGravityResizeAspect;    }        //start timer    self.timer=[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tick) userInfo:nil repeats:YES];        //set initial clock time    [self tick];}- (void)setDigit:(NSInteger)digit forView:(UIView *)view{    //adjust contentsRect to select correct digit    view.layer.contentsRect=CGRectMake(digit * 0.1, 0, 0.1, 1.0);}- (void)tick{    //convert time to hours, minutes and seconds    NSCalendar *calendar=[[NSCalendar alloc] initWithCalendarIdentifier: NSCalendarIdentifierGregorian];    NSUInteger units=NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;    NSDateComponents *components=[calendar components:units fromDate:[NSDate date]];        //set hours    [self setDigit:components.hour / 10 forView:self.digitViews[0]];    [self setDigit:components.hour % 10 forView:self.digitViews[1]];        //set minutes    [self setDigit:components.minute / 10 forView:self.digitViews[2]];    [self setDigit:components.minute % 10 forView:self.digitViews[3]];        //set seconds    [self setDigit:components.second / 10 forView:self.digitViews[4]];    [self setDigit:components.second % 10 forView:self.digitViews[5]];}@end
一个模糊的时钟,由默认的kCAFilterLinear引起

作为修改,我们在for循环中加入以下代码

view.layer.magnificationFilter=kCAFilterNearest;
修改了magnificationFilter后的时钟组透明

UIView中用来处理透明度的属性叫alpha,CALayer中对应的属性为opacity,这两个属性都会对子层级产生影响,也就是说你给一个图层设置了opacity,他说有的子图层的opacity都会受到影响。下面的图片中是一个白色的视图内部有一个白色的Label,右边的视图被设置了50%的透明度。

右边子视图的label被显示了出来

这是由透明度的混合叠加造成的,当你显示一个50%透明度的图层时,图层的每个像素都会一半显示自己的颜色,另一半显示图层下面的颜色。这是正常的透明度的表现。但是如果图层包含一个同样显示50%透明的子图层时,你所看到的视图,50%来自子视图,25%来了图层本身的颜色,另外的25%则来自背景色。

书中提到了可以将info.plist中的UIViewGroupOpacity设置为YES来达到整个图层树保持相同透明度的效果,但是当前版本的XcodeUIViewGroupOpacity已经默认设置为了YES,所以想出现上图中的效果需要先将UIViewGroupOpacity设置为NOUIViewGroupOpacity的缺点在于他是一个整体配置,整个应用可能会受到不良影响。

除了UIViewGroupOpacity,另一个方法就是启用CALayershouldRasterize属性来组透明效果。为了启用shouldRasterize属性,我们设置了图层的rasterizationScale属性。默认情况下,所有图层拉伸都是1.0, 所以如果你使用了shouldRasterize属性,你就要确保你设置了rasterizationScale属性去匹配屏幕,以防止出现Retina屏幕像素化的问题。

修复后的Label
- (void)viewDidLoad {    [super viewDidLoad];    self.layerView.alpha=0.5f;    self.layerView.layer.shouldRasterize=YES;    self.layerView.layer.rasterizationScale=[UIScreen mainScreen].scale;}
总结

这一章介绍了一些可以通过代码应用到图层上的视觉效果,比如圆角,阴影和蒙板。我们也了解了拉伸过滤器和组透明。在第五章,『变换』中,我们将会研究图层变化和3D转换。

我劝你等等,第一批iPhone13出问题了

现在有多少机友拿到iPhone 13系列的,举个手让机哥瞧瞧。

这两天身边不少朋友都跟机哥说,今年13 Pro的远峰蓝,比起去年12 Pro的海蓝色,调得更加合口味。

机哥帮大家找来对比了一下:

(左:海蓝色;右:远峰蓝)

而13的粉色、星光色,也是非常受欢迎。

看来新配色,才是大家换手机的核心驱动力啊……

机哥今天撇了一眼,某些型号在苹果官网,都排队排到双十一去了。

比较喜感的是,机哥看到很多网友在微博、在知乎,纷纷刷起了玄学帖子:「据说发帖,会提前发货。」

微博玄学

知乎也玄学

看得出来,大家等新手机等得非常焦急。

但是,机哥要说但是了。

之前很多人都说「早买早享受」,实际上,机哥今年觉得晚买也不吃亏。

因为就在这两天,抢到iPhone 13第一批的网友,纷纷吐槽自己碰到了很多问题。

并且绝大部分,都是iOS 15.0软件上的bug。

所以说,也许晚拿到,反而会得到体验更加完善的新iPhone。

接下来,机哥就来汇总一波,目前搭载iOS 15.0的iPhone 13系列,可能会出现哪些bug。

我们拿到新iPhone后,第一件要做的事情,很可能是数据迁移。但没想到,今年的bug之旅,从这里开始。

数据迁移bug

不少网友吐槽,把旧手机的数据全都迁移到iPhone 13后,一点亮屏幕就震惊到了。

因为……桌面小组件全都变成白色了。

就像这个样子,白晃晃一片:

有网友觉得非常讽刺的是,苹果9月24日在“Apple支持”官方微博称:

迁移后,连主屏幕的排列都跟原先一模一样。

结果自己数据迁移到iPhone 13后,精心排列好的桌面小组件,都被还原了。

还好,机哥看到苹果的动作也很快,立马在官网发布了一份支持文件,确认了这个bug:

但是从公告可以看出,从iPhone 13系列到iPad 9、iPad mini 6,这次发售的全部新品,都中招了啊。

目前苹果给出的解决方法,是让大家去设置页面,重新加载一下小组件。

emmm,从解决办法可以看出,这就是一个iOS的系统bug嘛。

只是令机哥也没想到的是,iPhone 13在数据迁移方面,bug接踵而至。

这两天,苹果在官网又确认了一个bug:恢复备份后,Apple Music可能无法正常使用。

不过,目前已经紧急推送了一个400MB左右的系统更新,解决了这个问题。

So,机哥建议大家拿到新手机后,立即把系统升到最新版……

是不是没想到,今年新iPhone的新bug,是从数据迁移开始的呢?

也许,当我们正式开始用iPhone 13后,也会发现问题不少。

比如说,解锁。

在苹果开发布会前,很多爆料都说iPhone 13会新增Face ID口罩解锁。

结果开完发布会才知道,今年Face ID的变化,好像就只有刘海变小了。

So,对于经常戴口罩的机哥,就得继续依赖从iOS 14.5开始加入的「使用Apple Watch解锁」。

但这次,iPhone 13翻了个大车。

Apple Watch无法解锁iPhone 13

现在进到iPhone 13的系统解锁设置,会弹出一个框框说:「无法与Apple Watch通信」。

会不会是绑定的问题?要不试试解绑手表,再重新配对?

然后机哥就发现……于事无补。

再看看今天网上吐槽的情况,估计不是小面积的事情:

有网友咨询了苹果客服,得到的答复是等待系统更新。

好家伙,又是一个iOS的bug。

今年硬件部门做出十三香,愣是被软件部门搞出反向升级……真有你的。

而就在刚刚,苹果官网更新了,确认了这个问题的存在。

机哥希望它早点修复吧~

上面说到的,都是老功能出问题。实际上,这次iPhone 13新加入的某些功能,也有不同程度的翻车。

我们先从相机开始说起吧,毕竟苹果在发布会上介绍iPhone 13时,大半部分都在讲相机升级。

拍照出现马赛克

今天早上,微博热搜上有一条「iPhone 13拍照有马赛克」的讨论。

机哥还楞了两三秒,啥叫做拍照有马赛克啊?是我想的那种吗?

戳进去才发现,原来是有网友用他的iPhone 13 Pro Max拍照时,会出现不同程度的黑点。

这又是哪门子的bug,闻所未闻,见所未见啊。

机哥一路追寻这个图片的来源,终于发现这是小红书上的一位用户发的。

而且他为了验证不是P的,后来还重新录了一段实际操作的视频:

通过这个演示,机哥发现这个所谓的小黑点马赛克,刚拍完照时是没有的。戳进相册后,等个一两秒,才突然出现。

通常这个等待的过程,iPhone是在背后进行AI优化。所以机哥猜,现在出现这个问题,很可能是计算摄影的算法出了bug。

而在今天早上的微博讨论,拍照时出现马赛克的情形,似乎不止这一种。

像下面这位用户晒出来的图片,他的iPhone 13自拍之后,头发部分会出现诡异的颜色:

这种bug,还真的让人哭笑不得。

除了相机,这次iPhone 13 Pro系列最大的卖点之一,就是高刷新率的加入。

但这个周末,不少拿到新iPhone的网友发现,目前这个版本的iOS系统,在某些情况下,高刷会失效。

第三方App里120Hz高刷新率失效

比如,机哥看到国外一个App开发者,在对比iPhone 13 Pro和iPad Pro的120Hz高刷新率时,发现了一件神奇的事情。

他在iPhone 13 Pro上使用推特App时,发现除了滑动时的动画能达到120Hz,其他动画都被限制在60Hz。

由于是在同一个App里,所以体验会非常割裂。

但相对的是,在同样支持ProMotion动态刷新率 iPad Pro上,并没有限制,所有动画都以120Hz呈现。

按道理来说,同样的App,同样的系统底层,同样的场景,不应该会出现这种情况啊?难道又是bug?

紧接着,他发现不仅推特,而且大多数第三方App,动画都被限制在60Hz。

嗯?难道高刷了个寂寞?

今天,终于破案了。

苹果官方回应称:

所有第三方应用程序可以充分利用120Hz ProMotion刷新率,但开发者需要通过在应用程序的plist中添加一个条目,来声明他们的应用程序需要使用更高的帧率。关于所需的plist条目的文件,将很快提供给开发者。

害,所以是之前没有把相关的适配文件给App开发者呗。

像这种适配工作,往年苹果都是第一时间提醒开发者的……怎么今年就慢半拍?

结果机哥看到有不少博主称:不仅第三方App,就连iOS系统内很多二级菜单,也都是维持在60Hz。

好吧……说白了,就连苹果自己都还没优化好这iPhone 13 Pro的高刷新率。

所以今天苹果的回应里,还有这么一句:

还有一个错误影响了一些使用Core Animation(核心动画效果)构建的动画,将在即将到来的软件更新中修复。

一句话概括就是:等更新吧。

可以说,上面提到的iPhone 13目前存在的问题,大部分原因都是iOS 15.0没优化好。等后续系统更新,应该就能被解决了。

所以机哥才觉得,也许今年不是「早买早享受」,而是「晚买晚吃香」。

相比之下,跟iPhone 13同时发售的iPad mini 6,问题好像更大。

这两天,兴高采烈地网友们拿到全新的iPad mini,结果发现屏幕好像不太对劲。

iPad mini 6屏幕有果冻效应

有人说,在滑动的过程中,会察觉出来显示内容duang duang的。就像用手指戳果冻后,果冻会一弹一弹的。

也有用户说,自己的屏幕按压下去,会出现类似水波纹的情况。

甚至有上一代iPad mini 5的用户反映,原来mini 5也有这问题。

于是引起一场全网大测试,所有拿到新iPad mini 6的用户,都第一时间跑去测试屏幕。

前两天八弟不是才刚买了一台iPad mini 6么?机哥今天又借过来测试了一下。

好家伙,结果八弟这一台也中招了。

(慢动作镜头下拍摄)

有网友说,类似的现象,当年iPhone 7刚发售的时候也有,当时是品控的问题。

而在这次全网大测试的过程中,有人说碰到了,也有人没碰到。

So,目前还不能确定问题所在,我们还是等待苹果的进一步回应吧。

最后,机哥这里也要提醒大家,如果在苹果官网、Apple Store App、苹果直营店购买的iPhone 13、iPad mini 6,是支持14天内无理由退换货的。

所以一旦碰到问题,赶紧去找售后。

看来,今年等等党还是不会亏的。说不定晚点收到货,还会发现把该优化的都优化了呢。

发表评论