UIKit框架(四十三) —— CALayer的简单实用示例(一)

版本记录

版本号 时间
V1.0 2020.07.10 星期五

前言

iOS中有关视图控件用户能看到的都在UIKit框架里面,用户交互也是通过UIKit进行的。感兴趣的参考上面几篇文章。
1. UIKit框架(一) —— UIKit动力学和移动效果(一)
2. UIKit框架(二) —— UIKit动力学和移动效果(二)
3. UIKit框架(三) —— UICollectionViewCell的扩张效果的实现(一)
4. UIKit框架(四) —— UICollectionViewCell的扩张效果的实现(二)
5. UIKit框架(五) —— 自定义控件:可重复使用的滑块(一)
6. UIKit框架(六) —— 自定义控件:可重复使用的滑块(二)
7. UIKit框架(七) —— 动态尺寸UITableViewCell的实现(一)
8. UIKit框架(八) —— 动态尺寸UITableViewCell的实现(二)
9. UIKit框架(九) —— UICollectionView的数据异步预加载(一)
10. UIKit框架(十) —— UICollectionView的数据异步预加载(二)
11. UIKit框架(十一) —— UICollectionView的重用、选择和重排序(一)
12. UIKit框架(十二) —— UICollectionView的重用、选择和重排序(二)
13. UIKit框架(十三) —— 如何创建自己的侧滑式面板导航(一)
14. UIKit框架(十四) —— 如何创建自己的侧滑式面板导航(二)
15. UIKit框架(十五) —— 基于自定义UICollectionViewLayout布局的简单示例(一)
16. UIKit框架(十六) —— 基于自定义UICollectionViewLayout布局的简单示例(二)
17. UIKit框架(十七) —— 基于自定义UICollectionViewLayout布局的简单示例(三)
18. UIKit框架(十八) —— 基于CALayer属性的一种3D边栏动画的实现(一)
19. UIKit框架(十九) —— 基于CALayer属性的一种3D边栏动画的实现(二)
20. UIKit框架(二十) —— 基于UILabel跑马灯类似效果的实现(一)
21. UIKit框架(二十一) —— UIStackView的使用(一)
22. UIKit框架(二十二) —— 基于UIPresentationController的自定义viewController的转场和展示(一)
23. UIKit框架(二十三) —— 基于UIPresentationController的自定义viewController的转场和展示(二)
24. UIKit框架(二十四) —— 基于UICollectionViews和Drag-Drop在两个APP间的使用示例 (一)
25. UIKit框架(二十五) —— 基于UICollectionViews和Drag-Drop在两个APP间的使用示例 (二)
26. UIKit框架(二十六) —— UICollectionView的自定义布局 (一)
27. UIKit框架(二十七) —— UICollectionView的自定义布局 (二)
28. UIKit框架(二十八) —— 一个UISplitViewController的简单实用示例 (一)
29. UIKit框架(二十九) —— 一个UISplitViewController的简单实用示例 (二)
30. UIKit框架(三十) —— 基于UICollectionViewCompositionalLayout API的UICollectionViews布局的简单示例(一)
31. UIKit框架(三十一) —— 基于UICollectionViewCompositionalLayout API的UICollectionViews布局的简单示例(二)
32. UIKit框架(三十二) —— 替换Peek and Pop交互的基于iOS13的Context Menus(一)
33. UIKit框架(三十三) —— 替换Peek and Pop交互的基于iOS13的Context Menus(二)
34. UIKit框架(三十四) —— Accessibility的使用(一)
35. UIKit框架(三十五) —— Accessibility的使用(二)
36. UIKit框架(三十六) —— UICollectionView UICollectionViewDiffableDataSource的使用(一)
37. UIKit框架(三十七) —— UICollectionView UICollectionViewDiffableDataSource的使用(二)
38. UIKit框架(三十八) —— 基于CollectionView转盘效果的实现(一)
39. UIKit框架(三十九) —— iOS 13中UISearchController 和 UISearchBar的新更改(一)
40. UIKit框架(四十) —— iOS 13中UISearchController 和 UISearchBar的新更改(二)
41. UIKit框架(四十一) —— 使用协议构建自定义Collection(一)
42. UIKit框架(四十二) —— 使用协议构建自定义Collection(二)

开始

首先看下主要内容:

在本文中,您将了解CALayer及其工作原理。 您将使用CALayer以获得酷炫的效果,例如形状,渐变和粒子系统。本文内容来自翻译。

