I'm using the code below. How can I disable the button before the Finish Animation effect? In that case when I clicked the button before finish animating than animation image restarts and it is not very smooth.
#IBAction func btn_LeeStatue(sender: AnyObject) {
lee_statue_img.animationImages = [
UIImage(named: "lee_statueAni0001.png")!,
UIImage(named: "lee_statueAni0002.png")!,
UIImage(named: "lee_statueAni0003.png")!,
UIImage(named: "lee_statueAni0004.png")!,
UIImage(named: "lee_statueAni0005.png")!,
]
lee_statue_img.animationDuration = 3
lee_statue_img.animationRepeatCount = 1
lee_statue_img.startAnimating()
}
UIImageView image animation doesn't have a "completion handler". It is much more simple-minded than that.
If you want a completion handler, use some sort of animation that does have a completion handler.
(In your case, I don't see why you even need a completion handler. You know when this animation will end: X second from now. So just set an NSTimer to call you back then.)
so your code will be:
func showButton(timer : NSTimer) {
YourButtonOutlet.enabled = true
//this will enable you button again
}
#IBAction func btn_LeeStatue(sender: AnyObject) {
lee_statue_img.animationImages = [
UIImage(named: "lee_statueAni0001.png")!,
UIImage(named: "lee_statueAni0002.png")!,
UIImage(named: "lee_statueAni0003.png")!,
UIImage(named: "lee_statueAni0004.png")!,
UIImage(named: "lee_statueAni0005.png")!,
]
//Disable the button
YourButtonOutlet.enabled= false
lee_statue_img.animationDuration = 3
lee_statue_img.animationRepeatCount = 1
lee_statue_img.startAnimating()
//set the timer for X seconds (4s in this example)
let myTimer : NSTimer = NSTimer.scheduledTimerWithTimeInterval(4, target: self, selector: Selector("showButton:"), userInfo: nil, repeats: false)
}
and you can also use dispatch after,
just add this function->
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
and you can call it like this->(0.75 is the time that the app should wait before executing whats in the closure)
delay(0.75, closure: {
//write here the code to be executed after 0.75 second
//in your case it will be YourButtonOutlet.enabled = true
})
PS: you have to set YourButtonOutlet.enabled = false before the animation start
I think you want to disable a button from users touch animate it then enable it to a user's touch. To do that you toggle the userInteractionEnabled property.
let button: UIButton = UIButton()
button.userInteractionEnabled = false
button.animateMethod()
button.userInteractionEnabled = true
Related
I have animateButtons() function of which I need to set completion handler only when the animation has finished. The problem is that in animateButtons() I can only set the completion handler just after imageView.startAnimating() so the whole timing gets compromised, as it's completion is used to launch other animations. I read in a another post with same exact issue, that I should set an NSTimer to set the completion handler , as I actually thought of doing, but I don't really know how to . I have set NSTimer to call setCompletion() function, but how would I set it to call animateButtons() completion handler ?
Can you point me in the right direction?
This is the function and the selector function:
static func animateButtons(completed: #escaping(Bool) ->(), imageView: UIImageView, images: [UIImage]) {
imageView.animationImages = images
imageView.animationDuration = 1.0 // check duration
imageView.animationRepeatCount = 1
imageView.startAnimating()
_ = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(setCompletion), userInfo: nil, repeats: false)
// completed(true)
}
#objc func setCompletion() {
}
You can try to use inline callback
static func animateButtons(completed: #escaping(Bool) ->(), imageView: UIImageView, images: [UIImage]) {
imageView.animationImages = images
imageView.animationDuration = 1.0 // check duration
imageView.animationRepeatCount = 1
imageView.startAnimating()
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false, block: { (t) in
t.invalidate()
completed(true)
})
}
BTW a dispatch after can do the job also
static func animateButtons(completed: #escaping(Bool) ->(), imageView: UIImageView, images: [UIImage]) {
imageView.animationImages = images
imageView.animationDuration = 1.0 // check duration
imageView.animationRepeatCount = 1
imageView.startAnimating()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
completed(true)
}
}
Approach 1 (I prefer this one) (If you are animating Gif images)
Use a third-party library like Gifu https://github.com/kaishin/Gifu to calculate the runtime of your gif. It will work even if you change the gif image in the future.
static func animateButtons(completed: #escaping(Bool) ->(), imageView: UIImageView, images: [UIImage]) {
imageView.prepareForAnimation(withGIFNamed: "welcome", loopCount: 1) {
self.imageView.startAnimatingGIF()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2 + self.imageView.gifLoopDuration, execute: {
// Do your Task after animation completes
)
}
}
Approach 2 (Calculate the run Time of the animation and set a timer)
I'm trying to create an app that is similar to SnapChat when it takes videos. You are supposed to hold down a button to start taking a video and then release it when you are done. I'm trying to implement a timeout feature after 7 seconds and also a progress bar by using a Timer object. However, when I try fire the timer, it only calls the given selector once and that's it even though I tell it to repeat itself.
Here's my code to initialize the button:
let photoButton:UIButton = {
let but = UIButton(type: .custom)
but.layer.cornerRadius = 40
but.layer.borderColor = UIColor.white.cgColor
but.layer.borderWidth = 4
but.clipsToBounds = true
but.addTarget(self, action: #selector(takeVideo), for: .touchDown)
but.addTarget(self, action: #selector(stopVideo), for: [.touchUpInside, .touchUpOutside])
but.translatesAutoresizingMaskIntoConstraints = false
return but
}()
Here's the function takeVideo that is called when the user starts to hold down the button. I initialize and fire the Timer object here:
#objc func takeVideo() {
progressBar.isHidden = false
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateProgressbar), userInfo: nil, repeats: true)
startTime = Date().timeIntervalSince1970()
timer?.fire()
}
This is the function stopVideo where I invalidate the Timer object:
#objc func stopVideo() {
timer?.invalidate()
// stop video
}
And this is the updateProgressBar function:
#objc private func updateProgressbar() {
let maxLength = 7.0
let difference = Date().timeIntervalSince1970 - startTime!
progressBar.progress = Float(difference / maxLength)
if difference >= maxLength {
stopVideo() // Invalidates the timer and will stop video.
}
}
It seems that this person has had a similar issue, but when I tried to incorporate his answer of presenting the current view controller using async, it still doesn't work. Here's how I present the video recording view controller:
let recorder = RecorderViewController()
recorder.db = db
DispatchQueue.main.async(execute: {
self.present(recorder, animated: true, completion: nil)
})
Edit: I've fixed the incorrectness concerning the Timer object's fireDate property (the fix is also fixed in the above code). It fixed my problem.
EDIT: I figured out I need to resume the layer if its paused, but am not sure how to resume the layer when the view returns. viewDidLoad doesn't seem to trigger on segue
I have an app with an animation, sound, and a webView. The animation is triggered by a button and a segue to the webView is triggered by another button. If the animation is playing when the app segues to the webView, once the app segues back, I have it set so the animation restarts when the play button is tapped. This works fine
However, when the animation is paused and the view changes, upon returning to the main view, the image alpha and label alpha doesn't appear to animate... although the debugger says the alpha is changing appropriately, nothing appears on screen.
How can I get it to animate appropriately when play is tapped after returning from the WebView?
Here's the play button:
touch += 1
//define animated layers
imgLayer = firstImg.layer
labelLayer = firstLbl.layer
if touch == 1 {
print("touch = \(touch)")
self.firstImg.alpha = 0
self.firstLbl.text = ""
animateStart()
player.play()
self.playOut.setBackgroundImage(UIImage(named: "pause.png"), for: UIControlState.normal)
} else {
touch = 2
}
//toggle pause
if touch == 2 {
print("touch = \(touch)")
pause = !pause
if pause {
animatePause()
player.pause()
} else {
animateResume()
player.play()
}
}
}
and the animate start block:
func animateStart() {
print("animation started")
UIView.animate(withDuration: 4, animations: {
self.firstImg.alpha = 1
print("firstImg alpha = \(self.firstImg.alpha)")
self.firstImg.image = UIImage(named:"peter.JPG")
self.view.layoutIfNeeded()
}, completion: { finished in
if finished {
self.animateSecond()
}
})
}
and here's how I dismiss the VC on segue
func dismissVC() {
touch = 0
self.playOut.setBackgroundImage(UIImage(named: "playbtn.png"), for: UIControlState.normal)
player.stop()
initAudio()
self.dismiss(animated: true, completion: nil)
}
backround.backgroundColor = UIColor.redColor()
//Delay code ?
backround.backgroundColor = UIColor.blueColor()
Is there anyway to do this? I do not know the exact code line to create this type of delay.
func performDelayBlock(block:() -> Void, afterDelay delay:NSTimeInterval)
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), block)
}
usage:
backround.backgroundColor = UIColor.redColor()
performDelayBlock({
self.backround.backgroundColor = UIColor.blueColor()
}, afterDelay: 1)
You can do it using NSTimer.
//Register for NSTimer wherever you are setting your first color and then set the interval time.
//Since you want to repeat you can just do repeat to be true
NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: "changeBackground", userInfo: nil, repeats: true)
func changeBackground() {
backround.backgroundColor = UIColor.redColor() //whichever you like
//Have conditional code to change it back and forth to red and other color you want to.
}
I got a button with a LongPressureGesture, and i would like to have a small ProgressView on top of this button as visual feedback for the user that the longPressureGesture is recognized.
I'm stuck on how to detect the beginning of the longPressure and the duration of the longPressure to be able to set the setProgress() on my ProgressView.
EDIT: So i inspired myself from the answers, thank you. Here is what i made. Feel free to comment the following code, maybe there is a better solution.
private var lpProgress:Float = 0
private var startTouch: NSTimer!
#IBAction func pauseJogButtonTouchDown(sender: AnyObject) {
self.startTouch = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: "updateLPGestureProgressView", userInfo: nil, repeats: true)
}
func updateLPGestureProgressView() {
self.lpProgress += 0.1
self.lpGestureProgressView.setProgress(self.lpProgress, animated: true)
if self.lpProgress >= 1 {
self.startTouch.invalidate()
self.pauseBarButton.hidden = true
self.lpGestureProgressView.setProgress(0.0, animated: false)
self.toolbarHomeMadeView.hidden = false
self.switchToState(.Paused)
}
}
#IBAction func pauseJogButtonTouchUpInside(sender: AnyObject) {
self.lpProgress = 0
self.startTouch.invalidate()
self.lpGestureProgressView.setProgress(0.0, animated: false)
}
You do not need the LongPressureGesture in this case.
Use "Touch Down" IBAction of UIButton to start NSTimer, and "Touch Up Inside" to stop timer and check if the delay was right.
ProgressView you can fill by timer progress.
Set up an NSTimer on touchesBegan.
At the same time start your animation to animate the view.
When touchesEnded is triggered then stop the animation if the NSTimer has not triggered yet and cancel the timer.
When the timer finishes run your desired action.
Long Press isn't really designed for this sort of thing.