Im new to swift and I'm trying to create an app where a slider moves back and forth form a value of 1- 100 for 60 second by pressing a button.However,I am not sure how to make the slider move smoothly from 1-100.This is my code so far:
#IBOutlet weak var button: UIButton!
#IBOutlet weak var timer: UILabel!
var county = 5
#IBAction func animateSlider(sender: AnyObject) {if (county == 59){
_ = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("update"), userInfo: nil, repeats: true)}
if (county>0){
Slider.setValue(100, animated: true)
}
The Problem? The UISlider value instantly moves from 1-100.So, how do I slow down this animation to 5 seconds or so?
Im aware of a similar thread but i'm new to Swift and I believe the other thread is written in Objective-C.Thus,I would love if anyone could help me explain in terms of swift.Thanks in advance!
#IBAction func uiSliderShowProgress(sender: UISlider) {
if sender.value == 100 {
var transition: CATransition = CATransition()
transition.startProgress = 0
transition.endProgress = 1.0
transition.duration = 0.8
uiSliderShowProgress.layer.addAnimation(transition, forKey: "transition")
uiSliderShowProgress.value = 0
lblSliderProgress.text = "\(Int(uiSliderShowProgress.value))"
}
else {
var transition: CATransition = CATransition()
transition.startProgress = 0
transition.endProgress = 1.0
transition.duration = 0.8
uiSliderShowProgress.layer.addAnimation(transition, forKey: "transition")
uiSliderShowProgress.value = 100
lblSliderProgress.text = "\(Int(uiSliderShowProgress.value))"
}
}
#IBAction func btnChangeSliderValueClick(sender: UIButton) {
self.uiSliderShowProgress(uiSliderShowProgress)
}
This changes the value of slider from 0-100 and 100-0 on buttons click and makes the thumb of the slider fade in and out, creating an illusion of being delayed!!! Hope this will help you...
Related
Here is the code that I am using, at the bottom of the code is my timer it is a timer counting up and once it hits 60 minutes I would like for a button to turn red.
import UIKit
import AVFoundation
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func btnPressed1(_ sender: UIButton) {
sender.backgroundColor = sender.backgroundColor == UIColor.red ? UIColor.black : UIColor.red
}
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var progressBar1: UIProgressView!
let start = 5
var timer = Timer()
var player: AVAudioPlayer!
var totalTime = 0
var secondsPassed = 0
#IBAction func startButtonPressed(_ sender: UIButton) {
let startB = sender.titleLabel?.text
totalTime = start
progressBar1.progress = 0.0
secondsPassed = 0
titleLabel.text = "coffee timer"
timer = Timer.scheduledTimer(timeInterval: 1.0, target:self, selector: #selector(updateTimer), userInfo:nil, repeats: true)
}
#objc func updateTimer() {
if secondsPassed < totalTime {
secondsPassed += 1
progressBar1.progress = Float(secondsPassed) / Float(totalTime)
print(Float(secondsPassed) / Float(totalTime))
} else {
timer.invalidate()
titleLabel.text = "check coffee"
let url = Bundle.main.url(forResource: "alarm_sound", withExtension: "mp3")
player = try! AVAudioPlayer(contentsOf: url!)
player.play()
}
}
}
I need the button to turn the color red after my timer ends and if possible when the button is pressed have the color turn back to black.
You could add an IBOutlet to the button, and then use that outlet to update the button in your updateTimer routine.
An alternative to adding an IBOutlet to the button is to pass the button as the userInfo: parameter of the Timer.
You can pass anything you want as the userInfo: and right now you're just passing nil. If you change nil to sender, then the button will be passed along to the Timer.
timer = Timer.scheduledTimer(timeInterval: 1.0, target:self,
selector: #selector(updateTimer), userInfo: sender,
repeats: true)
Then, add the Timer parameter to updateTimer:
#objc func updateTimer(t: Timer) {
if let button = t.userInfo as? UIButton {
button.backgroundColor = .red
}
}
Making use of userInfo makes even better sense if you have multiple buttons that share the same updateTimer code. By creating a structure to hold the secondsPassed and button and passing that structure as userInfo:, you could have multiple buttons using multiple timers at the same time and each Timer would know which button it was assigned to.
I am playing around to learn and implemented a progressView that is triggered using a function and reset using another one when buttons are clicked (and feed it with value). For some reason, after the first click, it works fine even though the functions are called also on the following clicks and the progressView is reset, its animation is not restarting (checked functions and values are all correct using print()...)
import UIKit
class ViewController: UIViewController {
let eggTimes = ["Soft" : 5, "Medium" : 7, "Hard": 12]
#IBOutlet weak var progressView: UIProgressView!
//reset the progress bar
func resetProgress(){
progressView.setProgress(0, animated: true)
print("function progress view reset fired")
}
//starts the prgress bar
func startProgressView(duration: Double) {
UIView.animate(withDuration: duration) {
print("progress bar has been initiated")
self.progressView.setProgress(1.0, animated: true)
}
}
#IBAction func hardnessSelected(_ sender: UIButton) {
resetProgress()
print("progress bar has been reset")
let hardness = sender.currentTitle!
var timerCounter = eggTimes[hardness]! * 10
let temp1: Double = Double(timerCounter)
print("temp 1 = \(temp1) ")
self.startProgressView(duration: temp1)
print(eggTimes[hardness]!)
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { (timer) in
if timerCounter > 0 {
timerCounter -= 1
// print(timerCounter)
}
else {
print("egg ready")
timer.invalidate()
}
}
}
}
I'm new to UI programming and right now I'm trying to make the screen pulse at a speed based on the number of times the screen has been tapped. My issue is that when a tap is detected and the duration of the animation is shortened, it starts the animation from the beginning creating a white flash as it restarts. How would I go about speeding up the animation from whatever point it is at when a tap is detected.
My Code:
class ViewController: UIViewController {
var tapCount: Int = 0
var pulseSpeed: Double = 3
override func viewDidLoad() {
super.viewDidLoad()
counter.center = CGPoint(x: 185, y: 118)
pulseAnimation(pulseSpeed: pulseSpeed)
}
func pulseAnimation(pulseSpeed: Double) {
UIView.animate(withDuration: pulseSpeed, delay: 0, options: [UIViewAnimationOptions.repeat, UIViewAnimationOptions.autoreverse],
animations: {
self.red.alpha = 0.5
self.red.alpha = 1.0
})
}
#IBOutlet weak var red: UIImageView!
#IBOutlet weak var counter: UILabel!
#IBAction func screenTapButton(_ sender: UIButton) {
tapCount += 1
counter.text = "\(tapCount)"
pulseSpeed = Double(3) / Double(tapCount)
pulseAnimation(pulseSpeed: pulseSpeed)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
You need to use Core Animation directly to achieve what you're after instead of relying on the UIView animation which is built in top.
// create animation in viewDidLoad
let pulseAnimation = CABasicAnimation(keyPath: "opacity")
pulseAnimation.fromValue = 0.5
pulseAnimation.toValue = 1.0
pulseAnimation.autoreverses = true
pulseAnimation.duration = 3.0
pulseAnimation.repeatCount = .greatestFiniteMagnitude
// save animation to property on ViewController
self.pulseAnimation = pulseAnimation
// update animation speed in screenTapButton
pulseAnimation.speed += 0.5
You may want to play with the speed numbers a little. The default speed is 1.0 and the animation specifies a duration of 3 seconds, so it should take 6 seconds to go from 0.5 to 1.0 back to 0.5. At a speed of 2.0, the same animation will happen twice as quickly, or 3 seconds for the full cycle.
I hope that helps!
I have amended some code I pulled from https://github.com/kaandedeoglu/KDCircularProgress
I have a small UIView which represents the circle progress timer. I have referenced it and also instantiated it to KDCircularProgress class.
I have managed to implement methods to start and reset the timer progress circular bar.
But I am having trouble restarting the circular progress bar when I pause the animation.
My code preamble:
import UIKit
class SomeTableViewController: UITableViewController {
//Circular progress vars
var currentCount = 1.0
let maxCount = 60.0
//Reference selected UIView & instantiate
#IBOutlet weak var circularProgressView: KDCircularProgress!
Start animation - 60 second animation:
if currentCount != maxCount {
currentCount += 1
circularProgressView.animateToAngle(360, duration: 60, completion: nil)
}
To stop and reset the animation:
currentCount = 0
circularProgressView.animateFromAngle(circularProgressView.angle, toAngle: 0, duration: 0.5, completion: nil)
To pause the animation:
circularProgressView.pauseAnimation()
How would I set up a method to restart the animation after the paused state?
Many thanks in advance for any clarification. It's my first animation, I have tried to resolve the matter myself, but cannot seem to find any syntax applicable to my particular case.
UPDATED SOLUTION:
Thanks to #Duncan C for putting me on the right path.
I solved my problem as follows ...
Since I initiated the counter's progress using currentCount += 1 I thought I would try to pause the counter with:
if currentCount != maxCount {
currentCount -= 1
circularProgressView.animateToAngle(360, duration: 60, completion: nil)
}
which I thought would have a 'net' effect on the counter (netting off counter +=1 and counter -=1) to effectively stop the counter. In theory this should being the counter's progress to zero, but it continued to count down.
So I reverted back to circularProgressView.pauseAnimation() to pause the circular counter animation.
To restart the animation after being paused, I had to amend the duration to represent the updated duration - i.e. the time at which the animation was paused.
I used a bit of a trick here and included a NSTimer - which I happened to have in my code anyway.
To restart the animation at the time of pause:
if currentCount != maxCount {
currentCount += 1
circularProgressView.animateToAngle(360, duration: NSTimeInterval(swiftCounter), completion: nil)
}
I couldn't figure out how to update my duration for the animated circular bar, but did know how to update time passed using an NSTimer - timer with the same duration and countdown speed. So I tagged the reference to the updated timer's value. Issue resolved ;)
My Code:
import UIKit
class SomeFunkyTableViewController: UITableViewController {
//Circular progress variables
var currentCount = 0.0
let maxCount = 60.0
#IBOutlet weak var circularProgressView: KDCircularProgress!
//Timer countdown vars
var swiftTimer = NSTimer()
var swiftCounter = 60
#IBOutlet weak var startButton: UIButton!
#IBOutlet weak var timerView: UIView!
#IBOutlet weak var timerLabel: UILabel!
#IBOutlet weak var startView: UIView!
override func viewDidLoad() {
circularProgressView.angle = 0
timerLabel.text = String(swiftCounter)
super.viewDidLoad()
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func startButton(sender: AnyObject) {
pauseBtn.alpha = 1.0
playBtn.alpha = 1.0
stopBtn.alpha = 1.0
circularProgressView.hidden = false
if currentCount != maxCount {
currentCount += 1
circularProgressView.animateToAngle(360, duration: 60, completion: nil)
}
startView.hidden = true
timerView.hidden = false
swiftTimer = NSTimer.scheduledTimerWithTimeInterval(1, target:self, selector: #selector(SomeFunkyTableViewController.updateCounter), userInfo: nil, repeats: true)
}
#IBOutlet weak var pauseBtn: UIButton!
#IBAction func pauseButton(sender: AnyObject) {
circularProgressView.pauseAnimation()
swiftTimer.invalidate()
pauseBtn.alpha = 0.5
playBtn.alpha = 1.0
stopBtn.alpha = 1.0
}
#IBOutlet weak var playBtn: UIButton!
#IBAction func playButton(sender: AnyObject) {
if currentCount != maxCount {
currentCount += 1
circularProgressView.animateToAngle(360, duration: NSTimeInterval(swiftCounter), completion: nil)
}
if !swiftTimer.valid {
swiftTimer = NSTimer.scheduledTimerWithTimeInterval(1, target:self, selector: #selector(SomeFunkyTableViewController.updateCounter), userInfo: nil, repeats: true)
}
if swiftCounter == 0 {
swiftTimer.invalidate()
}
pauseBtn.alpha = 1.0
playBtn.alpha = 0.5
stopBtn.alpha = 1.0
}
#IBOutlet weak var stopBtn: UIButton!
#IBAction func stopButton(sender: AnyObject) {
currentCount = 0
circularProgressView.animateFromAngle(circularProgressView.angle, toAngle: 0, duration: 0.5, completion: nil)
circularProgressView.hidden = true
timerView.hidden = true
startView.hidden = false
swiftTimer.invalidate()
swiftCounter = 60
timerLabel.text = String(swiftCounter)
pauseBtn.alpha = 1.0
playBtn.alpha = 1.0
stopBtn.alpha = 0.5
}
func updateCounter() {
swiftCounter -= 1
timerLabel.text = String(swiftCounter)
if swiftCounter == 0 {
swiftTimer.invalidate()
}
}
}
Side Note : I have two overlapping views - StartView and TimerView. One is hidden on view load, hence the hide/unhide references. And I dim buttons on press - play/pause/stop.
Pausing and resuming an animation takes special code. You might not be able to do it without modifying the library you are using.
The trick is to set the speed on the animation on the parent layer that's hosting the animation to 0 and record the time offset of the animation.
Here are a couple of methods (written in Objective-C) from one of my projects that pause and resume an animation:
- (void) pauseLayer: (CALayer *) theLayer
{
CFTimeInterval mediaTime = CACurrentMediaTime();
CFTimeInterval pausedTime = [theLayer convertTime: mediaTime fromLayer: nil];
theLayer.speed = 0.0;
theLayer.timeOffset = pausedTime;
}
//-----------------------------------------------------------------------------
- (void) removePauseForLayer: (CALayer *) theLayer;
{
theLayer.speed = 1.0;
theLayer.timeOffset = 0.0;
theLayer.beginTime = 0.0;
}
//-----------------------------------------------------------------------------
- (void) resumeLayer: (CALayer *) theLayer;
{
CFTimeInterval pausedTime = [theLayer timeOffset];
[self removePauseForLayer: theLayer];
CFTimeInterval mediaTime = CACurrentMediaTime();
CFTimeInterval timeSincePause =
[theLayer convertTime: mediaTime fromLayer: nil] - pausedTime;
theLayer.beginTime = timeSincePause;
}
Note that starting in iOS 10, there is a newer, better way to pause and resume UIView animations: UIViewPropertyAnimator.
I have a sample project (written in Swift) on Github that demonstrates this new UIViewPropertyAnimator class. Here's the link: UIViewPropertyAnimator-test
Below is an extract from the README from that project:
A UIViewPropertyAnimator allows you to easily create UIView-based animations that can be paused, reversed, and scrubbed back and forth.
A UIViewPropertyAnimator object takes a block of animations, very much like the older animate(withDuration:animations:) family of UIView class methods. However, a UIViewPropertyAnimator can be used to run mulitiple animation blocks.
There is built-in support for scrubbing an animation by setting the fractionComplete property on the animator. There is NOT an automatic mechanism to observe the animation progress, however.
You can reverse a UIViewPropertyAnimator animation by setting its isReversed property, but there are some quirks. If you change the isReversed property of a running animator from false to true, the animate reverses, but you can't set the isReversed property from true to false while the animation is running and have it switch direction from reverse to forward "live". You have to first pause the animation, switch the isReversed flag, and then restart the animation. (To use an automotive analogy, you can switch from forward to reverse while moving, but you have to come to a comlete stop before you can switch from reverse back into drive.)
I want to increment UILabel.text value from 0 to say 100 using CoreAnimation with easing functions (e.g. easeInOutQuad). But it seems like text property is not animatable. So how to achieve this with the help of CA? Or Would I need to implement easing function myself and call it using GCD?
Thanks
P.S. I'd like to stick to CA as much as possible.
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
var counter = 0
override func viewDidLoad() {
super.viewDidLoad()
let timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("animate"), userInfo: nil, repeats: true)
timer.fire()
}
func animate() {
UIView.transitionWithView(label,
duration: 1.0,
options: [.CurveEaseInOut],
animations: { () -> Void in
self.counter += 1
self.label.text = "\(self.counter)"
}, completion: nil)
}
}
You can achieve that using core animation. You can add animation to your label. Try out following
CATransition *textAnimation = [CATransition animation];
textAnimation.type = kCATransitionMoveIn;
textAnimation.duration = 0.5;
textAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[numLabel.layer addAnimation:textAnimation forKey:#"changeTextTransition"];
You can change your label text anytime later on adding animation and it will animate.
numLabel.text = "1";