接着看一下写作环境:

Swift 5, iOS 13, Xcode 11

下面就是正文了

您可能知道,在iOS应用中看到的所有内容都是视图。有按钮视图,表格视图,滑块视图,甚至包含其他视图的父视图!

但是您可能不知道,iOS中的每个视图都由另一个称为图层的类(具体来说就是CALayer)支持。

在本文中,您将构建Layer Player应用程序。在此过程中,您将学到:

  • 什么是CALayer及其运作方式。
  • 如何使用CALayer功能来实现酷效果,例如形状,渐变甚至粒子系统。

CALayer具有多种属性和方法可以修改。它具有几个具有独特属性和方法的子类。

Layer Player应用程序演示了九种不同的CALayer功能。

入门项目并没有做什么用。您将要编写代码以将此应用程序变成功能齐全的CALayer指导!

在每个部分中,您都将添加必要的代码以使神奇的图层发挥作用。 添加了每一行代码后,请按照刚刚启用的设置进行操作。 这将使您对所探索的每种功能的功能有更深入的了解。

但是首先,来一些理论。


How does CALayer relate to UIView?

UIView处理许多事情,包括布局和触摸事件。 但是,它不能直接控制图形或动画。 UIKit将该任务委托给Core Animation框架,从而可以使用CALayer。 实际上,UIView只是CALayer的封装。

每个UIView都有一个根CALayer,可以包含多个子层。 当您在UIView上设置bounds时,该视图又在其支持CALayer上设置bounds。 如果在UIView上调用layoutIfNeeded(),则该调用将转发到根CALayer

在深入研究Layer Player应用程序之前,还应该了解一些有关CALayer的知识:

  • Layers can have sublayers - 层可以具有子层:就像视图可以具有子视图一样,层也可以具有子层。 您可以将它们用于一些很酷的效果!
  • Their properties are animatable - 它们的属性是可设置动画的:更改图层的属性时,可以使用CAAnimation为更改设置动画。
  • Layers are lightweight - 图层重量轻:图层的重量比视图轻,因此可以帮助您获得更好的性能。
  • They have tons of useful properties - 它们具有大量有用的属性:您将在以下示例中进行探索。

现在,您就可以开始使用CALayer创建一些自定义视图了。


Customizing Views With CALayer

打开CALayerViewController.swift并将以下代码添加到setupLayer()中:

//1
layer.frame = viewForLayer.bounds
layer.contents = UIImage(named: "star")?.cgImage

// 2
layer.contentsGravity = .center
layer.magnificationFilter = .linear

// 3
layer.cornerRadius = 100.0
layer.borderWidth = 12.0
layer.borderColor = UIColor.white.cgColor
layer.backgroundColor = swiftOrangeColor.cgColor

//4
layer.shadowOpacity = 0.75
layer.shadowOffset = CGSize(width: 0, height: 3)
layer.shadowRadius = 3.0
layer.isGeometryFlipped = false

在此代码中,您将创建一个自定义视图:

  • 1)设置图层layer的边界bounds,然后将图像设置为图层的内容。 注意底层CGImage的使用。
  • 2)然后,将图像居中放置在图层中。 您可以使用contentsGravity来更改大小(如resizing, resizing aspect and resizing aspect fill)以及位置(中心,顶部,右上角,右边等)。magnificationFilter控制放大图像的行为。
  • 3)接下来,设置CALayer的背景颜色,使其变圆并为其添加边框。 请注意,您正在使用基础CGColor对象更改图层的颜色属性。
  • 4)最后,为图层创建阴影。 当isGeometryFlippedtrue时,位置几何形状和阴影将上下颠倒。

构建并运行。 选择CALayer,然后检查结果:

控件目前不执行任何操作。 因此,添加以下内容以到prepare(for:sender:)

if segue.identifier == "DisplayLayerControls" {
  (segue.destination as? CALayerControlsViewController)?.layerViewController = self
}

这将连接嵌入式CALayerControlsViewController。 再次构建并运行,然后检查实际使用的图层属性。 试玩各种控件,以感受一下使用CALayer可以做什么!

1. Improving Performance With ShouldRasterize and DrawsAsynchronously

CALayer具有两个其他可改善性能的属性:shouldRasterizedrawsAsynchronously

默认情况下,shouldRasterizefalse。设置为true时,图层的内容仅渲染一次,从而提高了性能。非常适合在屏幕上动起来但外观不变的对象。

