当我更改模型层的属性时,动画中的意外行为 [英] Unexpected behaviour in animation when i change the properties of the model layers

查看:63
本文介绍了当我更改模型层的属性时,动画中的意外行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

参考

然后我继续重构 setupLayers() animate()以从左到右水平运行动画(换句话说,我旋转90度)°顺时针向上表示上层).

更改代码以旋转动画后,我会遇到两个问题:

  1. 动画开始时, firstJointLayer 位置沿 perspectiveLayer 从左向右平移.公平地说,这应该是一种预期的行为,因为它是 perspectiveLayer 的子层,实际上我不确定为什么在原始项目中不会发生这种情况.但是,为解决此问题,我添加了另一个动画,负责在其相对系统中从右向左平移它,以便它实际上看起来是静止的.在这一点上,虽然我不更改模型层的最终值(项目下方的注释线),但动画按预期水平运行.如果我不必同时修改模型层,则可以达到我的目标,因为这正是我想要的动画.但是...

  2. ...如果我随后尝试设置动画的最终值(只需将行注释掉),则会得到意外的行为.在动画的初始帧处,红色,蓝色和灰色层看起来彼此折叠,因此旋转不再像预期的那样起作用.以下是时间0.0、0.5和1.0(持续时间:1.0)的一些快照:

对我而言,最不合逻辑的部分是,将模型层的值设置为与表示层的最终值相等会导致该错误,但它只会影响表示层,因为一旦动画位于下面的模型层上,就可以预期了(和想要的)旋转/位置:

当围绕正确的点旋转时,可以肯定地将锚点放置在正确的位置.我认为这可能与问题1有关,但是我尝试多次重新定位图层,但没有成功.直到今天,这个问题仍未解决,在两天内,我无法找到主要问题,因此无法解决.在我看来,原始项目(上方)和旋转项目(下方)在逻辑上看起来是相同的.

我在代码中发现了一个小错误,我正在从等于PerspectiveLayer x位置而不是他自己的x位置的起始值对firstJointLayer x位置进行动画处理,已修复它,但没有任何改变.

EDIT3 :由于将模型图层值设置为等于动画最终值是导致错误的原因,因此请注意,使用 animation.fillMode = CAMediaTimingFillMode.forwards animation.isRemovedOnCompletion = false 对于避免触及模态层不是可行的解决方法,因为我需要稍后还原动画,因此需要保持表示层和模型层同步.

