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

Posted

技术标签:

【中文标题】将 UIView.layer.clipsToBounds = true 与 CABasicAnimation 结合使用时,并非所有像素都被裁剪【英文标题】:Not all pixels clipped when using UIView.layer.clipsToBounds = true in conjunction with CABasicAnimation 【发布时间】:2019-10-18 16:55:59 【问题描述】:

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

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

这是错误的样子: https://www.youtube.com/watch?v=OJxX5PQc7Jo&feature=youtu.be

我的代码: UIImageView 通过将其layercornerRadius 设置为等于UIImageView.frame.height / 2 并设置UIImageView.clipsToBounds = true 来进行舍入:

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)
        
    

【问题讨论】:

【参考方案1】:

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

extension UIView 

static var kRotationAnimationKey: String 
    return "kRotationAnimationKey"


func makeAnimationLayer() -> CALayer 

    let results: [CALayer]? = layer.sublayers?.filter( $0.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
    

【讨论】:

这非常令人鼓舞。让我尝试实现它并回复您! 完美运行。所以让我等了两个小时才给你赏金,所以你今天晚些时候会得到它:)【参考方案2】:

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

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)
        
    

【讨论】:

我通过albumArtImageView.layer.cornerRadius = albumArtImageView.frame.height / 2albumArtImageView.layer.masksToBounds = truealbumArtImageView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner,.layerMinXMaxYCorner, .layerMaxXMaxYCorner] 实现了这一点,不幸的是,我得到了同样的行为。

以上是关于将 UIView.layer.clipsToBounds = true 与 CABasicAnimation 结合使用时,并非所有像素都被裁剪的主要内容,如果未能解决你的问题,请参考以下文章

如何将Ios文件上传到

Javascript 将正则表达式 \\n 替换为 \n,将 \\t 替换为 \t,将 \\r 替换为 \r 等等

如何将视频文件转换格式

sh 一个将生成CA的脚本,将CA导入到钥匙串中,然后它将创建一个证书并与CA签名,然后将其导入到

python怎么将0写入文件?

如何将CMD窗口背景改成透明?