drawsAsynchronouslyshouldRasterize相反,默认情况下也为false。当应用需要重复重绘图层内容时,将其设置为true可以提高性能。例如,当您使用连续渲染动画粒子的发射器层时,可能会发生这种情况。您将在本教程的后面部分中使用此功能。

注意:在设置shouldRasterizedrawsAsynchronously之前,请先考虑其含义。更改设置并比较性能。这将帮助您确定在给定情况下激活这些功能是否实际上会提高性能。如果您滥用这些属性,性能可能会降低!


Scrolling With CAScrollLayer

CAScrollLayer显示可滚动图层的一部分。 这是非常基本的-它无法直接响应用户的触摸,甚至无法检查可滚动图层的bounds。 但这确实很酷,例如防止滚动超出bounds

UIScrollView不使用CAScrollLayer来完成其工作。 而是直接更改图层的bounds。 使用CAScrollLayer,您可以:

  • 将滚动模式设置为水平或垂直(horizontal or vertical)
  • 以编程方式滚动到特定点point或区域area

构建并运行,然后从菜单中选择CAScrollLayer。 您会看到一张图片和两个控制滚动方向的开关。

现在是时候设置滚动了。

1. Setting up Scrolling

返回代码并打开CAScrollLayerViewController.swift。 视图中已经有一个CAScrollLayer

将以下内容添加到panRecognized(_ :)

var newPoint = scrollingView.bounds.origin
newPoint.x -= sender.translation(in: scrollingView).x
newPoint.y -= sender.translation(in: scrollingView).y
sender.setTranslation(.zero, in: scrollingView)
scrollingViewLayer.scroll(to: newPoint)
    
if sender.state == .ended {
  UIView.animate(withDuration: 0.3) {
    self.scrollingViewLayer.scroll(to: CGPoint.zero)
  }
}

发生平移手势时,您需要计算所需的相应平移,然后在CAScrollLayer上调用scroll(to :)方法以相应地移动图像。

scroll(to :)不会自动设置动画,因此您可以使用UIView.animate(withDuration:animations :)对其进行明确设置。

构建并运行,然后返回到CAScrollLayer示例。 现在,平移图像时,您将看到类似以下的内容:

Layer Player还包括两个控件,用于锁定水平和垂直滚动。 接下来,您将看一下这部分。

2. Locking Scrolling Directions

将以下代码添加到scrollingSwitchChanged(_ :)

switch (horizontalScrollingSwitch.isOn, verticalScrollingSwitch.isOn) {
case (true, true):
  scrollingViewLayer.scrollMode = .both
case (true, false):
  scrollingViewLayer.scrollMode = .horizontally
case (false, true):
  scrollingViewLayer.scrollMode = .vertically
default:
  scrollingViewLayer.scrollMode = .none
}

在这里,您添加了一个简单的switch块。 它根据用户界面中开关的值确定滚动方向。

现在构建并运行。 返回应用程序,拨动开关,然后查看CAScrollLayer的行为。

以下是何时使用或不使用CAScrollLayer的一些经验法则:

  • 如果您想要轻巧(lightweight)的东西并且只需要以编程方式滚动,请考虑使用CAScrollLayer
  • 如果希望用户能够滚动,最好使用UIScrollView。 要了解更多信息,请查看我们的Scroll View School视频教程系列。
  • 如果要滚动很大的图像,请考虑使用CATiledLayer

Rendering Text With CATextLayer

CATextLayer提供简单但快速的纯文本或属性字符串渲染。 与UILabel不同,CATextLayer不能具有分配的UIFont,只能具有CTFontCGFont

打开CATextLayerViewController.swift并将以下内容添加到setUpTextLayer()的末尾:

// 1
textLayer.font = helveticaFont
textLayer.fontSize = Constants.baseFontSize

// 2
textLayer.foregroundColor = UIColor.darkGray.cgColor
textLayer.isWrapped = true
textLayer.alignmentMode = .left
textLayer.truncationMode = .end

// 3
textLayer.contentsScale = UIScreen.main.scale

这是您正在做的事情:

  • 1) 您设置文本图层的字体。请注意,这是CTFont,而不是UIFont。您可以使用CTFontCreateWithName(_:_:_ :)创建这些文件。 CATextLayer允许您像在此处一样直接在图层上设置字体大小。
  • 2) 接下来,设置文本颜色,换行,对齐和截断模式。所有这些都可以在常规UILabelUITextView上获得。
  • 3) 最后,将图层的contentsScale设置为与屏幕的比例相匹配。默认情况下,所有图层类(不仅是CATextLayer)都以比例因子1进行渲染。将图层附加到视图会自动将其contentScale设置为当前屏幕的适当比例因子。但是,对于您手动创建的任何图层,必须显式设置contentsScale。否则,其比例因子将为1,从而使其在Retina显示器上显示为像素化。

