当 UIView.layer.clipsToBounds = true 与 CABasicAnimation 结合使用时,并非所有像素都被剪裁

Not all pixels clipped when using UIView.layer.clipsToBounds = true in conjunction with CABasicAnimation

我在做什么: 我正在创建一个音乐应用程序。在这个音乐应用程序中,我有一个与 Apple Music 和 Spotify 链接的音乐播放器。音乐播放器在 UIImageView 上显示当前歌曲的专辑封面。每当播放歌曲时,UIImageView 都会旋转(就像唱机上的唱片一样)。

我的问题是: UIImageView 的专辑封面默认为正方形。我将 UIImageViewlayer 的角四舍五入并将 UIImageView.clipsToBounds 设置为等于 true 以使其显示为一个圆圈。 每当 UIImageView 旋转时,UIImageViewlayer 之外的一些像素(图像四舍五入后被切除的部分)会渗出。

漏洞如下所示: https://www.youtube.com/watch?v=OJxX5PQc7Jo&feature=youtu.be

我的代码: 通过将 layercornerRadius 设置为 UIImageView.frame.height / 2 并设置 UIImageView.clipsToBounds = true:

来舍入 UIImageView
class MyViewController: UIViewController {

    @IBOutlet var albumArtImageView: UIImageView()

    override func viewDidLoad() {
        super.viewDidLoad()
        albumArtImageView.layer.cornerRadius = albumArtImageView.frame.height / 2
        albumArtImageView.clipsToBounds = true

        //I've also tried the following code, and am getting the same behavior:
        /*
        albumArtImageView.layer.cornerRadius = albumArtImageView.frame.height / 2
        albumArtImageView.layer.masksToBounds = true
        albumArtImageView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner,.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
        */
    }
}

只要按下一个按钮,UIImageView 就会开始旋转。我使用了以下 extension 来进行 UIView 的旋转:

extension UIView {
    func rotate(duration: Double = 1, startPoint: CGFloat) {
        if layer.animation(forKey: UIView.kRotationAnimationKey) == nil {
            let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation")

            rotationAnimation.fromValue = startPoint
            rotationAnimation.toValue = (CGFloat.pi * 2.0) + startPoint
            rotationAnimation.duration = duration
            rotationAnimation.repeatCount = Float.infinity

            layer.add(rotationAnimation, forKey: UIView.kRotationAnimationKey)
        }
    }
}

我还有以下扩展来结束轮换:

extension UIView {

    ...

    func stopRotating(beginTime: Double!, startingAngle: CGFloat) -> CGFloat? {
        if layer.animation(forKey: UIView.kRotationAnimationKey) != nil {
            let animation = layer.animation(forKey: UIView.kRotationAnimationKey)!
            let elapsedTime = CACurrentMediaTime() - beginTime
            let angle = elapsedTime.truncatingRemainder(dividingBy: animation.duration)/animation.duration

            layer.transform = CATransform3DMakeRotation((CGFloat(angle) * (2 * CGFloat.pi)) + startingAngle, 0.0, 0.0, 1.0)
            layer.removeAnimation(forKey: UIView.kRotationAnimationKey)
            return (CGFloat(angle) * (2 * CGFloat.pi)) + startingAngle
        } else {
            return nil
        }
    }
}

这是在我的视图控制器上下文中使用这些扩展函数的方式:

class MyViewController: UIViewController {

    var songBeginTime: Double!
    var currentRecordAngle: CGFloat = 0.0
    var isPlaying = false

    ...

    @IBAction func playButtonPressed(_ sender: Any) {
        if isPlaying {
            if let angle = albumArtImageView.stopRotating(beginTime: songBeginTime, startingAngle: currentRecordAngle) {
                currentRecordAngle = angle
            }
            songBeginTime = nil
        } else {
            songBeginTime = CACurrentMediaTime()
            albumArtImageView.rotate(duration: 3, startPoint: currentRecordAngle)
        }
    }
}

所以,总的来说,MyViewController 看起来像这样:

class MyViewController: UIViewController {

    @IBOutlet var albumArtImageView: UIImageView()

    var songBeginTime: Double!
    var currentRecordAngle: CGFloat = 0.0
    var isPlaying = false

    override func viewDidLoad() {
        super.viewDidLoad()
        albumArtImageView.layer.cornerRadius = albumArtImageView.frame.height / 2
        albumArtImageView.clipsToBounds = true
    }

    @IBAction func playButtonPressed(_ sender: Any) {
        if isPlaying {
            if let angle = albumArtImageView.stopRotating(beginTime: songBeginTime, startingAngle: currentRecordAngle) {
                currentRecordAngle = angle
            }
            songBeginTime = nil
        } else {
            songBeginTime = CACurrentMediaTime()
            albumArtImageView.rotate(duration: 3, startPoint: currentRecordAngle)
        }
    }
}

剪辑到边界不包括圆角。尝试改用角遮罩。

class MyViewController: UIViewController {

    @IBOutlet var albumArtImageView: UIImageView()

    var songBeginTime: Double!
    var currentRecordAngle: CGFloat = 0.0
    var isPlaying = false

    override func viewDidLoad() {
        super.viewDidLoad()
        albumArtImageView.layer.cornerRadius = albumArtImageView.frame.height / 2
        albumArtImageView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner,.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
    }

    @IBAction func playButtonPressed(_ sender: Any) {
        if isPlaying {
            if let angle = albumArtImageView.stopRotating(beginTime: songBeginTime, startingAngle: currentRecordAngle) {
                currentRecordAngle = angle
            }
            songBeginTime = nil
        } else {
            songBeginTime = CACurrentMediaTime()
            albumArtImageView.rotate(duration: 3, startPoint: currentRecordAngle)
        }
    }
}

我将你的代码复制到项目中,我可以重现这个问题。但是,如果将动画添加到另一个 CALayer,这似乎可以解决问题。

extension UIView {

static var kRotationAnimationKey: String {
    return "kRotationAnimationKey"
}

func makeAnimationLayer() -> CALayer {

    let results: [CALayer]? = layer.sublayers?.filter({ [=10=].name ?? "" == "animationLayer" })

    let animLayer: CALayer
    if let sublayers = results, sublayers.count > 0 {
        animLayer = sublayers[0]
    }
    else {
        animLayer = CAShapeLayer()
        animLayer.name = "animationLayer"
        animLayer.frame = self.bounds
        animLayer.contents = UIImage(named: "imageNam")?.cgImage
        layer.addSublayer(animLayer)
    }

    return animLayer
}

func rotate(duration: Double = 1, startPoint: CGFloat) {
    if layer.animation(forKey: UIView.kRotationAnimationKey) == nil {

        let animLayer = makeAnimationLayer()

        let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation")
        rotationAnimation.fromValue = startPoint
        rotationAnimation.toValue = (CGFloat.pi * 2.0) + startPoint
        rotationAnimation.duration = duration
        rotationAnimation.repeatCount = Float.infinity
        animLayer.add(rotationAnimation, forKey: UIView.kRotationAnimationKey)
    }
}

func stopRotating(beginTime: Double!, startingAngle: CGFloat) -> CGFloat? {

    let animLayer = makeAnimationLayer()

    if animLayer.animation(forKey: UIView.kRotationAnimationKey) != nil {
        let animation = animLayer.animation(forKey: UIView.kRotationAnimationKey)!
        let elapsedTime = CACurrentMediaTime() - beginTime
        let angle = elapsedTime.truncatingRemainder(dividingBy: animation.duration)/animation.duration
        animLayer.transform = CATransform3DMakeRotation(CGFloat(angle) * (2 * CGFloat.pi) + startingAngle, 0.0, 0.0, 1.0)
        animLayer.removeAnimation(forKey: UIView.kRotationAnimationKey)
        return (CGFloat(angle) * (2 * CGFloat.pi)) + startingAngle
    }
    else {
        return nil
    }
}