在动画中用 Swift 做一个简单的渐变?

我试图在斯威夫特制作一个简单的动画。这是一个淡入。

我试图:

self.myFirstLabel.alpha = 0
self.myFirstButton.alpha = 0
self.mySecondButton.alpha = 0

然后,我有:

self.view.addSubview(myFirstLabel)
self.view.addSubview(myFirstButton)
self.view.addSubview(mySecondButton)

然后:

UIView.animateWithDuration(1.5, animations: {
self.myFirstLabel.alpha = 1.0
self.myFirstButton.alpha = 1.0
self.mySecondButton.alpha = 1.0
})

我在 viewDidLoad 函数中有所有这些。

我该怎么做?

120874 次浏览

The problem is that you're trying start the animation too early in the view controller's lifecycle. In viewDidLoad, the view has just been created, and hasn't yet been added to the view hierarchy, so attempting to animate one of its subviews at this point produces bad results.

What you really should be doing is continuing to set the alpha of the view in viewDidLoad (or where you create your views), and then waiting for the viewDidAppear: method to be called. At this point, you can start your animations without any issue.

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)


UIView.animate(withDuration: 1.5) {
self.myFirstLabel.alpha = 1.0
self.myFirstButton.alpha = 1.0
self.mySecondButton.alpha = 1.0
}
}

0x7ffffff's answer is ok and definitely exhaustive.

As a plus, I suggest you to make an UIView extension, in this way:

public extension UIView {


/**
Fade in a view with a duration


- parameter duration: custom animation duration
*/
func fadeIn(duration duration: NSTimeInterval = 1.0) {
UIView.animateWithDuration(duration, animations: {
self.alpha = 1.0
})
}


/**
Fade out a view with a duration


- parameter duration: custom animation duration
*/
func fadeOut(duration duration: NSTimeInterval = 1.0) {
UIView.animateWithDuration(duration, animations: {
self.alpha = 0.0
})
}


}

Swift-3

/// Fade in a view with a duration
///
/// Parameter duration: custom animation duration
func fadeIn(withDuration duration: TimeInterval = 1.0) {
UIView.animate(withDuration: duration, animations: {
self.alpha = 1.0
})
}


/// Fade out a view with a duration
///
/// - Parameter duration: custom animation duration
func fadeOut(withDuration duration: TimeInterval = 1.0) {
UIView.animate(withDuration: duration, animations: {
self.alpha = 0.0
})
}

Swift-5

public extension UIView {


/**
Fade in a view with a duration
 

- parameter duration: custom animation duration
*/
func fadeIn(duration: TimeInterval = 1.0) {
UIView.animate(withDuration: duration, animations: {
self.alpha = 1.0
})
}


/**
Fade out a view with a duration
 

- parameter duration: custom animation duration
*/
func fadeOut(duration: TimeInterval = 1.0) {
UIView.animate(withDuration: duration, animations: {
self.alpha = 0.0
})
}


}

In this way you can do this wherever in your code:

let newImage = UIImage(named: "")
newImage.alpha = 0 // or newImage.fadeOut(duration: 0.0)
self.view.addSubview(newImage)
...
newImage.fadeIn()

Code reuse is important!

Swift only solution

Similar to Luca's anwer, I use a UIView extension. Compared to his solution I use DispatchQueue.main.async to make sure animations are done on the main thread, alpha parameter for fading to a specific value and optional duration parameters for cleaner code.

extension UIView {
func fadeTo(_ alpha: CGFloat, duration: TimeInterval = 0.3) {
DispatchQueue.main.async {
UIView.animate(withDuration: duration) {
self.alpha = alpha
}
}
}


func fadeIn(_ duration: TimeInterval = 0.3) {
fadeTo(1.0, duration: duration)
}


func fadeOut(_ duration: TimeInterval = 0.3) {
fadeTo(0.0, duration: duration)
}
}

How to use it:

// fadeIn() - always animates to alpha = 1.0
yourView.fadeIn()     // uses default duration of 0.3
yourView.fadeIn(1.0)  // uses custom duration (1.0 in this example)


// fadeOut() - always animates to alpha = 0.0
yourView.fadeOut()    // uses default duration of 0.3
yourView.fadeOut(1.0) // uses custom duration (1.0 in this example)


// fadeTo() - used if you want a custom alpha value
yourView.fadeTo(0.5)  // uses default duration of 0.3
yourView.fadeTo(0.5, duration: 1.0)

If you want repeatable fade animation you can do that by using CABasicAnimation like below :

First create handy UIView extension :

extension UIView {


enum AnimationKeyPath: String {
case opacity = "opacity"
}


func flash(animation: AnimationKeyPath ,withDuration duration: TimeInterval = 0.5, repeatCount: Float = 5){
let flash = CABasicAnimation(keyPath: animation.rawValue)
flash.duration = duration
flash.fromValue = 1 // alpha
flash.toValue = 0 // alpha
flash.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
flash.autoreverses = true
flash.repeatCount = repeatCount


layer.add(flash, forKey: nil)
}
}

How to use it:

    // You can use it with all kind of UIViews e.g. UIButton, UILabel, UIImage, UIImageView, ...
imageView.flash(animation: .opacity, withDuration: 1, repeatCount: 5)
titleLabel.flash(animation: .opacity, withDuration: 1, repeatCount: 5)
import UIKit


/*
Here is simple subclass for CAAnimation which create a fadeIn animation
*/


class FadeInAdnimation: CABasicAnimation {
override init() {
super.init()
keyPath = "opacity"
duration = 2.0
fromValue = 0
toValue = 1
fillMode = CAMediaTimingFillMode.forwards
isRemovedOnCompletion = false
}


required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}


/*
Example of usage
*/


class ViewController: UIViewController {


weak var label: UILabel!


override func loadView() {
let view = UIView()
view.backgroundColor = .white


let label = UILabel()
label.alpha = 0
label.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
label.text = "Hello World!"
label.textColor = .black
view.addSubview(label)
self.label = label


let button = UIButton(type: .custom)
button.frame = CGRect(x: 0, y: 250, width: 300, height: 100)
button.setTitle("Press to Start FadeIn", for: UIControl.State())
button.backgroundColor = .red
button.addTarget(self, action: #selector(startFadeIn), for: .touchUpInside)
view.addSubview(button)


self.view = view
}


/*
Animation in action
*/
@objc private func startFadeIn() {
label.layer.add(FadeInAdnimation(), forKey: "fadeIn")
}


}

Swift 5

Other answers are correct, but in my case I need to handle other properties also (alpha, animate, completion). Because of this, I modified a bit to expose these parameters as below:

extension UIView {
/// Helper function to update view's alpha with animation
/// - Parameter alpha: View's alpha
/// - Parameter animate: Indicate alpha changing with animation or not
/// - Parameter duration: Indicate time for animation
/// - Parameter completion: Completion block after alpha changing is finished
func set(alpha: CGFloat, animate: Bool, duration: TimeInterval = 0.3, completion: ((Bool) -> Void)? = nil) {
let animation = { (view: UIView) in
view.alpha = alpha
}
    

if animate {
UIView.animate(withDuration: duration, animations: {
animation(self)
}, completion: { finished in
completion?(finished)
})
} else {
layer.removeAllAnimations()
animation(self)
completion?(true)
}
}
}