Starting second UIView animation undoes previous animation - ios

In Swift I'm using UIView.animateWithDuration to animate a button and some text out of the frame. In the "completion" block, I perform a func called showQuestion which is another UIView animation. However, the first time the showQuestion function runs, the button and text that was previously hidden by the prior animation appears back in the frame, as if it had never moved.
I've checked a few questions on here, and none seem to directly address the issue.
EDIT #1
Here are the three functions.
func startQuiz(){
UIView.animateWithDuration(0.2, delay: 0.2, options: .CurveEaseIn, animations: {
self.goButton.frame.origin.y = self.view.frame.height + 5
self.languageLabel.frame.origin.y = self.view.frame.height + 5
self.label.frame.origin.y = self.view.frame.height + 5
}, completion: { finished in
self.generateFirstQuestion()
})
}
func generateFirstQuestion() {
var aBase = 0;
var qBase = 0;
if(self.lang == "fr") {
aBase = 25
qBase = 5
}
var offset = (self.currentQuestion * 5) + aBase
var question:String = self.questions[self.currentQuestion+qBase]
self.questionLabel.text = question
var answer1:String = self.answers[offset]
var answer2:String = self.answers[offset+1]
var answer3:String = self.answers[offset+2]
var answer4:String = self.answers[offset+3]
var answer5:String = self.answers[offset+4]
self.showQuestion()
}
func showQuestion(){
if(self.isAnimating){
UIView.animateWithDuration(0.2, delay: 0.2, options: .CurveEaseOut, animations: {
self.questionLabel.center.x = self.view.center.x
}, completion: { finished in
self.isAnimating = false
self.currentQuestion++
})
}
else {
var aBase = 0;
var qBase = 0;
if(self.lang == "fr") {
aBase = 25
qBase = 5
}
var offset = (self.currentQuestion * 5) + aBase
var question:String = self.questions[self.currentQuestion+qBase]
self.questionLabel.text = question
var answer1:String = self.answers[offset]
var answer2:String = self.answers[offset+1]
var answer3:String = self.answers[offset+2]
var answer4:String = self.answers[offset+3]
var answer5:String = self.answers[offset+4]
UIView.animateWithDuration(0.2, delay: 0.2, options: .CurveEaseIn, animations: {
self.questionLabel.frame.origin.x = 0-700
}, completion: { finished in
self.questionLabel.frame.origin.x = self.view.frame.width+5
self.isAnimating = true
self.showQuestion()
})
}
}

Related

Coreplot Animation of a sequence of plots