构建并运行,然后从菜单中选择CATextLayerLayer Player具有可以更改许多CATextLayer属性的控件。和他们一起玩,看看他们做什么:

接下来,您将使Layer Player能够播放媒体文件。


Playing Media With AVPlayerLayer

AVPlayerLayerAVFoundation增添了美好的一面。 它拥有一个AVPlayer来播放AVPlayerItem类型的媒体文件。

1. Setting up Media Playback

打开AVPlayerLayerViewController.swift并将以下代码添加到setUpPlayerLayer()中:

// 1
playerLayer.frame = viewForPlayerLayer.bounds

// 2
let url = Bundle.main.url(forResource: "colorfulStreak", withExtension: "m4v")!
let item = AVPlayerItem(asset: AVAsset(url: url))
let player = AVPlayer(playerItem: item)

// 3
player.actionAtItemEnd = .none

// 4
player.volume = 1.0
player.rate = 1.0


playerLayer.player = player

在此代码中,您设置了播放器。这是如何做:

  • 1) 首先,设置图层的frame
  • 2) 接下来,使用AVPlayerItem创建一个player
  • 3) 您告诉播放器完成播放后什么也不做。其他选项包括暂停或前进到下一个资源(如果适用)。
  • 4) 最后,设置播放器的音量和速率。

现在您可以播放媒体文件了,您将使用户能够更改他们的播放速度。

2. Changing Playback Speeds

rate设置视频播放速度。 0表示暂停,1表示视频以正常速度播放。

但是,设置速率rate也会指示以该速率开始播放。换句话说,调用pause()并将rate设置为0作用是一样的,同样调用play()并将rate设置为1作用也是一样!

那么快进,慢动作或反向播放呢?好吧,AVPlayer都可以满足您!

当您将rate设置为高于1的任何值时,播放将以该速度乘以常规速度开始。例如,将rate设置为2表示倍速。并将rate设置为负数会导致以该速度乘以正常速度反向播放。

但是,在以常规速度向前播放以外的任何速率进行播放之前,应检查AVPlayerItem中的相应变量以验证是否可以该速率播放:

  • canPlayFastForward大于1的任何数字。
  • canPlaySlowForward的范围为0到最大(但不包括1)之间的任何数字。
  • canPlayReverse-1
  • canPlaySlowReverse的范围是-1到最大(但不包括0)之间的任何数字。
  • canPlayFastReverse的值小于-1

大多数视频可以以各种前进速度播放,但很少可以反向播放。

3. Updating the User Interface

现在,回到代码。当您点击播放按钮时,它应切换控件以播放AVAset并设置按钮的标题。

将以下内容添加到playButtonTapped(_ :)

if player?.rate == 0 {
  player?.rate = rate
  updatePlayButtonTitle(isPlaying: true)
} else {
  player?.pause()
  updatePlayButtonTitle(isPlaying: false)
}

在此,您可以根据初始速率切换播放器的状态并更新播放按钮的标题。

最后,您将添加代码,以在播放器到达媒体文件的末尾时将播放光标移回开头。

4. Resetting the Playback Cursor

viewDidLoad()中,您可以看到AVPlayerItemDidPlayToEndTimeNotification具有observer。 当AVPlayerItem到达末尾时,将调用此通知。

将以下代码添加到playerDidReachEndNotificationHandler(_ :)

// 1
guard let playerItem = notification.object as? AVPlayerItem else { return }

// 2
playerItem.seek(to: .zero, completionHandler: nil)
    
// 3
if player?.actionAtItemEnd == .pause {
  player?.pause()
  updatePlayButtonTitle(isPlaying: false)
}

在此代码中:

  • 1) 您验证通知对象是AVPlayerItem
  • 2) 您可以使用seek(to:completionHandler :)将播放器发送到所需位置。 在这种情况下,您将播放器发送到CMTime.zero这是开始。
  • 3) 如果播放器的actionAtItemEnd设置为.pause,则可以暂停播放器,并相应地设置按钮文本。

构建并运行,然后从菜单中选择AVPlayerLayer。 更改控件上的值,以查看每个控件如何更改图层的行为。

