UIViewPropetyAnimator crossfade

91 views Asked by At

I would like to use a property animator, as it gives me control over stopping the animation and its progress. I'm trying to achieve a cross-dissolve effect when setting the image of an image view. I can do this easily with a view animation like so:

UIView.transition(
    with: imageView,
    duration: 2,
    options: .transitionCrossDissolve,
    animations: { [weak self] in
        self?.imageView.image = anImage
    },
    completion: {
        // do something
    }
) 

I tried something along the lines of:

let animator = UIViewPropertyAnimator(duration: 2, curve: .linear)
animator.addAnimations {
    self.imageView.alpha = 0
}
animator.addAnimations {
    self.imageView.image = image
}
animator.addAnimations {
    self.imageView.alpha = 1
}
animator.startAnimation()

Any help would be greatly appreciated.

1

There are 1 answers

0
DonMag On

I think you'll have much better luck using two image views -- one on top of the other.

Then use UIViewPropertyAnimator to animate the .alpha property of the "on top" image view.

Quick example:

class FadeVC: UIViewController {
    
    var animator: UIViewPropertyAnimator!
    
    let imgViewA = UIImageView()
    let imgViewB = UIImageView()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        guard let imgA = UIImage(systemName: "apple.logo"),
              let imgB = UIImage(systemName: "swift")
        else {
            fatalError("Could not load images!")
        }

        imgViewA.image = imgA
        imgViewB.image = imgB

        imgViewA.backgroundColor = .systemBlue
        imgViewA.tintColor = .cyan

        imgViewB.backgroundColor = .systemYellow
        imgViewB.tintColor = .systemRed
        
        imgViewA.contentMode = .scaleAspectFit
        imgViewB.contentMode = .scaleAspectFit

        view.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
        
        let slider = UISlider()

        [imgViewA, imgViewB, slider].forEach { v in
            v.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(v)
        }
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            
            imgViewA.widthAnchor.constraint(equalToConstant: 300.0),
            imgViewA.heightAnchor.constraint(equalTo: imgViewA.widthAnchor),
            imgViewA.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            imgViewA.centerYAnchor.constraint(equalTo: g.centerYAnchor),
            
            imgViewB.topAnchor.constraint(equalTo: imgViewA.topAnchor),
            imgViewB.leadingAnchor.constraint(equalTo: imgViewA.leadingAnchor),
            imgViewB.trailingAnchor.constraint(equalTo: imgViewA.trailingAnchor),
            imgViewB.bottomAnchor.constraint(equalTo: imgViewA.bottomAnchor),

            slider.topAnchor.constraint(equalTo: imgViewA.bottomAnchor, constant: 40.0),
            slider.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            slider.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            
        ])
        
        // "top" image view starts fully transparent
        imgViewB.alpha = 0.0
        
        slider.addTarget(self, action: #selector(sliderChanged), for: .valueChanged)
        
        animator = UIViewPropertyAnimator(duration: 1, curve: .linear)
        { [unowned self, imgViewB] in
            imgViewB.alpha = 1.0
        }

    }

    @objc func sliderChanged(_ sender: UISlider) {
        animator.fractionComplete = CGFloat(sender.value)
    }
    
}

Looks like this... as you drag the slider back and forth, it animates the "swift" logo image view's alpha:

enter image description here enter image description here

enter image description here enter image description here