just for iOS

10/21

Book: iOS Core Animation: Advanced Techniques

这儿有中文翻译版本 看来是一本好书
这是另外一个版本

关键帧动画

CAKeyframeAnimation * animation = [CAKeyframeAnimation animation];
animation.keyPath = @"transform.scale";
animation.values = @[@1.5,@2.0, @0.8, @1.0];
animation.duration = 0.5;
animation.calculationMode = kCAAnimationCubic;
[self.layer addAnimation:animation forKey:nil];
这个动画是让一个物体(如self:Button),先放到再缩小,最后恢复正常大小的动画,也就是一个点击效果。
let zoom = CAKeyframeAnimation()
zoom.keyPath = "transform.scale"
zoom.values = [1.1, 1.3, 1.0, 0.7, 1.0]
zoom.duration = 0.5
zoom.calculationMode = .cubic
self.layer.add(zoom, forKey: "")
这里面的keyPath也是KVO

KVO的运用

在代码中我们经常会看到
explosionCell.name = @"explosionCell";
[self.explosionLayer setValue:@1000 forKeyPath:@"emitterCells.explosionCell.birthRate"];这就是一种KVO的非常直接的运用,
- 一方面,这会让代码变得很简单,不需要传递某些参数,而是直接通过名字就能修改某些变量
- 但另一方面,这也让代码变得不那么好理解,感觉这不是真正的API

CAEmitterCell:烟花的时序控制

我们通常会将烟花分成好几个过程,
- rocket射向空中
- 炸开
- 下落?
使用CAEmitterCell的cells,也就是subCells来实现,
设置 beginTime/lifetime 就可以实现一般控制

其实整个iOS都CALayer都是这样设计的,
var sublayers: [CALayer]? An array containing the layer’s sublayers.
var superlayer: CALayer? The superlayer of the layer.
func addSublayer(CALayer) Appends the layer to the layer’s list of sublayers.
而且每个layer都可以设置sublayers和beginTime,从而实现时序控制

CAEmitterLayer 关于发生粒子位置/形状

var renderMode: CAEmitterLayerRenderModeDefines how particle cells are rendered into the layer.
var emitterPosition: CGPoint The position of the center of the particle emitter. Animatable.
var emitterShape: CAEmitterLayerEmitterShape Specifies the emitter shape.
var emitterZPosition: CGFloat Specifies the center of the particle emitter shape along the z-axis. Animatable.
var emitterDepth: CGFloat Determines the depth of the emitter shape.
var emitterSize: CGSize Determines the size of the particle emitter shape. Animatable.
原则上这些参数都会有影响,参CAEmitterLayerEmitterShape,但实际使用起来很困难,调试起来发现很多跟想象不一样。

CALayer的size

当我们手动添加一个layer给一个view的时候,如果view的size变化,添加的layer不会跟着变化。需要自己设置,
- (void)layoutSubviews {
[super layoutSubviews];
// resize your layers based on the view's new bounds
mylayer.frame = self.bounds;
}

CACurrentMediaTime()

func CACurrentMediaTime() -> CFTimeInterval
这个函数不是Timer,而是获取当前时间,精度比较高,在游戏中尤其重要。

CAMediaTiming.beginTime

var beginTime: CFTimeInterval { get set }beginTime是接口的函数,而 CAAnimation/CAEmitterCell/CALayer都实现了这个接口,也就是CAEmitterCell/CAEEmitterLayer都有这个方法。在我实验的过程中,我发现一个问题,比如我想让气球从底部升起,但是无论我怎么调整参数发现气球出现的位置都是随机的,直到我设置:
cell.beginTime = CACurrentMediaTime()
但我并不了解这个问题为什么会被解决了,我的灵感来自这个讨论

Example

Examples:
- confetti
- fireworks
- 这儿以及这儿一个很酷的东西
Other libs:
- SAConfettiView
-
CAEMitterLayer本质上是SpriteKit的UIKit的包装产物,不过也不一定How to stop?
- particlesLayer?.removeFromSuperlayer()
- 采用
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 4)
- 采用Timer
Timer.scheduledTimer(timeInterval: 4,
target: self,
selector: #selector(self.updateTime),
userInfo: nil,
repeats: false)
- particlesLayer.birthRate = 0 (default is 1)
本以为设置birthRate = 0不是个好选择,但观察发现其实是最好的,否则就会出现突然停止的。因此先设置birthRate = 0,之后remove
How to start?
- view.layer.addSublayer(particlesLayer)
- 根据上面,可以通过调整birthRate来增大,达到一种效果
beginTime:
用这个来控制两个连续的动作,比如烟花上升和炸开。

CAEmitterLayer & CAEmitterCell

这是Core Animation的两个主要的类,也是particle system的核心API其中CAEmitterCell能够嵌套:
var emitterCells: [CAEmitterCell]?
An optional array containing the sub-cells of this cell.
这样的话可以作出fireworks那种连续动作
并且CAEmitterLayer是overlay的效果,可以很好的融入代码中。

基本的效果

Confetti: 五色彩纸
Balloon:彩色气球,如twitter birthday
Fireworks: 烟火(烟火需要好几个过程,因此比较复杂)- 上升,炸开,零星效果

Resources from Github & others

FunWithParticleEmitter & Source code: Confetti/Fireworks
IHImageViewEffects: Balloon, fireworks
Fireworks editor: an app that generate swift code for fireworks
Apple: 采用SpriteKit, CKEmitterNode 以及 如何添加使用xcode来添加sks:
new/file/SpriteKit particle file

Catalyst

简而言之就是iOS的app直接支持Mac,当然后面的支持部分可能会复杂,但实际效果很简单。只需要将项目的target添加Mac即可。以前开发MacApp并不容易因为是单独的一套API。
由于没有模拟器,跑在Mac上是真实的设备,所以app需要sign。
Xcode12/Mac bigSur: 但是Mac Catalina也是可以的
Apple tutorial
一般的UI都是左右,类似与iPad的形式。