下一步,您将看到使用CALayer制作炫酷渐变非常容易。


Color Blending With CAGradientLayer

CAGradientLayer使混合两种或多种颜色变得容易,这使其特别适合于背景。您可以通过分配以下内容进行配置:

  • CGColors数组。
  • 一个开始点startPoint,用于指定渐变图层应从何处开始。
  • 一个Endpoint,用于指定渐变层应在何处结束。

请记住,startPointendPoint不是显式点。而是在单位坐标空间中定义它们,并在绘制它们时将它们映射到图层的边界。换一种说法:

  • x值为1表示该点位于图层的右边缘。
  • y值为1表示该点位于图层的底部边缘。

CAGradientLayer具有type属性,可用于更改渐变图案。默认(也是最常用的)是axial(线性)。这意味着,如果在startPointendPoint之间绘制一条线(A),则渐变将沿着垂直于A的假想线(B)发生,并且B上的所有点都将具有相同的颜色:

或者,您可以使用locations属性控制渐变图案。 location接受一个介于01之间的值的数组。这些值指定相对停止点,渐变层应在其中使用colors数组中的下一个颜色。 如果未指定,则默认情况下停止位置的间隔均匀。

注意:如果设置locations,则其数量必须与colors的数量匹配,否则会发生不良情况。 所以要当心!

打开CAGradientLayerViewController.swift并将以下代码添加到setUpGradientLayer()中:

gradientLayer.frame = viewForGradientLayer.bounds
gradientLayer.colors = colors
gradientLayer.locations = locations.map { NSNumber(value: $0) }
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)

在此代码中,您为图层分配了一个颜色数组,并设置了起点和终点。

构建并运行,从菜单中选择CAGradientLayer,然后查看结果:

好丰富多彩! Layer Player为您提供了更改起点和终点,颜色和位置的控件。 尝试他们的乐趣,看看您能得到什么。

对于下一步,您将向Layer Player添加加载动画。


Creating a Loading Animation with CAReplicatorLayer

CAReplicatorLayer将图层复制指定的次数。 这使您可以创建一些令人惊讶的炫酷效果!

每个图层副本可以具有自己的颜色和位置更改。 您可以延迟其绘制,以使整个复制器层具有动画效果。

构建并运行,然后从菜单中选择CAReplicatorLayer。 您会看到几个控件……但没有什么可控制:

首先,配置要复制的图层。

1. Configuring Replicator Layers

返回Xcode并打开CAReplicatorLayerViewController.swift。 在// MARK: - Layer setup下有三种方法要填充。

首先,将以下内容添加到setUpReplicatorLayer()中:

// 1
replicatorLayer.frame = viewForReplicatorLayer.bounds

// 2
let count = instanceCountSlider.value
replicatorLayer.instanceCount = Int(count)
replicatorLayer.instanceDelay = 
  CFTimeInterval(instanceDelaySlider.value / count)

// 3
replicatorLayer.instanceColor = UIColor.white.cgColor
replicatorLayer.instanceRedOffset = offsetValueForSwitch(offsetRedSwitch)
replicatorLayer.instanceGreenOffset = offsetValueForSwitch(offsetGreenSwitch)
replicatorLayer.instanceBlueOffset = offsetValueForSwitch(offsetBlueSwitch)
replicatorLayer.instanceAlphaOffset = offsetValueForSwitch(offsetAlphaSwitch)

//4
let angle = Float.pi * 2.0 / count
replicatorLayer.instanceTransform = 
  CATransform3DMakeRotation(CGFloat(angle), 0.0, 0.0, 1.0)

//5
viewForReplicatorLayer.layer.addSublayer(replicatorLayer)

在此代码中,配置复制图层(replicator layer)的实例:

  • 1) 首先,设置图层的frame
  • 2) 您可以设置要创建的复制数量以及两次创建之间的时间延迟。
  • 3) 您可以定义所有复制实例的基础颜色以及要添加到每个颜色组件的增量值。 每个默认值均为0,可有效保留所有实例的颜色值。 但是,在这种情况下,实例颜色最初设置为白色。 这意味着红色,绿色和蓝色已经是1.0。 例如,如果将红色设置为0,并将绿色和蓝色设置为负数,则红色将成为突出的颜色。 同样,将alpha偏移量添加到每个连续复制实例的alpha中。
  • 4) 现在,旋转每个实例以创建一个圆形效果。
  • 5) 最后,将图层添加到视图的图层。

