This question could be closed as a duplicate, but because there are a couple things that are different and the solution may not completely obvious I'm posting this answer...
For reference, this would be the duplicate post: https://stackoverflow.com/a/50074722/6257435
and the related code can be found here: https://gist.github.com/ArtFeel/ad4b108f026e53723c7457031e291bc8
However, links can go away, so I'll include all the code below.
First, as mentioned in the comments, you are adding a new instance of your animation view every time viewDidAppear() is called. So, let's move the view creation to viewDidLoad():
class PersistentAnimationViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// do this here in viewDidLoad
// NOT in viewDidAppear!
let x = AnimationView(frame: .zero)
x.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(x)
x.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true
x.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor).isActive = true
x.widthAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.widthAnchor).isActive = true
x.heightAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.heightAnchor).isActive = true
}
}
Next, it's generally a bad idea to start animations (or other activity) in a class init, so we'll put the animation code in didMoveToSuperview() instead.
Also, you were executing foregroundView.layer.cornerRadius = self.frame.width / 2 during init, when the view may or may not have a frame. That should go in layoutSubviews().
We can get better control of animations by using CABasicAnimation instead of UIView.animate(...), so we'll make that change as well.
Finally, we'll use the referenced extensions and custom layer subclass to enable the animation to pause/resume when the app goes into / comes back from the background.
So, your AnimationView class (modified):
class AnimationView: UIView {
private let backgroundView = UIView()
private let foregroundView = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
// setting up the views
backgroundView.backgroundColor = .green
foregroundView.backgroundColor = .orange
// if you want rounded corners (or a "circle" view)
// put this in layoutSubviews()
//foregroundView.layer.cornerRadius = self.frame.width / 2
// adding the views
addSubview(backgroundView)
addSubview(foregroundView)
// activating the views
backgroundView.translatesAutoresizingMaskIntoConstraints = false
foregroundView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
backgroundView.topAnchor.constraint(equalTo: self.topAnchor),
backgroundView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
backgroundView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
backgroundView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
foregroundView.topAnchor.constraint(equalTo: self.topAnchor),
foregroundView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
foregroundView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
foregroundView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
])
// make the layer animation
// pause when app goes to background
// resume when app comes to foreground
// see below
self.foregroundView.layer.makeAnimationsPersistent()
}
// don't start the animation during init
// this is a reasonable place
override func didMoveToSuperview() {
super.didMoveToSuperview()
let animation = CABasicAnimation(keyPath:"transform.scale.xy")
animation.beginTime = CACurrentMediaTime()
animation.fromValue = 1.0
animation.toValue = 0.3
// set desired timing, or leave it at the default
//animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
animation.duration = 2.0
animation.repeatCount = .infinity
animation.autoreverses = true
self.foregroundView.layer.add(animation, forKey: "scale")
}
override func layoutSubviews() {
super.layoutSubviews()
// here in layoutSubviews we have a valid frame/bounds
// so do anything like rounding corners, etc
foregroundView.layer.cornerRadius = bounds.width / 2
}
}
and the required extension / layer subclass code:
// from: https://stackoverflow.com/a/50074722/6257435
// extensions and LayerPersistentHelper class found here:
// https://gist.github.com/ArtFeel/ad4b108f026e53723c7457031e291bc8
public extension CALayer {
var isAnimationsPaused: Bool {
return speed == 0.0
}
func pauseAnimations() {
if !isAnimationsPaused {
let currentTime = CACurrentMediaTime()
let pausedTime = convertTime(currentTime, from: nil)
speed = 0.0
timeOffset = pausedTime
}
}
func resumeAnimations() {
let pausedTime = timeOffset
speed = 1.0
timeOffset = 0.0
beginTime = 0.0
let currentTime = CACurrentMediaTime()
let timeSincePause = convertTime(currentTime, from: nil) - pausedTime
beginTime = timeSincePause
}
}
public extension CALayer {
static private var persistentHelperKey = "CALayer.LayerPersistentHelper"
func makeAnimationsPersistent() {
var object = objc_getAssociatedObject(self, &CALayer.persistentHelperKey)
if object == nil {
object = LayerPersistentHelper(with: self)
let nonatomic = objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC
objc_setAssociatedObject(self, &CALayer.persistentHelperKey, object, nonatomic)
}
}
}
public class LayerPersistentHelper {
private var persistentAnimations: [String: CAAnimation] = [:]
private var persistentSpeed: Float = 0.0
private weak var layer: CALayer?
public init(with layer: CALayer) {
self.layer = layer
addNotificationObservers()
}
deinit {
removeNotificationObservers()
}
}
private extension LayerPersistentHelper {
func addNotificationObservers() {
let center = NotificationCenter.default
let enterForeground = UIApplication.willEnterForegroundNotification
let enterBackground = UIApplication.didEnterBackgroundNotification
center.addObserver(self, selector: #selector(didBecomeActive), name: enterForeground, object: nil)
center.addObserver(self, selector: #selector(willResignActive), name: enterBackground, object: nil)
}
func removeNotificationObservers() {
NotificationCenter.default.removeObserver(self)
}
func persistAnimations(with keys: [String]?) {
guard let layer = self.layer else { return }
keys?.forEach { (key) in
if let animation = layer.animation(forKey: key) {
persistentAnimations[key] = animation
}
}
}
func restoreAnimations(with keys: [String]?) {
guard let layer = self.layer else { return }
keys?.forEach { (key) in
if let animation = persistentAnimations[key] {
layer.add(animation, forKey: key)
}
}
}
}
@objc extension LayerPersistentHelper {
func didBecomeActive() {
guard let layer = self.layer else { return }
restoreAnimations(with: Array(persistentAnimations.keys))
persistentAnimations.removeAll()
if persistentSpeed == 1.0 { // if layer was playing before background, resume it
layer.resumeAnimations()
}
}
func willResignActive() {
guard let layer = self.layer else { return }
persistentSpeed = layer.speed
layer.speed = 1.0 // in case layer was paused from outside, set speed to 1.0 to get all animations
persistAnimations(with: layer.animationKeys())
layer.speed = persistentSpeed // restore original speed
layer.pauseAnimations()
}
}