I have a number of plots, which depict (x,y) data at different time intervals, and wish to plot them in a sequence one after the other(like a gif file). My approach was generate all the plots, use a Timer.scheduledTimer and initially hide all plots, unhiding current plot and hiding previous plot at each fired schedule. The time between each plot hiding/unhiding shows a blank graph for more time than the plots are shown. Each plot has 32x32 data points. How can I speed this up, so I never see a blank graph? Another approach was to fade out one plot, whilst introducing the next, but I see the same effect.
#objc func tapAnimationButton(_ sender: Any) {
isAnimating = !isAnimating
plotSpacesUserInteraction = !plotSpacesUserInteraction
if let _animationButton = animationButton {
_animationButton.isSelected = isAnimating
previousAnimatedPlot = nil
if isAnimating {
animationCounter = 0
for i in 0..<plotDetails.count {
// fieldsplots[i].isHidden = true
fieldsplots[i].opacity = 0.0
}
animationTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(animateGraph(_:)), userInfo: nil, repeats: true)
if let _animationTimer = animationTimer {
animateGraph(_animationTimer)
}
}
else {
animationTimer?.invalidate()
animationTimer = nil
}
}
}
#objc func animateGraph(_ timer: Timer) {
if animationSeconds > 120.0 {
timer.invalidate()
self.animationTimer = nil
animationSeconds = 0.0
animationCounter = 0
previousAnimatedPlot = nil
}
else {
if let currentAnimatedPlot = self.graph.plot(at: animationCounter) {
// previousAnimatedPlot?.isHidden = true
// currentAnimatedPlot.isHidden = false
previousAnimatedPlot?.opacity = 1.0
let fadeOutAnimation = CABasicAnimation(keyPath: "opacity")
fadeOutAnimation.duration = 0.1
fadeOutAnimation.isRemovedOnCompletion = false
fadeOutAnimation.fillMode = CAMediaTimingFillMode.forwards
fadeOutAnimation.toValue = Float(0.0)
previousAnimatedPlot?.add(fadeOutAnimation, forKey: "animateOpacity")
currentAnimatedPlot.opacity = 0.0
let fadeInAnimation = CABasicAnimation(keyPath: "opacity")
fadeInAnimation.duration = 0.1
fadeInAnimation.isRemovedOnCompletion = false
fadeInAnimation.fillMode = CAMediaTimingFillMode.forwards
fadeInAnimation.toValue = Float(1.0)
currentAnimatedPlot.add(fadeInAnimation, forKey: "animateOpacity")
previousAnimatedPlot = currentAnimatedPlot
}
animationSeconds += 0.5
animationCounter += 1;
if animationCounter >= plotDetails.count {
animationCounter = 0
}
}
}
The fade in/out method actually works...not sure how I missed that.
As an add-on to answer, in order to produce a gif image one needs to hide/unhide the plots in sequence
for currentPlot in self.graph.allPlots() {
currentPlot.isHidden = true
}
var images: [UIImage] = []
var previousPlot: CPTPlot?
for currentPlot in self.graph.allPlots() {
if let _previousPlot = previousPlot {
_previousPlot.isHidden = true
}
currentPlot.isHidden = false
if let image = graph.imageOfLayer() {
images.append(image)
}
previousPlot = currentPlot
}
for currentPlot in self.graph.allPlots() {
currentPlot.isHidden = false
}
if images.count > 2 {
TwoDPlot_Utilities.GIFExport(with: images, plot: thisplot, plotIndex: plotIndex, frameDelay: 0.5)
}

Variable used within its own initial value in Swift 5

I try to write an animation with Swift 5, following are some codes
let animations:(() -> Void) = {
self.keyboardOPT.transform = CGAffineTransform(translationX: 0,y: -deltaY)
if duration > 0 {
let options = UIView.AnimationOptions(rawValue: UInt((userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as! NSNumber).intValue << 16))
UIView.animate(withDuration: duration, delay: 0, options: options, animations: animations, completion: nil)
} else {
animations()
}
}
But in animations: animations and animations() it shows error:
Variable used within its own initial value
You can not call itself when initializing.
You can achieve it like this also.
var animations:(() -> Void)!
animations = {
animations()
}

Why does UILabel update kill animation?

I have a super simple UIView animation where the origin y value fails to animate to 88 on the first try if i change the input text set in the UILabels.
The animation runs fine on the 2nd attempt. It feels like an initialization problem. Running layoutSubViews and updateConstraints is not helping. Thanks for any tips on this.
func previewDisplay(notifView: UIView, hdrView: UIView) {
populateText()
self.notifView?.frame.origin.y = 0
self.notifView?.frame.size.height = 33
self.notifView?.layoutSubviews()
self.notifView?.updateConstraints()
self.notifView = notifView
self.closeBtn.isHidden = true
self.notifBodyLabel.isHidden = true
self.closeBtn.alpha = 0
self.notifBodyLabel.alpha = 0
UIView.animate(withDuration: 1.0, animations: {
self.notifView?.frame.origin.y = 88
}, completion: nil)
}
func populateText() {
if let info = notification?.userInfo as? Dictionary<String,String> {
// Check if value present before using it
if let t = info["title"] {
self.notifTitleMessageLabel.text = t
} else {
self.notifTitleMessageLabel.text = ""
}
if let b = info["body"] {
self.notifBodyLabel.text = b
} else {
self.notifBodyLabel.text = ""
}
}
}