2. Setting Position and Fade

接下来,为replicator layer创建一个实例层以供使用。 将以下内容添加到setUpInstanceLayer

let layerWidth = CGFloat(layerSizeSlider.value)
let midX = viewForReplicatorLayer.bounds.midX - layerWidth / 2.0
instanceLayer.frame = CGRect(
  x: midX,
  y: 0.0,
  width: layerWidth,
  height: layerWidth * lengthMultiplier)
instanceLayer.backgroundColor = UIColor.white.cgColor
replicatorLayer.addSublayer(instanceLayer)

此代码设置实例frame。 在这里,第一个实例将绘制在中心xviewForReplicatorLayerbounds的顶部。 然后,您设置实例的颜色并将实例层添加到replicator layer

现在用于淡入淡出动画。 将以下内容添加到setUpLayerFadeAnimation中:

fadeAnimation.fromValue = 1.0
fadeAnimation.toValue = 0.0
fadeAnimation.repeatCount = Float(Int.max)

在这里,您使用CABasicAnimationfadeAnimation将实例的不透明度从1(不透明)更改为0(透明)。

构建并运行并享受您的代码现在所做的工作:

Layer Player包含用于操纵大多数这些属性的控件。 更改值,看看它们如何影响动画!

现在,您已经创建了一个有趣的动画圆,接下来将使用CALayer的另一个属性绘制星星。


Drawing a Star With CAShapeLayer

CAShapeLayer使用可缩放的矢量路径绘制线条,形状和图案-比使用图片要快得多! 另一个好处是,您无需提供常规的@ 2x@ 3x尺寸的图像。

此外,您还可以使用各种属性来自定义图形。 例如,您可以调整线的粗细,颜色,虚线并指定线的连接方式。 而且,当您的线条形成形状时,您可以更改其填充方式。

现在该画一个橙色的星星了! 首先,打开CAShapeLayerViewController.swift并将以下内容添加到setUpShapeLayer()中:

// 1
shapeLayer.path = openPath.cgPath

// 2
shapeLayer.lineCap = .butt
shapeLayer.lineJoin = .miter
shapeLayer.miterLimit = 4.0

// 3
shapeLayer.lineWidth = CGFloat(lineWidthSlider.value)
shapeLayer.strokeColor = swiftOrangeColor.cgColor
shapeLayer.fillColor = UIColor.white.cgColor

// 4
shapeLayer.lineDashPattern = nil
shapeLayer.lineDashPhase = 0.0

viewForShapeLayer.layer.addSublayer(shapeLayer)

经历这个:

  • 1) CAShapeLayer采用CGPath,它定义了如何在屏幕上绘制它。 您很快就会画出这条路。
  • 2) lineJoinlineCap定义路径中的线如何相交和结束。
  • 3) 设置路径线的宽度和颜色。
  • 4) lineDashPatternlineDashPhase允许您绘制虚线(dashed lines)。 在这种情况下,您将绘制不带dashes的简单线条。

接下来,您将绘制路径本身。 将以下内容添加到setUpPath()中:

openPath.move(to: CGPoint(x: 30, y: 196))
    
openPath.addCurve(
  to: CGPoint(x: 112.0, y: 12.5),
  controlPoint1: CGPoint(x: 110.56, y: 13.79),
  controlPoint2: CGPoint(x: 112.07, y: 13.01))
    
openPath.addCurve(
  to: CGPoint(x: 194, y: 196),
  controlPoint1: CGPoint(x: 111.9, y: 11.81),
  controlPoint2: CGPoint(x: 194, y: 196))
    
openPath.addLine(to: CGPoint(x: 30.0, y: 85.68))
openPath.addLine(to: CGPoint(x: 194.0, y: 48.91))
openPath.addLine(to: CGPoint(x: 30, y: 196))

这将绘制您的星形。

构建并运行,然后选择CAShapeLayer以查看结果:

Layer Player包含用于操纵许多CAShapeLayer属性的控件。 玩这些控件来调整星星:

CALayer不仅可以让您绘制2D图形,还可以绘制3D形状。 这就是您在下一节中要做的。


Drawing a 3D Cube With CATransformLayer

CATransformLayer不会像其他图层类一样展平其子图层层次结构。 这使得绘制3D结构非常方便。 CATransformLayer实际上是其子层的容器。 每个子层可以具有自己的transformsopacity更改。 但是,它忽略对其他渲染图层属性(如边框宽度和颜色)的更改。