我们非常感谢您的帮助.下面是旋转的项目-我还评论了我从上方项目更改的块:

 类ViewController:UIViewController {var transform:CATransform3D = CATransform3DIdentityvar topSleeve:CALayer = CALayer()var middleSleeve:CALayer = CALayer()var bottomSleeve:CALayer = CALayer()var topShadow:CALayer = CALayer()var middleShadow:CALayer = CALayer()宽度:CGFloat = 200身高:CGFloat = 300var firstJointLayer:CALayer = CATransformLayer()var secondJointLayer:CALayer = CATransformLayer()var sizeWidth:CGFloat = 0var positionX:CGFloat = 0var firstJointLayerPositionX:CGFloat = 0var PerspectiveLayer:CALayer = {让perspectiveLayer = CALayer()PerspectiveLayer.speed = 0.0PerspectiveLayer.fillMode =.已移除返回PerspectiveLayer}()var mainView:UIView = {让view = UIView()返回视图}()私人让滑杆:UISlider = {让滑块= UISlider()slide.addTarget(self,action:#selector(slide(sender:event :)),用于:.valueChanged)返回滑块}()覆盖func viewDidLoad(){super.viewDidLoad()view.addSubview(滑块)setupLayers()}覆盖func viewDidLayoutSubviews(){super.viewDidLayoutSubviews()slide.frame = CGRect(x:view.bounds.size.width/3,y:view.bounds.size.height/10 * 8,宽度:view.bounds.size.width/3,高度:view.bounds.size.height/10)}@objc私人功能幻灯片(发送方:UISlider,事件:UIEvent){如果让touchEvent = event.allTouches?.first {切换touchEvent.phase {案例结束:resumeLayer(layer:PerspectiveLayer)默认:PerspectiveLayer.timeOffset = CFTimeInterval(sender.value)}}}私人函式resumeLayer(图层:CALayer){让pausedTime = layer.timeOffsetlayer.speed = 1.0layer.timeOffset = 0.0layer.beginTime = 0.0让timeSincePause = layer.convertTime(CACurrentMediaTime(),from:nil)-暂停时间layer.beginTime = timeSincePause}私人功能setupLayers(){//在此更改所有锚点和位置,以便将整个-90°旋转mainView = UIView(框架:CGRect(x:50,y:50,宽度:宽度* 3,高度:高度))mainView.backgroundColor = UIColor.yellowview.addSubview(mainView)PerspectiveLayer.frame = CGRect(x:宽度,y:0,宽度:宽度* 2,高度:高度)PerspectiveLayer.backgroundColor = UIColor.cyan.cgColormainView.layer.addSublayer(perspectiveLayer)firstJointLayer.fillMode =.已移除firstJointLayer.frame = mainView.boundsfirstJointLayer.anchorPoint = CGPoint(x:1,y:0.5)firstJointLayer.position = CGPoint(x:宽度* 2,y:高度/2)PerspectiveLayer.addSublayer(firstJointLayer)topSleeve.fillMode =.已移除topSleeve.frame = CGRect(x:0,y:0,宽度:宽度,高度:高度)topSleeve.anchorPoint = CGPoint(x:1,y:0.5)topSleeve.backgroundColor = UIColor.red.cgColortopSleeve.position = CGPoint(x:宽度* 3,y:高度/2)firstJointLayer.addSublayer(topSleeve)topSleeve.masksToBounds = truesecondJointLayer.fillMode =.已移除secondJointLayer.frame = mainView.boundssecondJointLayer.frame = CGRect(x:0,y:0,宽度:宽度* 2,高度:高度)secondJointLayer.anchorPoint = CGPoint(x:1,y:0.5)secondJointLayer.position = CGPoint(x:宽度* 2,y:高度/2)firstJointLayer.addSublayer(secondJointLayer)secondJointLayer.fillMode =.已移除middleSleeve.frame = CGRect(x:0,y:0,宽度:宽度,高度:高度)middleSleeve.anchorPoint = CGPoint(x:1,y:0.5)middleSleeve.backgroundColor = UIColor.blue.cgColormiddleSleeve.position = CGPoint(x:宽度* 2,y:高度/2)secondJointLayer.addSublayer(middleSleeve)middleSleeve.masksToBounds = truebottomSleeve.fillMode =.已移除bottomSleeve.frame = CGRect(x:0,y:0,宽度:宽度,高度:高度)bottomSleeve.anchorPoint = CGPoint(x:1,y:0.5)bottomSleeve.backgroundColor = UIColor.gray.cgColorbottomSleeve.position = CGPoint(x:宽度,y:高度/2)secondJointLayer.addSublayer(bottomSleeve)topShadow.fillMode =.已移除topSleeve.addSublayer(topShadow)topShadow.frame = topSleeve.boundstopShadow.backgroundColor = UIColor.black.cgColortopShadow.opacity = 0middleShadow.fillMode =.已移除middleSleeve.addSublayer(middleShadow)middleShadow.frame = middleSleeve.boundsmiddleShadow.backgroundColor = UIColor.black.cgColormiddleShadow.opacity = 0transform.m34 = -1/700PerspectiveLayer.sublayerTransform =变换sizeWidth = PerspectiveLayer.bounds.size.widthpositionX = PerspectiveLayer.position.xfirstJointLayerPositionX = firstJointLayer.position.xanimate()}私人func animate(){CATransaction.begin()CATransaction.setDisableActions(true)CATransaction.setCompletionBlock {[弱自我]如果self == nil {return}self?.perspectiveLayer.speed = 0}//firstJointLayer.transform = CATransform3DMakeRotation(CGFloat(-85 * Double.pi/180),0,1,0)//secondJointLayer.transform = CATransform3DMakeRotation(CGFloat(170 * Double.pi/180),0,1,0)//bottomSleeve.transform = CATransform3DMakeRotation(CGFloat(-165 * Double.pi/180),0,1,0)//PerspectiveLayer.bounds.size.width = 0//PerspectiveLayer.position.x = 600//firstJointLayer.position.x = 0//topShadow.opacity = 0.5//middleShadow.opacity = 0.5var animation = CABasicAnimation(keyPath:"transform.rotation.y")animation.fillMode = CAMediaTimingFillMode.removedanimation.duration = 1animation.fromValue = 0animation.toValue = -85 * Double.pi/180firstJointLayer.add(animation,forKey:nil)动画= CABasicAnimation(keyPath:"transform.rotation.y")animation.fillMode = CAMediaTimingFillMode.removedanimation.duration = 1animation.fromValue = 0animation.toValue = 170 * Double.pi/180secondJointLayer.add(animation,forKey:nil)动画= CABasicAnimation(keyPath:"transform.rotation.y")animation.fillMode = CAMediaTimingFillMode.removedanimation.duration = 1animation.fromValue = 0animation.toValue = -165 * Double.pi/180bottomSleeve.add(animation,forKey:nil)动画= CABasicAnimation(keyPath:"bounds.size.width")animation.fillMode = CAMediaTimingFillMode.removedanimation.duration = 1animation.fromValue = sizeWidthanimation.toValue = 0PerspectiveLayer.add(animation,forKey:nil)动画= CABasicAnimation(keyPath:"position.x")animation.fillMode = CAMediaTimingFillMode.removedanimation.duration = 1animation.fromValue = positionXanimation.toValue = 600PerspectiveLayer.add(animation,forKey:无)//如上所述,我添加了此动画,该动画未包含在原始项目中,因为firstJointLayer正在将他的位置与PerspectiveLayer位置一起从左向右平移,因此我在其相对系统中进行了反向转换,以便固定在mainView系统中动画= CABasicAnimation(keyPath:"position.x")animation.fillMode = CAMediaTimingFillMode.removedanimation.duration = 1animation.fromValue = firstJointLayerPositionXanimation.toValue = 0firstJointLayer.add(animation,forKey:nil)动画= CABasicAnimation(keyPath:"opacity")animation.fillMode = CAMediaTimingFillMode.removedanimation.duration = 1animation.fromValue = 0animation.toValue = 0.5topShadow.add(动画,forKey:无)动画= CABasicAnimation(keyPath:"opacity")animation.fillMode = CAMediaTimingFillMode.removedanimation.duration = 1animation.fromValue = 0animation.toValue = 0.5middleShadow.add(animation,forKey:nil)CATransaction.commit()}} 

解决方案

好的-有点儿玩...

您似乎需要翻转动画,因为它们实际上是向后移动".

  private func animate(){CATransaction.begin()CATransaction.setDisableActions(true)CATransaction.setCompletionBlock {[弱自我]如果self == nil {return}//self?.perspectiveLayer.speed = 0}firstJointLayer.transform = CATransform3DMakeRotation(CGFloat(-85 * Double.pi/180),0,1,0)secondJointLayer.transform = CATransform3DMakeRotation(CGFloat(170 * Double.pi/180),0,1,0)bottomSleeve.transform = CATransform3DMakeRotation(CGFloat(-165 * Double.pi/180),0,1,0)PerspectiveLayer.bounds.size.width = 0PerspectiveLayer.position.x = 600firstJointLayer.position.x = 0topShadow.opacity = 0.5middleShadow.opacity = 0.5var animation = CABasicAnimation(keyPath:"transform.rotation.y")animation.fillMode = CAMediaTimingFillMode.removedanimation.duration = 1animation.fromValue = 0animation.toValue = -85 * Double.pi/180firstJointLayer.add(animation,forKey:nil)动画= CABasicAnimation(keyPath:"transform.rotation.y")animation.fillMode = CAMediaTimingFillMode.removedanimation.duration = 1//翻转180度animation.fromValue = 180 * Double.pi/180//到180-170animation.toValue = 10 * Double.pi/180secondJointLayer.add(animation,forKey:nil)动画= CABasicAnimation(keyPath:"transform.rotation.y")animation.fillMode = CAMediaTimingFillMode.removedanimation.duration = 1//翻转-180度animation.fromValue = -180 * Double.pi/180//到180-165animation.toValue = -15 * Double.pi/180bottomSleeve.add(animation,forKey:nil)动画= CABasicAnimation(keyPath:"bounds.size.width")animation.fillMode = CAMediaTimingFillMode.removedanimation.duration = 1animation.fromValue = sizeWidthanimation.toValue = 0PerspectiveLayer.add(animation,forKey:无)动画= CABasicAnimation(keyPath:"position.x")animation.fillMode = CAMediaTimingFillMode.removedanimation.duration = 1animation.fromValue = positionXanimation.toValue = 600PerspectiveLayer.add(animation,forKey:无)//如上所述,我添加了此动画,该动画未包含在原始项目中,因为firstJointLayer正在将他的位置与PerspectiveLayer位置一起从左向右平移,因此我在其相对系统中进行了反向转换,以便固定在mainView系统中动画= CABasicAnimation(keyPath:"position.x")animation.fillMode = CAMediaTimingFillMode.removedanimation.duration = 1animation.fromValue = firstJointLayerPositionXanimation.toValue = 0firstJointLayer.add(animation,forKey:nil)动画= CABasicAnimation(keyPath:"opacity")animation.fillMode = CAMediaTimingFillMode.removedanimation.duration = 1animation.fromValue = 0animation.toValue = 0.5topShadow.add(动画,forKey:无)动画= CABasicAnimation(keyPath:"opacity")animation.fillMode = CAMediaTimingFillMode.removedanimation.duration = 1animation.fromValue = 0animation.toValue = 0.5middleShadow.add(animation,forKey:nil)CATransaction.commit()} 

Referring to this post, i'm trying to adapt the animations to landscape mode. Basically what i want is to rotate all layers of -90° (90° clockwise) and the animations to run horizontally instead of vertically. The author didn't bother to explain the logic under the hood, there are a dozen paper folding libraries in obj-c which are all based on the same architecture, so apparently this is the way to go for folding.

EDIT: To further clarify what i want to achieve, here you can look at three snapshots (starting point, halftime and ending point) of the animations i want. In the question from the link up above the animation collapses from bottom to top, while i want it to collapse from left to right.

Down below you can take a look at the the original project a bit tweaked:

  • i changed the gray bottomSleeve layer final angle value, as well as the red and blue ones angle;
  • i paused the animations on initialization by setting the perspectiveLayer speed equal to 0 and added a slider, the slider value is then set equal to the perspectiveLayer timeOffset so that you can interactively run each frame of the animations by sliding. When the touch event on the slider ends, the animations are then resumed from the frame relative to the current timeOffset to the final value.
  • i changed all the model layers values before running each animation added to the relative presentation layer using CATransaction. Also, on completion the perspectiveLayer speed is set to 0 again.
  • for a better visual understanding, i set the perspectiveLayer backgroundColor equal to cyan.

Just to point it out, there are two main functions:

  1. setupLayers(), called in viewDidLoad() is responsible of setting up the layers positions and anchor points, as well as adding them as sublayers to the mainView layer.
  2. animate(), called recursively in setupLayers(), responsible of adding the animations. Here i also set the model layers values to the related animations final value before adding them.

Just copy, paste it and run:

class ViewController: UIViewController {

var transform: CATransform3D = CATransform3DIdentity
var topSleeve: CALayer = CALayer()
var middleSleeve: CALayer = CALayer()
var bottomSleeve: CALayer = CALayer()
var topShadow: CALayer = CALayer()
var middleShadow: CALayer = CALayer()
let width: CGFloat = 300
let height: CGFloat = 150
var firstJointLayer: CATransformLayer = CATransformLayer()
var secondJointLayer:CATransformLayer = CATransformLayer()
var sizeHeight: CGFloat = 0
var positionY: CGFloat = 0

var perspectiveLayer: CALayer = {
    let perspectiveLayer = CALayer()
    perspectiveLayer.speed = 0.0
    perspectiveLayer.fillMode = .removed
    return perspectiveLayer
}()

var mainView: UIView = {
    let view = UIView()
    return view
}()

private let slider: UISlider = {
    let slider = UISlider()
    slider.addTarget(self, action: #selector(slide(sender:event:)) , for: .valueChanged)
    return slider
}()

override func viewDidLoad() {
    super.viewDidLoad()
    view.addSubview(slider)
    setupLayers()
}

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    slider.frame = CGRect(x: view.bounds.size.width/3,
                          y: view.bounds.size.height/10*8,
                          width: view.bounds.size.width/3,
                          height: view.bounds.size.height/10)
}

@objc private func slide(sender: UISlider, event: UIEvent) {
    if let touchEvent = event.allTouches?.first {
        
        switch touchEvent.phase {
        case .ended:
            resumeLayer(layer: perspectiveLayer)
        default:
            perspectiveLayer.timeOffset = CFTimeInterval(sender.value)
        }
        
    }
}

private func resumeLayer(layer: CALayer) {
    let pausedTime = layer.timeOffset
    layer.speed = 1.0
    layer.timeOffset = 0.0
    layer.beginTime = 0.0
    let timeSincePause = layer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
    layer.beginTime = timeSincePause
}

private func setupLayers() {
    
    mainView = UIView(frame:CGRect(x: 50, y: 50, width: width, height: height*3))
    mainView.backgroundColor = UIColor.yellow
    view.addSubview(mainView)
    
    perspectiveLayer.frame = CGRect(x: 0, y: 0, width: width, height: height*2)
    perspectiveLayer.backgroundColor = UIColor.cyan.cgColor
    mainView.layer.addSublayer(perspectiveLayer)
    
    firstJointLayer.fillMode = .removed
    firstJointLayer.frame = mainView.bounds
    perspectiveLayer.addSublayer(firstJointLayer)
    
    topSleeve.fillMode = .removed
    topSleeve.frame = CGRect(x: 0, y: 0, width: width, height: height)
    topSleeve.anchorPoint = CGPoint(x: 0.5, y: 0)
    topSleeve.backgroundColor = UIColor.red.cgColor
    topSleeve.position = CGPoint(x: width/2, y: 0)
    firstJointLayer.addSublayer(topSleeve)
    topSleeve.masksToBounds = true
    
    secondJointLayer.fillMode = .removed
    secondJointLayer.frame = mainView.bounds
    secondJointLayer.frame = CGRect(x: 0, y: 0, width: width, height: height*2)
    secondJointLayer.anchorPoint = CGPoint(x: 0.5, y: 0)
    secondJointLayer.position = CGPoint(x: width/2, y: height)
    firstJointLayer.addSublayer(secondJointLayer)
    
    secondJointLayer.fillMode = .removed
    middleSleeve.frame = CGRect(x: 0, y: 0, width: width, height: height)
    middleSleeve.anchorPoint = CGPoint(x: 0.5, y: 0)
    middleSleeve.backgroundColor = UIColor.blue.cgColor
    middleSleeve.position = CGPoint(x: width/2, y: 0)
    secondJointLayer.addSublayer(middleSleeve)
    middleSleeve.masksToBounds = true
    
    bottomSleeve.fillMode = .removed
    bottomSleeve.frame = CGRect(x: 0, y: height, width: width, height: height)
    bottomSleeve.anchorPoint = CGPoint(x: 0.5, y: 0)
    bottomSleeve.backgroundColor = UIColor.gray.cgColor
    bottomSleeve.position = CGPoint(x: width/2, y: height)
    secondJointLayer.addSublayer(bottomSleeve)
    
    firstJointLayer.anchorPoint = CGPoint(x: 0.5, y: 0)
    firstJointLayer.position = CGPoint(x: width/2, y: 0)
    
    topShadow.fillMode = .removed
    topSleeve.addSublayer(topShadow)
    topShadow.frame = topSleeve.bounds
    topShadow.backgroundColor = UIColor.black.cgColor
    topShadow.opacity = 0
    
    middleShadow.fillMode = .removed
    middleSleeve.addSublayer(middleShadow)
    middleShadow.frame = middleSleeve.bounds
    middleShadow.backgroundColor = UIColor.black.cgColor
    middleShadow.opacity = 0
    
    transform.m34 = -1/700
    perspectiveLayer.sublayerTransform = transform
    
    sizeHeight = perspectiveLayer.bounds.size.height
    positionY = perspectiveLayer.position.y
    
    animate()
}


private func animate() {
    
    CATransaction.begin()
    
    CATransaction.setDisableActions(true)
    
    CATransaction.setCompletionBlock{ [weak self] in
        if self == nil { return }
        self?.perspectiveLayer.speed = 0
    }
    
    firstJointLayer.transform = CATransform3DMakeRotation(CGFloat(-85*Double.pi/180), 1, 0, 0)
    secondJointLayer.transform = CATransform3DMakeRotation(CGFloat(170*Double.pi/180), 1, 0, 0)
    bottomSleeve.transform = CATransform3DMakeRotation(CGFloat(-165*Double.pi/180), 1, 0, 0)
    perspectiveLayer.bounds.size.height = 0
    perspectiveLayer.position.y = 0
    topShadow.opacity = 0.5
    middleShadow.opacity = 0.5
    
    var animation = CABasicAnimation(keyPath: "transform.rotation.x")
    animation.fillMode = CAMediaTimingFillMode.removed
    animation.duration = 1
    animation.fromValue = 0
    animation.toValue = -85*Double.pi/180
    firstJointLayer.add(animation, forKey: nil)
    
    animation = CABasicAnimation(keyPath: "transform.rotation.x")
    animation.fillMode = CAMediaTimingFillMode.removed
    animation.duration = 1
    animation.fromValue = 0
    animation.toValue = 170*Double.pi/180
    secondJointLayer.add(animation, forKey: nil)
    
    animation = CABasicAnimation(keyPath: "transform.rotation.x")
    animation.fillMode = CAMediaTimingFillMode.removed
    animation.duration = 1
    animation.fromValue = 0
    animation.toValue = -165*Double.pi/180
    bottomSleeve.add(animation, forKey: nil)
    
    animation = CABasicAnimation(keyPath: "bounds.size.height")
    animation.fillMode = CAMediaTimingFillMode.removed
    animation.duration = 1
    animation.fromValue = sizeHeight
    animation.toValue = 0
    perspectiveLayer.add(animation, forKey: nil)
    
    
    animation = CABasicAnimation(keyPath: "position.y")
    animation.fillMode = CAMediaTimingFillMode.removed
    animation.duration = 1
    animation.fromValue = positionY
    animation.toValue = 0
    perspectiveLayer.add(animation, forKey: nil)
    
    animation = CABasicAnimation(keyPath: "opacity")
    animation.fillMode = CAMediaTimingFillMode.removed
    animation.duration = 1
    animation.fromValue = 0
    animation.toValue = 0.5
    topShadow.add(animation, forKey: nil)
    
    animation = CABasicAnimation(keyPath: "opacity")
    animation.fillMode = CAMediaTimingFillMode.removed
    animation.duration = 1
    animation.fromValue = 0
    animation.toValue = 0.5
    middleShadow.add(animation, forKey: nil)
    CATransaction.commit()

}
}

As you can see the animations run as expected, at this point in order to rotate the whole thing it should be just a matter of changing positions, anchor points and final animations values. Taken from an answer from the link above, here is a great representation of all the layers of the starting project:

Then i proceeded to refactor setupLayers() and animate() to run the animations horizontally, from left to right (in other words, i'm rotating of 90° clockwise the up above layers representation).

Once the code is changed to rotate the animations, i encounter two issues:

  1. when the animations start, the firstJointLayer position translate from left to right along the perspectiveLayer. To be fair to my understanding this should be an expected behaviour, as it is a sublayer of perspectiveLayer, actually i'm not sure why in the original project it doesn't happen. However, to fix this, i've added another animation responsible of translating it from right to left in its relative system, so that it actually appears stationary. At this point while i don't change the model layers final values (commented lines in the down below project), the animations run horizontally as expected. If i didn't have to also modify the model layers, my goal would be reached as this is the exact animation i want. However...

  2. ...if i then try to set the animations final values (just comment the lines out) i get an unexpected behaviour. At the initial frame of the animations, the red, blue and gray layers appear folded on each other, thus the rotations don't work as predicted anymore. Here are some snapshots at time 0.0, 0.5 and 1.0 (duration: 1.0):

The most illogical part to me is that setting the model layers values equal to the presentation layers final values causes the bug, but it only affects the presentation layers, as once the animations are over the model layers below are in the expected (and wanted) rotation/position:

The anchor points are for sure placed right as the rotations happen around the correct points. I think it may be related to issue 1., but i've tried to reposition the layers multiple times with no success. To the present day this is still unsolved, in two days i wasn't able to track down the primary issue and thus to fix it. To me the original project (up above) and the rotated project (down below) look the same in the logic under the hood.

EDIT2: i've found out a minor bug in the code, i was animating the firstJointLayer x position from a starting value equal to the perspectiveLayer x position instead of his own x position, i've fixed it but nothing changed.

EDIT3: Since setting the model layers values equal to the animation final values is what causes the bug, please note that using animation.fillMode = CAMediaTimingFillMode.forwards and animation.isRemovedOnCompletion = false is not a viable workaround for avoiding to touch the modal layers, as i need to revert the animation at a later time thus keeping presentation and model layers synced is required.

Any help is really appreciated. Down here the rotated project - i've also commented the blocks i've changed from the up above project:

  class ViewController: UIViewController {
    
var transform: CATransform3D = CATransform3DIdentity
var topSleeve: CALayer = CALayer()
var middleSleeve: CALayer = CALayer()
var bottomSleeve: CALayer = CALayer()
var topShadow: CALayer = CALayer()
var middleShadow: CALayer = CALayer()
let width: CGFloat = 200
let height: CGFloat = 300
var firstJointLayer: CALayer = CATransformLayer()
var secondJointLayer: CALayer = CATransformLayer()
var sizeWidth: CGFloat = 0
var positionX: CGFloat = 0
var firstJointLayerPositionX: CGFloat = 0


var perspectiveLayer: CALayer = {
    let perspectiveLayer = CALayer()
    perspectiveLayer.speed = 0.0
    perspectiveLayer.fillMode = .removed
    return perspectiveLayer
}()

var mainView: UIView = {
    let view = UIView()
    return view
}()

private let slider: UISlider = {
    let slider = UISlider()
    slider.addTarget(self, action: #selector(slide(sender:event:)) , for: .valueChanged)
    return slider
}()

override func viewDidLoad() {
    super.viewDidLoad()
    view.addSubview(slider)
    setupLayers()
}

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    slider.frame = CGRect(x: view.bounds.size.width/3,
                          y: view.bounds.size.height/10*8,
                          width: view.bounds.size.width/3,
                          height: view.bounds.size.height/10)

}

@objc private func slide(sender: UISlider, event: UIEvent) {
    if let touchEvent = event.allTouches?.first {
        
        switch touchEvent.phase {
        case .ended:
            resumeLayer(layer: perspectiveLayer)
        default:
                perspectiveLayer.timeOffset = CFTimeInterval(sender.value)

        }
        
    }
}

private func resumeLayer(layer: CALayer) {
    let pausedTime = layer.timeOffset
    layer.speed = 1.0
    layer.timeOffset = 0.0
    layer.beginTime = 0.0
    let timeSincePause = layer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime
    layer.beginTime = timeSincePause
}

private func setupLayers() {
    
   // Changing all anchor points and positions here, in order to rotate the whole thing of -90°

    mainView = UIView(frame:CGRect(x: 50, y: 50, width: width*3, height: height))
    mainView.backgroundColor = UIColor.yellow
    view.addSubview(mainView)
    
    perspectiveLayer.frame = CGRect(x: width, y: 0, width: width*2, height: height)
    perspectiveLayer.backgroundColor = UIColor.cyan.cgColor
    mainView.layer.addSublayer(perspectiveLayer)
    
    firstJointLayer.fillMode = .removed
    firstJointLayer.frame = mainView.bounds
    firstJointLayer.anchorPoint = CGPoint(x: 1, y: 0.5)
    firstJointLayer.position = CGPoint(x: width*2, y: height/2)
    perspectiveLayer.addSublayer(firstJointLayer)
    
    topSleeve.fillMode = .removed
    topSleeve.frame = CGRect(x: 0, y: 0, width: width, height: height)
    topSleeve.anchorPoint = CGPoint(x: 1, y: 0.5)
    topSleeve.backgroundColor = UIColor.red.cgColor
    topSleeve.position = CGPoint(x: width*3, y: height/2)
    firstJointLayer.addSublayer(topSleeve)
    topSleeve.masksToBounds = true
    
    secondJointLayer.fillMode = .removed
    secondJointLayer.frame = mainView.bounds
    secondJointLayer.frame = CGRect(x: 0, y: 0, width: width*2, height: height)
    secondJointLayer.anchorPoint = CGPoint(x: 1, y: 0.5)
    secondJointLayer.position = CGPoint(x: width*2, y: height/2)
    firstJointLayer.addSublayer(secondJointLayer)
    
    secondJointLayer.fillMode = .removed
    middleSleeve.frame = CGRect(x: 0, y: 0, width: width, height: height)
    middleSleeve.anchorPoint = CGPoint(x: 1, y: 0.5)
    middleSleeve.backgroundColor = UIColor.blue.cgColor
    middleSleeve.position = CGPoint(x: width*2, y: height/2)
    secondJointLayer.addSublayer(middleSleeve)
    middleSleeve.masksToBounds = true
    
    bottomSleeve.fillMode = .removed
    bottomSleeve.frame = CGRect(x: 0, y: 0, width: width, height: height)
    bottomSleeve.anchorPoint = CGPoint(x: 1, y: 0.5)
    bottomSleeve.backgroundColor = UIColor.gray.cgColor
    bottomSleeve.position = CGPoint(x: width, y: height/2)
    secondJointLayer.addSublayer(bottomSleeve)
    
    topShadow.fillMode = .removed
    topSleeve.addSublayer(topShadow)
    topShadow.frame = topSleeve.bounds
    topShadow.backgroundColor = UIColor.black.cgColor
    topShadow.opacity = 0
    
    middleShadow.fillMode = .removed
    middleSleeve.addSublayer(middleShadow)
    middleShadow.frame = middleSleeve.bounds
    middleShadow.backgroundColor = UIColor.black.cgColor
    middleShadow.opacity = 0
    
    transform.m34 = -1/700
    perspectiveLayer.sublayerTransform = transform
    
    sizeWidth = perspectiveLayer.bounds.size.width
    positionX = perspectiveLayer.position.x
    firstJointLayerPositionX = firstJointLayer.position.x

    
    animate()
}


private func animate() {
    
    CATransaction.begin()
    
    CATransaction.setDisableActions(true)
    
    CATransaction.setCompletionBlock{ [weak self] in
        if self == nil { return }
        self?.perspectiveLayer.speed = 0
    }
    
  //        firstJointLayer.transform = CATransform3DMakeRotation(CGFloat(-85*Double.pi/180), 0, 1, 0)
  //        secondJointLayer.transform = CATransform3DMakeRotation(CGFloat(170*Double.pi/180), 0, 1, 0)
  //        bottomSleeve.transform = CATransform3DMakeRotation(CGFloat(-165*Double.pi/180), 0, 1, 0)
  //        perspectiveLayer.bounds.size.width = 0
  //        perspectiveLayer.position.x = 600
  //        firstJointLayer.position.x = 0
  //        topShadow.opacity = 0.5
  //        middleShadow.opacity = 0.5
    
    var animation = CABasicAnimation(keyPath: "transform.rotation.y")
    animation.fillMode = CAMediaTimingFillMode.removed
    animation.duration = 1
    animation.fromValue = 0
    animation.toValue = -85*Double.pi/180
    firstJointLayer.add(animation, forKey: nil)
    
    animation = CABasicAnimation(keyPath: "transform.rotation.y")
    animation.fillMode = CAMediaTimingFillMode.removed
    animation.duration = 1
    animation.fromValue = 0
    animation.toValue = 170*Double.pi/180
    secondJointLayer.add(animation, forKey: nil)
    
    animation = CABasicAnimation(keyPath: "transform.rotation.y")
    animation.fillMode = CAMediaTimingFillMode.removed
    animation.duration = 1
    animation.fromValue = 0
    animation.toValue = -165*Double.pi/180
    bottomSleeve.add(animation, forKey: nil)
    
    animation = CABasicAnimation(keyPath: "bounds.size.width")
    animation.fillMode = CAMediaTimingFillMode.removed
    animation.duration = 1
    animation.fromValue = sizeWidth
    animation.toValue = 0
    perspectiveLayer.add(animation, forKey: nil)
    
    animation = CABasicAnimation(keyPath: "position.x")
    animation.fillMode = CAMediaTimingFillMode.removed
    animation.duration = 1
    animation.fromValue = positionX
    animation.toValue = 600
    perspectiveLayer.add(animation, forKey: nil)

  // As said above, i added this animation which is not included in the original project, as the firstJointLayer was translating his position from left to right along with the perspectiveLayer position, so i make a reverse translation in its relative system so that it is stationary in the mainView system

    animation = CABasicAnimation(keyPath: "position.x")
    animation.fillMode = CAMediaTimingFillMode.removed
    animation.duration = 1
    animation.fromValue = firstJointLayerPositionX 
    animation.toValue = 0
    firstJointLayer.add(animation, forKey: nil)
    
    animation = CABasicAnimation(keyPath: "opacity")
    animation.fillMode = CAMediaTimingFillMode.removed
    animation.duration = 1
    animation.fromValue = 0
    animation.toValue = 0.5
    topShadow.add(animation, forKey: nil)
    
    animation = CABasicAnimation(keyPath: "opacity")
    animation.fillMode = CAMediaTimingFillMode.removed
    animation.duration = 1
    animation.fromValue = 0
    animation.toValue = 0.5
    middleShadow.add(animation, forKey: nil)
    
    CATransaction.commit()

}

}

解决方案

OK - a bit of playing around...

Looks like you need to flip the animations, since they're effectively "going backward."

private func animate() {
    
    CATransaction.begin()
    
    CATransaction.setDisableActions(true)
    
    CATransaction.setCompletionBlock{ [weak self] in
        if self == nil { return }
        //self?.perspectiveLayer.speed = 0
    }
    
    firstJointLayer.transform = CATransform3DMakeRotation(CGFloat(-85*Double.pi/180), 0, 1, 0)
    secondJointLayer.transform = CATransform3DMakeRotation(CGFloat(170*Double.pi/180), 0, 1, 0)
    bottomSleeve.transform = CATransform3DMakeRotation(CGFloat(-165*Double.pi/180), 0, 1, 0)
    perspectiveLayer.bounds.size.width = 0
    perspectiveLayer.position.x = 600
    firstJointLayer.position.x = 0
    topShadow.opacity = 0.5
    middleShadow.opacity = 0.5

    var animation = CABasicAnimation(keyPath: "transform.rotation.y")
    animation.fillMode = CAMediaTimingFillMode.removed
    animation.duration = 1
    animation.fromValue = 0
    animation.toValue = -85*Double.pi/180
    firstJointLayer.add(animation, forKey: nil)
    
    animation = CABasicAnimation(keyPath: "transform.rotation.y")
    animation.fillMode = CAMediaTimingFillMode.removed
    animation.duration = 1
    // flip 180 degrees
    animation.fromValue = 180*Double.pi/180
    // to 180 - 170
    animation.toValue = 10*Double.pi/180
    secondJointLayer.add(animation, forKey: nil)
    
    animation = CABasicAnimation(keyPath: "transform.rotation.y")
    animation.fillMode = CAMediaTimingFillMode.removed
    animation.duration = 1
    // flip -180 degrees
    animation.fromValue = -180*Double.pi/180
    // to 180 - 165
    animation.toValue = -15*Double.pi/180
    bottomSleeve.add(animation, forKey: nil)
    
    animation = CABasicAnimation(keyPath: "bounds.size.width")
    animation.fillMode = CAMediaTimingFillMode.removed
    animation.duration = 1
    animation.fromValue = sizeWidth
    animation.toValue = 0
    perspectiveLayer.add(animation, forKey: nil)
    
    animation = CABasicAnimation(keyPath: "position.x")
    animation.fillMode = CAMediaTimingFillMode.removed
    animation.duration = 1
    animation.fromValue = positionX
    animation.toValue = 600
    perspectiveLayer.add(animation, forKey: nil)
    
    // As said above, i added this animation which is not included in the original project, as the firstJointLayer was translating his position from left to right along with the perspectiveLayer position, so i make a reverse translation in its relative system so that it is stationary in the mainView system
    
    animation = CABasicAnimation(keyPath: "position.x")
    animation.fillMode = CAMediaTimingFillMode.removed
    animation.duration = 1
    animation.fromValue = firstJointLayerPositionX
    animation.toValue = 0
    firstJointLayer.add(animation, forKey: nil)
    
    animation = CABasicAnimation(keyPath: "opacity")
    animation.fillMode = CAMediaTimingFillMode.removed
    animation.duration = 1
    animation.fromValue = 0
    animation.toValue = 0.5
    topShadow.add(animation, forKey: nil)
    
    animation = CABasicAnimation(keyPath: "opacity")
    animation.fillMode = CAMediaTimingFillMode.removed
    animation.duration = 1
    animation.fromValue = 0
    animation.toValue = 0.5
    middleShadow.add(animation, forKey: nil)
    
    CATransaction.commit()
    
}

这篇关于当我更改模型层的属性时,动画中的意外行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