UIView 仅在一个视图控制器中设置动画

UIView animate in ONLY ONE view controller

所以我在 swift 中使用容器视图来获取在导航控制器中的多个视图控制器之间共享的叠加视图。

它的工作方式是我有许多子视图为流程创建 "background"。在这些视图之上,我有一个容器视图,其中嵌入了一个导航控制器。该导航控制器通过多个视图控制器引导用户完成入职流程。在这个容器视图上方是 "progress view," ,它的前缘与父视图的前缘对齐,并且随着用户在引导过程中导航而增加宽度,直到最终,栏的宽度等于屏幕的宽度并且用户已经创建了他们的个人资料。 这是它的外观示例: Skip to 1:53 to see the main issue

这是包含视图控制器的样子。注意背景的分层,然后是容器视图,然后是进度条视图:

此视图控制器有一个名为 updateProgressBar 的函数,它获取一定比例的屏幕宽度来制作动画,然后执行动画。

func updateProgressBar(to percentAsDecimal: CGFloat!) {
    UIView.animate(withDuration: 0.25) {
        self.progressBarWidth.constant = self.view.frame.width * percentAsDecimal
        self.view.layoutIfNeeded()
    }
}

问题: 因为包含的导航控制器在进度条的动画期间推动了一个新的视图控制器,所以当这个新的视图控制器布置它的子视图时,这个布置是动画的。

我尝试过的: 我创建了自定义 类,它们是 UIView 的子 类,它使用 UIView.performWithoutAnimation 函数来避免子视图的这种动画效果。

import UIKit

class NonAnimatingView: UIView {
    override func layoutSubviews() {
        UIView.performWithoutAnimation {
            super.layoutSubviews()
        }
    }
}

我用按钮做了类似的事情。

import UIKit

class NonAnimatingButton: UIButton {
    override func layoutSubviews() {
        UIView.performWithoutAnimation {
            super.layoutSubviews()
        }
    }
}

然而,当我尝试使用 UISearchBar 执行此操作时,部分动画已解析,但不是全部。

import UIKit

class NonAnimatingSearchBar: UISearchBar {
    override func layoutSubviews() {
        UIView.performWithoutAnimation {
            super.layoutSubviews()
        }
    }

    override func addSubview(_ view: UIView) {
        UIView.performWithoutAnimation {
            super.addSubview(view)
        }
    }

    override func insertSubview(_ view: UIView, at index: Int) {
        UIView.performWithoutAnimation {
            super.insertSubview(view, at: index)
        }
    }

    override func insertSubview(_ view: UIView, aboveSubview siblingSubview: UIView) {
        UIView.performWithoutAnimation {
            super.insertSubview(view, aboveSubview: siblingSubview)
        }
    }

    override func insertSubview(_ view: UIView, belowSubview siblingSubview: UIView) {
        UIView.performWithoutAnimation {
            super.insertSubview(view, belowSubview: siblingSubview)
        }
    }

    override func sendSubviewToBack(_ view: UIView) {
        UIView.performWithoutAnimation {
            super.sendSubviewToBack(view)
        }
    }

    override func bringSubviewToFront(_ view: UIView) {
        UIView.performWithoutAnimation {
            super.bringSubviewToFront(view)
        }
    }

    override func exchangeSubview(at index1: Int, withSubviewAt index2: Int) {
        UIView.performWithoutAnimation {
            super.exchangeSubview(at: index1, withSubviewAt: index2)
        }
    }
}

我还确保在推送导航控制器视图控制器之前调用的任何布局更改都不会被动画化:

override func viewDidLoad() {
    UIView.performWithoutAnimation {
        super.viewDidLoad()
        femaleButton.backgroundColor = UIColor.lightGray.withAlphaComponent(0.25)
        maleButton.backgroundColor = UIColor.lightGray.withAlphaComponent(0.25)
        otherGenderButton.backgroundColor = UIColor.lightGray.withAlphaComponent(0.25)
        femaleButton.layer.cornerRadius = 5
        maleButton.layer.cornerRadius = 5
        otherGenderButton.layer.cornerRadius = 5
        nextButton.layer.cornerRadius = nextButton.frame.height/2
    }
}

我想知道的是: 有没有我可以调用而不是 UIView.animate 的东西,这样我就可以在指定的视图控制器中设置动画而不影响当前可见的所有视图控制器?如果没有,解决此问题的最佳方法是什么?

所以,我最终采纳了@rs7 的建议并改用了CAShapeLayer。我没有使用界面生成器,而是继续创建一个 CAShapeLayer 并将其添加到 viewDidLayoutSubviews()

var progressBar: CAShapeLayer!

override func viewDidLayoutSubviews() {

    progressBar = CAShapeLayer()
    progressBar.bounds = CGRect(x: 0, y: 0, width: 0, height: view.safeAreaInsets.top)
    progressBar.position = CGPoint(x: 0, y: 0)
    progressBar.anchorPoint = CGPoint(x: 0, y: 0)
    progressBar.backgroundColor = UIColor.secondaryColor.cgColor
    view.layer.addSublayer(progressBar)
}

然后我继续更新 updateProgressBar 以调整 CALayer 的大小,而不是 NSLayoutConstraint 常量。

func updateProgressBar(to percentAsDecimal: CGFloat!) {
    let newWidth = view.bounds.width * percentAsDecimal
    CATransaction.begin()
    CATransaction.setCompletionBlock({
        self.progressBar.bounds.size.width = newWidth
    })
    CATransaction.commit()
    let anim = CABasicAnimation(keyPath: "bounds")
    anim.isRemovedOnCompletion = false
    anim.fillMode = .forwards
    anim.duration = 0.25
    anim.fromValue = NSValue(cgRect: CGRect(x: 0, y: 0, width: progressBar.bounds.width, height: view.safeAreaInsets.top))
    anim.toValue = NSValue(cgRect: CGRect(x: 0, y: 0, width: newWidth, height: view.safeAreaInsets.top))
    progressBar.add(anim, forKey: "anim")
}

现在,不再需要我的自定义 NonAnimatingViewNonAnimatingButtonNonAnimatingSearchBar。 :)