在本部分中,您将构建一个交互式3D立方体。 首先打开CATransformLayerViewController.swift并将以下代码添加到buildCube()中:

// 1
transformLayer = CATransformLayer()

// 2
let redLayer = sideLayer(color: redColor)
redLayer.addSublayer(swipeMeTextLayer)
transformLayer.addSublayer(redLayer)
    
// 3
let orangeLayer = sideLayer(color: orangeColor)
var orangeTransform = CATransform3DMakeTranslation(
  sideLength / 2.0, 
  0.0,
  sideLength / -2.0)
orangeTransform = CATransform3DRotate(
  orangeTransform,
  degreesToRadians(90.0), 
  0.0, 
  1.0, 
  0.0)
orangeLayer.transform = orangeTransform
transformLayer.addSublayer(orangeLayer)

在这里,您创建cube的前两个侧面:

  • 1) 首先,创建一个CATransformerLayer
  • 2) 接下来,添加一个CALayer来表示cube的红色面,并将其添加到transformLayer`。
  • 3) 然后,添加cube的橙色面。 您可以通过设置transform属性来定位它,该属性接受CATransform3D`。

同样,添加以下代码:

let yellowLayer = sideLayer(color: yellowColor)
yellowLayer.transform = CATransform3DMakeTranslation(0.0, 0.0, -sideLength)
transformLayer.addSublayer(yellowLayer)
        
let greenLayer = sideLayer(color: greenColor)
var greenTransform = CATransform3DMakeTranslation(
  sideLength / -2.0, 
  0.0,
  sideLength / -2.0)
greenTransform = CATransform3DRotate(
  greenTransform,
  degreesToRadians(90.0), 
  0.0, 
  1.0, 
  0.0)
greenLayer.transform = greenTransform
transformLayer.addSublayer(greenLayer)
        
let blueLayer = sideLayer(color: blueColor)
var blueTransform = CATransform3DMakeTranslation(
  0.0, 
  sideLength / -2.0,
  sideLength / -2.0)
blueTransform = CATransform3DRotate(
  blueTransform,
  degreesToRadians(90.0), 
  1.0, 
  0.0, 
  0.0)
blueLayer.transform = blueTransform
transformLayer.addSublayer(blueLayer)
        
let purpleLayer = sideLayer(color: purpleColor)
var purpleTransform = CATransform3DMakeTranslation(
  0.0, 
  sideLength / 2.0,
  sideLength / -2.0)
purpleTransform = CATransform3DRotate(
  purpleTransform,
  degreesToRadians(90.0), 
  1.0, 
  0.0, 
  0.0)
purpleLayer.transform = purpleTransform
transformLayer.addSublayer(purpleLayer)

在这里,创建cube的其余四个面。

最后,设置图层的z锚点,然后通过添加以下内容将其添加到屏幕上:

transformLayer.anchorPointZ = sideLength / -2.0
viewForTransformLayer.layer.addSublayer(transformLayer)

在这里,您可以配置anchorPointZ,以指定发生几何操作的z轴上的锚点。

注意:要了解有关矩阵变换的更多信息,请查看以下练习:

  • Rich Turton 的 3DTransformFun project项目。
  • Enter The Matrix project 的Matrix项目。

现在,您已经创建了cube,是时候设置它了。

1. Rotating the Cube

要旋转cube,您需要处理触摸事件。 您无法直接点击hit test变换图层(transform layer),因为它没有2D坐标空间可用于映射接触点。 但是,可以对各个子层进行测试。

该项目代码已经包括Bill Dudney的 TrackBall utility实用程序,该实用程序已移植到Swift。 这使基于用户手势的3D transform变得容易。 您可以在// MARK: - Touch Handlingcheck it out

构建并运行。 从菜单中选择CATransformLayer,您会看到一个红色正方形:

滑动cube并切换开关以查看它们如何更改cube


Making a Shooting Star With CAEmitterLayer

在计算机图形学中,粒子系统Particle Systems用于生成现实世界的现象,例如火,烟,火花,烟花和爆炸。

Core Animation中,CAEmitterLayer可用于渲染此类系统并控制作为CAEmitterCell实例的动画粒子。 CAEmitterLayerCAEmitterCell都具有更改渲染速率,大小,形状,颜色,速度,寿命等的属性。

1. Preparing the Emitter Layer

发射层配置发射粒子的位置和形状。

将以下内容添加到CAEmitterLayerViewController.swift中的setUpEmitterLayer()中:

