I use the Lottie animation library in XCode. But when I go to another page, the animation is running in the background. When I go back the main page, the animation is running twice and there is too much RAM being consumed. How can I fix this problem? and how can I stop the animation after a specific time?
import UIKit
import Lottie
class CoffeeSpecialFortunePageVC: UIViewController {
#IBAction func PopUpIn(_ sender: Any) {
animateIn(desiredView: PopupBlur)
animateIn(desiredView: popupView)
animation()
}
#IBAction func SuccesBtn(_ sender: UIView) {
animateOut(desiredView: PopupBlur)
animateOut(desiredView: popupView)
}
#IBOutlet var popupView: AnimationView!
#IBOutlet var PopupBlur: UIVisualEffectView!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.purple
PopupBlur.bounds = self.view.bounds
popupView.bounds = CGRect(x: 0, y: 0, width: self.view.bounds.width * 0.9, height: self.view.bounds.height * 0.4)
// Do any additional setup after loading the view.
}
func animation() {
popupView! = .init(name: "check1")
popupView?.frame = view.bounds
view.addSubview(popupView!)
popupView?.play()
popupView.contentMode = .scaleAspectFit
popupView.frame.size.height = 350
popupView.frame.size.width = 350
popupView.center.x = view.center.x
popupView.center.y = view.center.y
}
func animateIn(desiredView: UIView) {
let backgraundview = self.view!
backgraundview.addSubview(desiredView)
desiredView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
desiredView.alpha = 0
desiredView.center = backgraundview.center
UIView.animate(withDuration: 0.3, animations:{
desiredView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
desiredView.alpha = 1
})
}
func animateOut(desiredView: UIView) {
UIView.animate(withDuration: 0.3, animations: {
desiredView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
desiredView.alpha = 0
},completion: {_ in
desiredView.removeFromSuperview()
})
}
}
You can use one of the play(fromProgress...) API's that Lottie provides
animationView.play(
fromProgress: animationView.currentProgress,
toProgress: 1,
loopMode: .playOnce,
completion: { [weak self] completed in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
self.stopLoopAndTranstion(to: animation, endIn: image)
}
}
)
This will ensure that your current animation will finish and in completion block you can do your finishing work
One of the best solutions:
Create a notifier when the app goes to the background for pause and when back to the foreground
override func viewDidLoad()
{
super.viewDidLoad()
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.willResignActiveNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(appBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
animationView = .init(name: "lang")
animationView!.frame = CGRect(x: 20, y: 100, width: 50, height: 50)
animationView!.contentMode = .scaleAspectFit
animationView!.loopMode = .loop
animationView!.animationSpeed = 0.5
view.addSubview(animationView!)
animationView!.play()
}
This will invoke notification methods when the app moves to background and foreground
#objc func appMovedToBackground()
{
print("App moved to background!")
animationView!.removeFromSuperview()
}
#objc func appBecomeActive()
{
print("App become active")
//animationView!.play()
animationView = .init(name: "lang")
animationView!.frame = CGRect(x: 20, y: 100, width: 50, height: 50)
animationView!.contentMode = .scaleAspectFit
animationView!.loopMode = .loop
animationView!.animationSpeed = 0.5
view.addSubview(animationView!)
animationView!.play()
}
This will solve your problem....... For sure
Related
i have a button in ViewController and i changed button position when clicked on that .
the question it is how to can move button to new position when Clicked with animation ?
this is view Controller :
override func viewDidLoad() {
super.viewDidLoad()
let X = 50
let Y = 100
let WIDTH = 100
let HEIGHT = 100
movable_Button.frame = CGRect(x: X, y: Y, width: WIDTH , height: HEIGHT)
}
this is change position method When clicked :
#IBAction func movable_Button_DidTouch(_ sender: Any) {
movable_Button.frame = CGRect(x: X + 150, y: Y + 150, width: 100, height: 100)
}
Let's use UIView animate block
#IBAction func movableButtonDidTouch(_ sender: Any) {
// Set initial frame here
UIView.animate(withDuration: 0.35) {
// Set final frame here
}
}
Please notice to remove underline (_) in function name, it's not the convention of Swift, use camel case instead.
Update
class ViewController: UIViewController {
var positions = [CGPoint]()
let width = 100.0
let height = 50.0
var index = 0
lazy var button: UIButton = {
let theButton = UIButton(type: .system)
theButton.frame = CGRect(x: 50, y: 50, width: self.width, height: self.height)
theButton.backgroundColor = .red
return theButton
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.button)
for index in 1...4 {
let point = CGPoint(x: 50 * index, y: 50 * index)
self.positions.append(point)
}
}
#IBAction func changeButtonPosition(_ sender: AnyObject) {
UIView.animate(withDuration: 0.35) { [weak self] in
guard let self = self else { return }
self.index += 1
let point = self.positions[self.index % 4]
self.button.frame = CGRect(origin: point, size: CGSize(width: self.width, height: self.height))
}
}
}
I am subclassing UIView, and am trying to "drag" a CGRect back and forth across the screen. Basically I want to move(redraw) the rectangle every time I drag my finger. So far, I have this code:
var rectangle: CGRect {
get {
return CGRect(x: 200,
y: 200,
width: frame.width / 6,
height: 15)
}
set {}
}
override func draw(_ rect: CGRect) {
let gesture = UIPanGestureRecognizer(target: self, action: #selector(dragRectangle(recognizer:)))
addGestureRecognizer(gesture)
drawRectangle(rect)
}
func drawRectangle(_ rect: CGRect) {
let path = UIBezierPath(rect: rectangle)
UIColor.black.set()
path.fill()
}
#objc func dragRectangle(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translation(in: self)
rectangle = CGRect(x: rectangle.midX + translation.x, y: rectangle.midY + translation.y, width: rectangle.width, height: rectangle.height)
setNeedsDisplay()
recognizer.setTranslation(CGPoint.zero, in: self)
}
This is my first time using UIPanGestureRecognizer, so I'm not sure of all the details that go into this. I have set breakpoints in drawRectangle and confirmed that this is being called. However, the rectangle on the screen does not move at all, no matter how many times I try to drag it. What's wrong?
This is how you can do it easily. just copy paste and run this.
//
// RootController.swift
// SampleApp
//
// Created by Chanaka Caldera on 24/6/19.
// Copyright © 2019 homeapps. All rights reserved.
//
import UIKit
class RootController: UIViewController {
private var draggableView: UIView!
private var pangesture: UIPanGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
setdragview()
}
}
extension RootController {
fileprivate func setdragview() {
draggableView = UIView()
draggableView.translatesAutoresizingMaskIntoConstraints = false
draggableView.backgroundColor = .lightGray
view.addSubview(draggableView)
let draggableviewConstraints = [draggableView.leftAnchor.constraint(equalTo: view.leftAnchor,constant: 10),
draggableView.topAnchor.constraint(equalTo: view.topAnchor, constant: 64),
draggableView.widthAnchor.constraint(equalToConstant: 100),
draggableView.heightAnchor.constraint(equalToConstant: 40)]
NSLayoutConstraint.activate(draggableviewConstraints)
pangesture = UIPanGestureRecognizer()
draggableView.isUserInteractionEnabled = true
draggableView.addGestureRecognizer(pangesture)
pangesture.addTarget(self, action: #selector(draggableFunction(_:)))
}
}
extension RootController {
#objc fileprivate func draggableFunction(_ sender: UIPanGestureRecognizer) {
view.bringSubviewToFront(draggableView)
let translation = sender.translation(in: self.view)
draggableView.center = CGPoint(x: draggableView.center.x + translation.x, y: draggableView.center.y + translation.y)
sender.setTranslation(CGPoint.zero, in: self.view)
print("drag works : \(translation.x)")
}
}
here is the demo,
Hope this will help. cheers !
Try like this (check comments through code):
#IBDesignable
class Rectangle: UIView {
#IBInspectable var color: UIColor = .clear {
didSet { backgroundColor = color }
}
// draw your view using the background color
override func draw(_ rect: CGRect) {
backgroundColor?.set()
UIBezierPath(rect: rect).fill()
}
// add the gesture recognizer to your view
override func didMoveToSuperview() {
addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(pan)))
}
// your gesture selector
#objc func pan(_ gesture: UIPanGestureRecognizer) {
// update your view frame origin
frame.origin += gesture.translation(in: self)
// reset the gesture translation
gesture.setTranslation(.zero, in: self)
}
}
extension CGPoint {
static func +=(lhs: inout CGPoint, rhs: CGPoint) {
lhs.x += rhs.x
lhs.y += rhs.y
}
}
To draw rectangles on your view when panning you can do as follow:
import UIKit
class ViewController: UIViewController {
var rectangles: [Rectangle] = []
override func viewDidLoad() {
super.viewDidLoad()
view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(pan)))
}
#objc func pan(_ gesture: UIPanGestureRecognizer) {
switch gesture.state {
case .began:
let rectangle = Rectangle(frame: .init(origin: gesture.location(in: view), size: .init(width: 0, height: 0)))
rectangle.fillColor = .red
rectangle.strokeColor = .white
rectangle.lineWidth = 3
view.addSubview(rectangle)
rectangles.append(rectangle)
case .changed:
let distance = gesture.translation(in: view)
let index = rectangles.index(before: rectangles.endIndex)
let frame = rectangles[index].frame
rectangles[index].frame = .init(origin: frame.origin, size: .init(width: frame.width + distance.x, height: frame.height + distance.y))
rectangles[index].setNeedsDisplay()
gesture.setTranslation(.zero, in: view)
case .ended:
break
default:
break
}
}
}
Sample Project
I figured out most of the problem as to why the rectangle wasn't moving. It turns out that I misunderstood how variable getters and setters work in Swift. Nevertheless, I changed this code:
var rectangle: CGRect {
get {
return CGRect(x: 200,
y: 200,
width: frame.width / 6,
height: 15)
}
set {}
}
to be a lazy variable:
lazy var rectangle: CGRect = {
return CGRect(x: 200,
y: 200,
width: self.frame.width / 6,
height: 15)
}()
The reason I needed get and set in the first place was because I use frame in my variable calculations, and that was not available until the view itself was fully instantiated. I also tweaked this code a bit:
rectangle = CGRect(x: rectangle.midX + translation.x, y: rectangle.midY + translation.y, width: rectangle.width, height: rectangle.height)
and used minX instead of midX:
rectangle = CGRect(x: rectangle.minX + translation.x, y: rectangle.minY + translation.y, width: rectangle.width, height: rectangle.height)
This is because CGRect is initialized with the x and y parameters being the minX and minY.
This is good progress(at least the rectangle moves). However, I am not able to figure out why the rectangle only switches places after I have released my mouse, resulting in choppy movement.
This problem has been answered several times before on this site, I have tried them all and none work. The difference I think is that I have a UITableView inside my UIViewController. I have tried when loading the data within viewDidLoad, here the screen I am coming from show until all is complete and my new view appears. I have also tried within viewDidAppear, here I have a blank table showing before the final view comes up.
I have tried 4 methods all from this site, I call pauseApp(n) before I start the load and restartApp(n) when completed
var spinner:UIActivityIndicatorView = UIActivityIndicatorView()
var loadingView = UIView()
var loadingLabel = UILabel()
var indicator = UIActivityIndicatorView()
#IBOutlet weak var tvTable: UITableView!
func pauseApp() {
tvTable.activityIndicatorView.startAnimating()
tvTable.activityIndicatorView.bringSubviewToFront(aIV)
UIApplication.shared.beginIgnoringInteractionEvents()
}
func pauseApp1() {
spinner = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
spinner.center = self.navBar.center
spinner.hidesWhenStopped = true
spinner.style = UIActivityIndicatorView.Style.gray
self.navigationController?.view.addSubview(spinner)
spinner.startAnimating()
UIApplication.shared.beginIgnoringInteractionEvents()
}
func pauseApp2() {
tvTable.activityIndicatorView.startAnimating()
indicator.startAnimating()
indicator.backgroundColor = UIColor.white
UIApplication.shared.beginIgnoringInteractionEvents()
}
func pauseApp3() {
setLoadingScreen()
UIApplication.shared.beginIgnoringInteractionEvents()
}
func restartApp() {
// sleep(2)
tvTable.activityIndicatorView.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()
}
func restartApp1() {
spinner.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()
}
func restartApp2() {
// sleep(2)
indicator.stopAnimating()
indicator.hidesWhenStopped = true
UIApplication.shared.endIgnoringInteractionEvents()
}
func restartApp3() {
// sleep(2)
removeLoadingScreen()
UIApplication.shared.endIgnoringInteractionEvents()
}
private func setLoadingScreen() {
let width: CGFloat = 120
let height: CGFloat = 30
let x = (view.frame.width / 2) - (width / 2)
let y = (view.frame.height / 2) - (height / 2) - 20
loadingView.frame = CGRect(x: x, y: y, width: width, height: height)
// Sets loading text
loadingLabel.textColor = .gray
loadingLabel.textAlignment = .center
loadingLabel.text = "Loading..."
loadingLabel.frame = CGRect(x: 0, y: 0, width: 140, height: 30)
// Sets spinner
spinner.style = .gray
spinner.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
spinner.startAnimating()
// Adds text and spinner to the view
loadingView.addSubview(spinner)
loadingView.addSubview(loadingLabel)
view.addSubview(loadingView)
view.bringSubviewToFront(loadingView)
}
private func removeLoadingScreen() {
spinner.stopAnimating()
spinner.isHidden = true
loadingLabel.isHidden = true
}
func activityIndicator() {
indicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
indicator.style = UIActivityIndicatorView.Style.gray
indicator.center = self.view.center
self.view.addSubview(indicator)
}
fileprivate var ActivityIndicatorViewAssociativeKey = "ActivityIndicatorViewAssociativeKey"
public var aIV: UIActivityIndicatorView = UIActivityIndicatorView()
public extension UITableView {
var activityIndicatorView: UIActivityIndicatorView {
get {
if let aIV = getAssociatedObject(&ActivityIndicatorViewAssociativeKey) as? UIActivityIndicatorView {
return aIV
} else {
let aIV = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
aIV.style = .gray
aIV.color = .gray
aIV.backgroundColor = UIColor.black
aIV.center = center
aIV.hidesWhenStopped = true
addSubview(aIV)
setAssociatedObject(aIV, associativeKey: &ActivityIndicatorViewAssociativeKey, policy: .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
aIV.bringSubviewToFront(aIV)
return aIV
}
}
set {
addSubview(newValue)
setAssociatedObject(newValue, associativeKey:&ActivityIndicatorViewAssociativeKey, policy: .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
public extension NSObject {
func setAssociatedObject(_ value: AnyObject?, associativeKey: UnsafeRawPointer, policy: objc_AssociationPolicy) {
if let valueAsAnyObject = value {
objc_setAssociatedObject(self, associativeKey, valueAsAnyObject, policy)
}
}
func getAssociatedObject(_ associativeKey: UnsafeRawPointer) -> Any? {
guard let valueAsType = objc_getAssociatedObject(self, associativeKey) else {
return nil
}
return valueAsType
}
}
Verify your Interface Builder file, specifically the order in which the components are defined. Components higher up in the hierarchy may be hidden by those defined below them. Thus it's quite possible that your tableview hides your activity view.
You should be able to confirm this fairly quickly by hiding the table view and other other views that may be on top. Depending on your activity view settings, you may also need to do tvTable.activityIndicatorView.isHidden = false. Note that since UITableView implement a built-in scrollview, adding an activity view as a child to a UITableView may not be the the best course. You are better off defining it as a child of the tableView's superview; ref:
Your attempt with pauseApp1 could work with minor modifications, but only if your view controller is hosted inside a navigation controller. You should also always define any relationship only AFTER the view is added as a subview not before.
Starting a brand new project from scratch, here's how you can display an activity indicator by code:
class ViewController: UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// We add some delay for fun, absolutely not required!
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
self.showSpinner()
}
}
private func showSpinner() {
let spinner = UIActivityIndicatorView.init(style: .gray)
self.view.addSubview(spinner)
spinner.center = self.view.center
spinner.startAnimating()
spinner.isHidden = false
spinner.hidesWhenStopped = true
}
}
Thanks Again,
The solution is to add an activityIndeicatorView with Storyboard below our TableView
Then in viewDidAppear have
#IBOutlet weak var mySpinner: UIActivityIndicatorView!
override func viewDidAppear(_ animated: Bool) {
self.pauseApp()
DispatchQueue.main.asyncAfter(deadline: .now()) {
self.doScreen()
}
}
func pauseApp() {
showSpinner()
UIApplication.shared.beginIgnoringInteractionEvents()
}
func restartApp() {
mySpinner.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()
}
private func showSpinner() {
mySpinner.startAnimating()
mySpinner.isHidden = false
mySpinner.hidesWhenStopped = true
}
The pauseApp call put the spinner on the screen, the doScreen does all the work and calls restartApp when it has finished. My doScreen does quite a lot of work going to a service to get the data, it takes about 2 seconds with a good internet connection, but much longer when the connection is poor.
I wanted to implement a loading overlay whilst I have content loading in from an API call however when I go to dismiss the view; I have no success.
func viewLoading(show:Bool, boxView: UIView, error: Bool, errorMessage: String){
let myNewView=UIView(frame: CGRect(x: 0, y: 0, width: boxView.frame.width, height: boxView.frame.height))
if show{
// Change UIView background colour
myNewView.backgroundColor = UIColor.black.withAlphaComponent(0.75)
myNewView.isOpaque = false
// Add rounded corners to UIView
myNewView.layer.cornerRadius = boxView.layer.cornerRadius
let activityView = UIActivityIndicatorView(style: .whiteLarge)
activityView.center = myNewView.center
activityView.startAnimating()
boxView.addSubview(myNewView)
myNewView.addSubview(activityView)
}else{
print("Done")
DispatchQueue.main.async(execute: { () -> Void in
myNewView.removeFromSuperview()
self.view.bringSubviewToFront(boxView)
})
myNewView.isHidden = true
}
}
None of the options after else have worked and I am lost at a solution.
Edit: I want the same function(s) to accommodate three different views within the one view controller.
Move myNewView outside of the viewLoading function scope, and it is better to create separate methods with their own responsibilities, like so:
class ViewController: UIViewController {
var loaderView: UIView?
func showLoading(boxView: UIView, error: Bool, errorMessage: String) {
if (self.loaderView != nil) {
self.hideLoading()
}
let newView = UIView(frame: CGRect(x: 0, y: 0, width: boxView.frame.width, height: boxView.frame.height))
newView.backgroundColor = UIColor.black.withAlphaComponent(0.75)
newView.isOpaque = false
// Add rounded corners to UIView
newView.layer.cornerRadius = boxView.layer.cornerRadius
let activityView = UIActivityIndicatorView(style: .whiteLarge)
activityView.center = newView.center
activityView.startAnimating()
boxView.addSubview(newView)
newView.addSubview(activityView)
self.loaderView = newView
}
func hideLoading() {
guard
let loaderView = self.loaderView,
let boxView = loaderView.superview
else { return }
DispatchQueue.main.async {
loaderView.removeFromSuperview()
self.view.bringSubviewToFront(boxView) // need this?
self.loaderView = nil
}
}
}
You are creating a new view every time that method is called and then you are trying to dismish that newly created view. Instead, you should save a reference of the view when you show it and call removeFromSuperview on that instance when you need to hide it.
Check this..
CommonMethods.swift
import UIKit
class CommonMethods: UIViewController {
static let actInd: UIActivityIndicatorView = UIActivityIndicatorView()
static let container: UIView = UIView()
static let loadingView: UIView = UIView()
static func showActivityIndicatory(uiView: UIView) {
container.frame = uiView.frame
container.center = uiView.center
container.backgroundColor = UIColor(red:255/255, green:255/255, blue:255/255, alpha: 0.3)
loadingView.frame = CGRect(origin: CGPoint(x: 0,y :0), size: CGSize(width: 80, height: 80))
loadingView.center = uiView.center
loadingView.backgroundColor = UIColor(red:44/255, green:44/255, blue:44/255, alpha: 0.7)
loadingView.clipsToBounds = true
loadingView.layer.cornerRadius = 10
actInd.frame = CGRect(origin: CGPoint(x: 0,y :0), size: CGSize(width: 40, height: 40))
actInd.style =
UIActivityIndicatorView.Style.whiteLarge
actInd.center = CGPoint(x: loadingView.frame.size.width / 2, y: loadingView.frame.size.height / 2);
loadingView.addSubview(actInd)
container.addSubview(loadingView)
uiView.addSubview(container)
actInd.startAnimating()
}
static func hideActivityIndicatory(uiView: UIView) {
container.removeFromSuperview()
actInd.stopAnimating()
}
}
call it from viewcontroller class like
CommonMethods.showActivityIndicatory(uiView: self.view)
CommonMethods.hideActivityIndicatory(uiView: self.view)
I want to trigger a CAAnimation with a Button. In playground and in the simulator, this works exactly as I want it to. However, when I run the same code on a device, the animation happens only after a short delay.
Apparently, the issue only happens on iOS 11.2.6. I updated my device and can now not reproduce the issue anymore. Can anyone confirm, or find out, how it would work on iOS 11.2.6?
import UIKit
class MyViewController : UIViewController {
let animatedView = UIView()
override func loadView() {
let view = UIView()
view.backgroundColor = .white
// Add a button
let button = UIButton(type: .system)
button.frame = CGRect(x: 150, y: 200, width: 200, height: 50)
button.setTitle("Animate", for: .normal)
button.addTarget(self, action: #selector(tap), for: .touchUpInside)
// Set color and frame of the view, that is animated.
animatedView.backgroundColor = UIColor.blue
animatedView.frame = CGRect(x: 50, y: 50, width: 50, height: 50)
// Add the views to the view hierarchy
view.addSubview(animatedView)
view.addSubview(button)
self.view = view
}
/// On Tap create an animation, that changes the position of the animated view.
#objc func tap() {
let originalY = animatedView.layer.position.y
let animation = CABasicAnimation(keyPath: "position.y")
animation.fromValue = originalY
animation.toValue = 300.0
animation.duration = 1.0
animatedView.layer.add(animation, forKey: "positionAnimation")
}
}
Can you try this code with iOS 11.2.9, I have tested your code and it's working fine.
// MARK: Animations
public extension CALayer {
var ani: CAAnimation {
let startPointAnim = CABasicAnimation(keyPath: #keyPath(CAGradientLayer.startPoint))
startPointAnim.fromValue = CGPoint(x: 0, y: 0.0)
startPointAnim.toValue = CGPoint(x:0, y: 300)
let endPointAnim = CABasicAnimation(keyPath: #keyPath(CAGradientLayer.endPoint))
endPointAnim.fromValue = CGPoint(x: 0, y: 0)
endPointAnim.toValue = CGPoint(x:2, y: 300)
let animGroup = CAAnimationGroup()
animGroup.animations = [startPointAnim, endPointAnim]
animGroup.duration = 1.0
animGroup.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
return animGroup
}
func playAnimation(_ anim: SkeletonLayerAnimation, key: String) {
recursiveSearch(inArray: skeletonSublayers,
leafBlock: { add(anim(self), forKey: key) }) {
$0.playAnimation(anim, key: key)
}
}
func stopAnimation(forKey key: String) {
recursiveSearch(inArray: skeletonSublayers,
leafBlock: { removeAnimation(forKey: key) }) {
$0.stopAnimation(forKey: key)
}
}
}