Text Field Drops Below Keyboard Upon Entering Text in Text Field

I have a strange issue with regard to entering text into a text field. I am currently using the code below. My code is modeled after the answer here.
class RocketViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate, NSFetchedResultsControllerDelegate {
var offsetY:CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(RocketViewController.keyboardFrameChangeNotification(notification:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
#objc func keyboardFrameChangeNotification(notification: Notification) {
if let userInfo = notification.userInfo {
let keyBoardFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect
let animationDuration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? Double ?? 0
let animationCurveRawValue = (userInfo[UIKeyboardAnimationCurveUserInfoKey] as? Int) ?? Int(UIViewAnimationOptions.curveEaseInOut.rawValue)
let animationCurve = UIViewAnimationOptions(rawValue: UInt(animationCurveRawValue))
if let _ = keyBoardFrame, keyBoardFrame!.intersects(self.mainStackView.frame) {
self.offsetY = self.mainStackView.frame.maxY - keyBoardFrame!.minY
UIView.animate(withDuration: animationDuration, delay: TimeInterval(0), options: animationCurve, animations: {
self.mainStackView.frame.origin.y = self.mainStackView.frame.origin.y - self.offsetY
self.rocketSelectTable.frame.origin.y = self.rocketSelectTable.frame.origin.y - self.offsetY
}, completion: nil)
} else {
if self.offsetY != 0 {
UIView.animate(withDuration: animationDuration, delay: TimeInterval(0), options: animationCurve, animations: {
self.mainStackView.frame.origin.y = self.mainStackView.frame.origin.y + self.offsetY
self.rocketSelectTable.frame.origin.y = self.rocketSelectTable.frame.origin.y + self.offsetY
self.offsetY = 0
}, completion: nil)
}
}
}
}
}
In my view I have a table view with a fetched results controller as its data source, and below that are the text fields in a stack view, called mainStackView, that are eventually saved in a core data store.
I have gone through several iterations of this code with the same result, whether I compute the offset off the first responder, or simply the stack view. When a text field becomes the first responder, the view slides up nicely with the keyboard. However, as soon as I attempt to type in the field, the view snaps back to its original position. I am sure I am making a newbie mistake, but I can't figure out what I am doing wrong, and I have found nothing in my searches, except a similar question for android. Thanks in advance.
While I have not determined why I was seeing the behavior with the text field that I was seeing with changing the frame, I was able to stop the behavior by using a CGAffineTransform instead. My code is now:
#objc func keyboardFrameChangeNotification(notification: Notification) {
if let userInfo = notification.userInfo {
let keyBoardFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect
let animationDuration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? Double ?? 0
let animationCurveRawValue = (userInfo[UIKeyboardAnimationCurveUserInfoKey] as? Int) ?? Int(UIViewAnimationOptions.curveEaseInOut.rawValue)
let animationCurve = UIViewAnimationOptions(rawValue: UInt(animationCurveRawValue))
if let _ = keyBoardFrame, keyBoardFrame!.intersects(self.mainStackView.frame) {
self.offsetY = self.mainStackView.frame.maxY - keyBoardFrame!.minY
let transformUp = CGAffineTransform.init(translationX: 0, y: (0 - self.offsetY))
UIView.animate(withDuration: animationDuration, delay: TimeInterval(0), options: animationCurve, animations: {
self.mainStackView.transform = transformUp
self.rocketSelectTable.transform = transformUp
}, completion: nil)
} else {
if self.offsetY != 0 {
UIView.animate(withDuration: animationDuration, delay: TimeInterval(0), options: animationCurve, animations: {
self.mainStackView.transform = CGAffineTransform.identity
self.rocketSelectTable.transform = CGAffineTransform.identity
self.offsetY = 0
}, completion: nil)
}
}
}
This code smoothly animates the movement of the views, and there is no snap back to the original position while typing in the text field. I hope this helps someone else.

how to make a UIImageView tappable while it is animating across the screen

I have a for in loop that generates dot images which move across the screen. I have no idea where or how to implement a tap gesture recognizer that would be applied to all of the dots that are being generated and be able to make them dissapear when they get tapped as they are moving/animating. Please help me out! Thank you!
#IBAction func animateButton(sender: AnyObject) {
self.view.userInteractionEnabled = true
// Declare delay counter
var delayCounter:Int = 100000
var durationCounter:Double = 0
// loop for 1000 times
for loopNumber in 0...1000 {
// set up some constants for the animations
let dotDuration:Double = 4 - durationCounter
let redDelay = NSTimeInterval(arc4random_uniform(100000) + delayCounter) / 25000
let blueDelay = NSTimeInterval(arc4random_uniform(100000) + delayCounter) / 25000
let yellowDelay = NSTimeInterval(arc4random_uniform(100000) + delayCounter) / 25000
let greenDelay = NSTimeInterval(arc4random_uniform(100000) + delayCounter) / 25000
let options = UIViewAnimationOptions.AllowUserInteraction
//set up some constants for the dots
let redSize:CGFloat = 54
let redYPosition : CGFloat = CGFloat( arc4random_uniform(275)) + 54
let blueSize:CGFloat = 54
let blueYPosition : CGFloat = CGFloat( arc4random_uniform(275)) + 54
let yellowSize:CGFloat = 54
let yellowYPosition : CGFloat = CGFloat( arc4random_uniform(275)) + 54
let greenSize:CGFloat = 54
let greenYPosition : CGFloat = CGFloat( arc4random_uniform(275)) + 54
// create the dots and add them to the view
let redDot = UIImageView()
redDot.image = UIImage(named: "Red Dot")
redDot.frame = CGRectMake(0-redSize, redYPosition, redSize, redSize)
self.view.addSubview(redDot)
let blueDot = UIImageView()
blueDot.image = UIImage(named: "Blue Dot")
blueDot.frame = CGRectMake(0-blueSize, blueYPosition, blueSize, blueSize)
self.view.addSubview(blueDot)
let yellowDot = UIImageView()
yellowDot.image = UIImage(named: "Yellow Dot")
yellowDot.frame = CGRectMake(0-yellowSize, yellowYPosition, yellowSize, yellowSize)
self.view.addSubview(yellowDot)
let greenDot = UIImageView()
greenDot.image = UIImage(named: "Green Dot")
greenDot.frame = CGRectMake(0-greenSize, greenYPosition, greenSize, greenSize)
self.view.addSubview(greenDot)
// define the animations
UIView.animateWithDuration(dotDuration , delay: redDelay, options: options, animations: {
redDot.frame = CGRectMake(675, redYPosition, redSize, redSize)
}, completion: { animationFinished in redDot.removeFromSuperview() })
UIView.animateWithDuration(dotDuration , delay: blueDelay, options: options, animations: {
blueDot.frame = CGRectMake(675, blueYPosition, blueSize, blueSize)
}, completion: { animationFinished in blueDot.removeFromSuperview() })
UIView.animateWithDuration(dotDuration , delay: yellowDelay, options: options, animations: {
yellowDot.frame = CGRectMake(675, yellowYPosition, yellowSize, yellowSize)
}, completion: { animationFinished in yellowDot.removeFromSuperview() })
UIView.animateWithDuration(dotDuration , delay: greenDelay, options: options, animations: {
greenDot.frame = CGRectMake(675, greenYPosition, greenSize, greenSize)
}, completion: { animationFinished in greenDot.removeFromSuperview() })
// FAILED ATTEMPT
var tapDot:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "dotTapped")
// FAILED ATTEMPT
redDot.addGestureRecognizer(tapDot)
// FAILED ATTEMPT
func dotTapped (recognizer: UITapGestureRecognizer) {
redDot.alpha = 0
}
durationCounter+=0.05
delayCounter+=50000
}
}
Your dotTapped method should be outside your animateButtonmethod.
Make sure userInteractionEnabled is true for the UIImageViews.

Resources