// 1
resetEmitterCells()
emitterLayer.frame = viewForEmitterLayer.bounds
viewForEmitterLayer.layer.addSublayer(emitterLayer)

// 2
emitterLayer.seed = UInt32(Date().timeIntervalSince1970)

// 3
emitterLayer.emitterPosition = CGPoint(
  x: viewForEmitterLayer.bounds.midX * 1.5, 
  y: viewForEmitterLayer.bounds.midY)

// 4
emitterLayer.renderMode = .additive

这是您所做的:

  • 1) 首先,重置图层并将其添加到视图中。
  • 2) 然后,为该层的随机数生成器提供种子。 反过来,这会随机化层的发射器单元的某些属性,例如速度。 有关更多信息,请参见下一个代码块之后的以下注释。
  • 3) 您设置发射器位置。
  • 4) 最后,定义粒子单元如何渲染到图层中。

2. Setting up the Emitter Cell

接下来,设置发射器单元(emitter cell),该单元确定粒子的特定行为和外观。

将以下内容添加到setUpEmitterCell()中:

// 1
emitterCell.contents = UIImage(named: "smallStar")?.cgImage
    
// 2
emitterCell.velocity = 50.0
emitterCell.velocityRange = 500.0
    
// 3 
emitterCell.color = UIColor.black.cgColor

// 4
emitterCell.redRange = 1.0
emitterCell.greenRange = 1.0
emitterCell.blueRange = 1.0
emitterCell.alphaRange = 0.0
emitterCell.redSpeed = 0.0
emitterCell.greenSpeed = 0.0
emitterCell.blueSpeed = 0.0
emitterCell.alphaSpeed = -0.5
emitterCell.scaleSpeed = 0.1
    
// 5
let zeroDegreesInRadians = degreesToRadians(0.0)
emitterCell.spin = degreesToRadians(130.0)
emitterCell.spinRange = zeroDegreesInRadians
emitterCell.emissionLatitude = zeroDegreesInRadians
emitterCell.emissionLongitude = zeroDegreesInRadians
emitterCell.emissionRange = degreesToRadians(360.0)
    
// 6
emitterCell.lifetime = 1.0
emitterCell.birthRate = 250.0

// 7
emitterCell.xAcceleration = -800
emitterCell.yAcceleration = 1000

逐步进行以下操作:

  • 1) 您可以通过将发射器单元的内容设置为图像来设置发射器单元。该图像在Layer Player项目中可用。
  • 2) 然后,使用VelocityRange指定初始速度和最大方差。发射器层使用上述种子创建随机数生成器。随机数生成器通过使用初始值加上和减去范围值来随机化范围内的值。对于以Range结尾的所有属性,都会发生这种随机化。
  • 3) 您将颜色设置为黑色。这样可以使方差与默认的白色不同,从而导致粒子过亮。
  • 4) 接下来,使用与velocityRange相同的随机化设置一系列颜色范围。这次,随机化指定每种颜色的随机范围。速度值决定了每种颜色在单元的整个生命周期内变化的速度。
  • 5) 在这里,您可以指定如何在整个圆锥周围分布cell。您可以通过设置发射器单元的旋转速度和发射范围来实现。 emissionRange定义以弧度指定的圆锥。 emissionRange确定如何在锥体周围分布发射器单元。
  • 6) 您将cell的寿命设置为1秒。该属性的默认值为0,因此,如果您未显式设置它,则永远不会显示cell!每秒的birthRate也是如此。 birthRate的默认值为0,因此必须将其设置为正数才能显示cell
  • 7) 最后,设置cell xy的加速度。这些值会影响粒子发射的视角。

构建并运行,然后选择CAEmitterLayer。结果如下:

返回Xcode,找到prepare(for:sender :)并取消注释该代码以连接控件表。

现在,再次构建并运行该应用程序,并使用控件来调整所有上述属性以及其他一些属性:

恭喜你! 您已经完成了精彩的CALayer之旅。 您已经看到了9个有关如何使用CALayer及其许多子类的示例。

但是不要在这里停下来! 打开一个新项目,或使用现有项目之一,然后看看如何使用图层。 您可能可以实现更好的性能,或者可以添加一些新的动画来让您的用户以及您自己赞叹不已!

后记

本篇主要讲述了CALayer的简单实用示例,感兴趣的给个赞或者关注~~~

你可能感兴趣的:(UIKit框架(四十三) —— CALayer的简单实用示例(一))