The UIView.animateWithDuration call in my ViewController viewDidLoad function does not animate. The completion block gets called immediately. The println output shows 'completion true'. Is the viewDidLoad function the wrong place to put my startup animation? Any hints are greatly appreciated.
class ViewController: UIViewController {
#IBOutlet var beatIndicator:UIView?
override func viewDidLoad() {
super.viewDidLoad()
if let led = beatIndicator? {
led.alpha = 1.0
led.frame = CGRectMake(20, 20, 30, 30)
led.layer.cornerRadius = 15
UIView.animateWithDuration(3.0, animations:{
led.alpha = 0.5
led.frame = CGRectMake(25, 25, 20, 20)
led.layer.cornerRadius = 10
}, completion:{(value: Bool) in
println("completion \(value)")
})
}
// ...
}
}
Have you tried putting it in ViewDidAppear?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let led = beatIndicator {
led.alpha = 1.0
led.frame = CGRect(x: 20, y: 20, width: 30, height: 30)
led.layer.cornerRadius = 15
UIView.animate(withDuration: 3.0, animations:{
led.alpha = 0.5
led.frame = CGRect(x: 25, y: 25, width: 20, height: 20)
led.layer.cornerRadius = 10
}, completion:{(value: Bool) in
print("completion \(value)")
})
}
// ...
}
viewDidLoad() is not the ideal place where you should animate views. I tried your code in the viewDidAppear() method and it works.
Related
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
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))
}
}
}
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 want an image of a red circle to move from the top of the screen to the bottom, as though it were a meteor traveling across the screen. It does move, but it moves in a millisecond with no animation. How do I make it move smoothly like a meteor with a tail?
class ViewController: UIViewController {
let meteorImage = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.meteorImage.backgroundColor = .clear
self.meteorImage.frame = CGRect(x: 50, y: 0, width: 50, height: 50)
self.meteorImage.isHidden = false
self.meteorImage.image = #imageLiteral(resourceName: "success")
view.addSubview(self.meteorImage)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func buttonPressed(_ sender: UIButton) {
let totalHeight = Int(view.frame.height)
var currentHeight = 0
while currentHeight < totalHeight {
self.meteorImage.backgroundColor = .clear
self.meteorImage.frame = CGRect(x: 50, y: currentHeight, width: 50, height: 50)
self.meteorImage.isHidden = false
self.meteorImage.image = #imageLiteral(resourceName: "success")
view.addSubview(self.meteorImage)
currentHeight += 1
}
}
}
Animations have to be done with an animate call.
This is a simple way to do an animation.
UIView.animate(withDuration: 0.1, animations: {
// Apply Changes to frame.
})
Consider also: instead of doing the while loop simply set the final location of the meteor's frame and set the duration to the amount of time you want it to take.
let newFrame= CGRect(x: 50,
y: currentHeight,
width: 50,
height: 50)
UIView.animate(withDuration: 3.0, animations: {
// Apply Changes to frame.
self.meteorImage.frame = newFrame
})
If you want to make your animation more complicated you can look into keyframe animations on a UIView.
I am new in iOS and I want to implement a framework call PNChart to my project. I would like to increment the circle chart by one but the UI will not update. Please help.
import UIKit
import PNChart
class ViewController: UIViewController {
//global var
let number = NSNumber(value: 70)
//obj outlet
#IBOutlet weak var circleChart: PNCircleChart!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
let myCgrect = CGRect(
x: self.view.bounds.width/2 - 40,
y: self.view.bounds.height/2 - 40,
width: 100, height: 100)
let circleChart = PNCircleChart(
frame: myCgrect, total: Int(100) as NSNumber!,
current: number, clockwise: false,
shadow: false, shadowColor: UIColor.red)
circleChart?.backgroundColor = UIColor.clear
circleChart?.strokeColor = UIColor.blue
circleChart?.stroke()
self.view.addSubview(circleChart!)
}
#IBAction func increaseCircle(_ sender: Any) {
_ = NSNumber(value: number.intValue + 1)
print("btnpressed")
}
}// end uiviewcontroller
In your function increaseCircle you aren't doing anything with the information. You need to feed in this incremented number into your circleChart and redraw it. Since you're customizing Gindi's library you probably have your own functions for doing this.