How to make more than one object rotate? - ios

I am building an app and I need to create gravity and rotation for three buttons. I wrote an infinite rotate function for one of my buttons but I don't know how to link it to the other two.
import UIKit
class ViewController: UIViewController {
var animator: UIDynamicAnimator?
var gravity: UIGravityBehavior?
var isRotating = false
#IBOutlet var Ball1: UIButton!
#IBOutlet var Ball2: UIButton!
#IBOutlet var Ball3: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
self.animator = UIDynamicAnimator(referenceView: self.view)
if !isRotating {
// create a spin animation
let spinAnimation = CABasicAnimation()
// starts from 0
spinAnimation.fromValue = 0
// goes to 360 ( 2 * π )
spinAnimation.toValue = M_PI*2
// define how long it will take to complete a 360
spinAnimation.duration = 1
// make it spin infinitely
spinAnimation.repeatCount = Float.infinity
// do not remove when completed
spinAnimation.removedOnCompletion = false
// specify the fill mode
spinAnimation.fillMode = kCAFillModeForwards
// and the animation acceleration
spinAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
// add the animation to the button layer
Ball1.layer.addAnimation(spinAnimation, forKey: "transform.rotation.z")
} else {
// remove the animation
Ball1.layer.removeAllAnimations()
}
// toggle its state
isRotating = !isRotating
}
}

You can add the animation layer to your UIButtons this way.
Ball2.layer.addAnimation(spinAnimation, forKey: "transform.rotation.z")
Ball3.layer.addAnimation(spinAnimation, forKey: "transform.rotation.z")
and remove them this way
Ball2.layer.removeAllAnimations()
Ball3.layer.removeAllAnimations()

Related

Horizontal flip with UICollisionBehavior

I'm working on a small piece of an app where I need a view to move to the opposite end of the screen when tapped. Once it collides with the screen it should to a horizontal flip (transform). And this works when it moves from left to right. But when it moves from right to left it does a vertical flip and is upside down until it collides with the left side of the screen. I can't figure out why it's doing this. Here's the relevant portion of my code.
class ViewController: UIViewController {
#IBOutlet weak var fish: UIImageView!
#IBOutlet weak var fishLeading: NSLayoutConstraint!
enum Direction: CGFloat {
case left = -1
case right = 1
func reversed() -> Direction {
return Direction(rawValue: -rawValue)!
}
}
var fishAnimator: UIDynamicAnimator?
var fishDirection = Direction.right
#objc func onFishTap(_ sender: UITapGestureRecognizer) {
let animator = UIDynamicAnimator(referenceView: water)
let collisionBehavior = UICollisionBehavior(items: [fish])
collisionBehavior.translatesReferenceBoundsIntoBoundary = true
collisionBehavior.collisionDelegate = self
collisionBehavior.collisionMode = .boundaries
animator.addBehavior(collisionBehavior)
let pushBehavior = UIPushBehavior(items: [fish], mode: .instantaneous)
pushBehavior.magnitude = 0.0000001
pushBehavior.pushDirection = .init(dx: fishDirection.rawValue, dy: 0)
animator.addBehavior(pushBehavior)
self.fishAnimator = animator
}
}
extension ViewController: UICollisionBehaviorDelegate {
func collisionBehavior(_ behavior: UICollisionBehavior, beganContactFor item: UIDynamicItem, withBoundaryIdentifier identifier: NSCopying?, at p: CGPoint) {
fishDirection = fishDirection.reversed()
UIView.animate(withDuration: 0.3, animations: {
self.fish.transform = .init(scaleX: self.fishDirection.rawValue, y: 1)
})
fishLeading.constant = fish.center.x - fish.frame.size.width / 2 + fishDirection.rawValue * 2
fishAnimator?.removeAllBehaviors()
fishAnimator = nil
}
}
As you can see in the first picture, when the fish is moving to the right it is right side up. When it is moving to the left it is upside down.

Swift/SceneKit - best practice to create menu / level select scenes for the game

I'm quite new to iOS level development, currently trying to create my first SceneKit game - however, i'm not sure what approach should i use to create the "non-3D" pages like menu, level select page, settings etc.
I currently have one GameViewController, and i use empty SCNScene with overlaySKScene to display my SpriteKit menu. Upon clicking the play button in menu i transition to another empty SCNScene where i would like to display list of levels. Should i again use ovelaySKSCene, is this the correct approach?
I also noticed that even if i set scnView.overlaySKScene = nil before transitioning to levelScene , if i tap in the spot of the previous placement of pla button, the nodePressed in overlayScene still contains the playButton node..
Forgive me for the rookie questions, i haven't been able to find a good example of this type of scenarios so far to learn from.
My code so far:
import UIKit
import QuartzCore
import SceneKit
import SpriteKit
class GameViewController: UIViewController {
var scnView: SCNView {
let scnView = view as! SCNView
scnView.backgroundColor = UIColor.init(displayP3Red: 0.98, green: 0.64, blue: 0.04, alpha: 1)
return scnView
}
var menuScene: SCNScene!
var levelsScene: SCNScene!
var ground: SCNNode!
var light: SCNNode!
var scnScene: SCNScene!
var cameraNode: SCNNode!
var brickNode: SCNNode!
var selectedBrickNode: SCNNode!
var bricksArray : [SCNNode] = []
var controls: SKScene!
var levels: SKScene!
override func viewDidLoad() {
super.viewDidLoad()
setupView()
showMenu()
setupCamera()
}
override var shouldAutorotate: Bool {
return true
}
override var prefersStatusBarHidden: Bool {
return true
}
func setupView() {
scnView.showsStatistics = true
scnView.allowsCameraControl = false
scnView.autoenablesDefaultLighting = true
}
func showMenu() {
menuScene = SCNScene( )
scnView.scene = menuScene
self.controls = Controls(size: self.view.bounds.size)
self.controls.isUserInteractionEnabled = false
scnView.overlaySKScene = self.controls
}
func showLevels(){
levelsScene = SCNScene()
self.levels = Levels(size: self.view.bounds.size)
self.levels.isUserInteractionEnabled = false
scnView.overlaySKScene = self.levels
let transition = SKTransition.push(with: .left, duration: 1.0)
self.scnView.present(levelsScene
, with: transition, incomingPointOfView: nil, completionHandler:nil)
}
func setupCamera(){
cameraNode = SCNNode()
let camera = SCNCamera()
camera.zFar = 10000
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 10, z: 20)
let constraint = SCNLookAtConstraint(target: ground)
constraint.isGimbalLockEnabled = true
cameraNode.constraints = [constraint]
let ambientLight = SCNLight()
ambientLight.color = UIColor.darkGray
ambientLight.type = SCNLight.LightType.ambient
cameraNode.light = ambientLight
menuScene.rootNode.addChildNode(cameraNode)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touch began ")
for touch: UITouch in touches {
let location = touch.location(in: scnView.overlaySKScene!)
let nodePressed = self.controls.atPoint(location)
print(nodePressed.name)
if (nodePressed.name == "playButton") {
scnView.overlaySKScene = nil
showLevels()
}
}
}
}
“the nodePressed in overlayScene still contains the playButton node...”
You are setting the pointer to the overlay scene to nil, and in touchesbegan you get the location from the overlay scene but then you use atPoint on the skscene stored in the “controls” property, so it still finds the play button. So replace self.controls with scnView.overlaySKScene in touchesbegan.
That said, I don’t know what in general is considered best practice by others but personally, once I need more than just a couple of buttons, I stop using the spritekit overlay scene and instead use regular UIKit elements to build menus on top of the SCNView.

How do I speed up an animation while it is already occurring using swift?

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!

Change views on UIScrollView so music changes

I have a UIScrollView setup but when I change view the music doesn't stop. How do I make it so the music stops when you change view?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear( animated)
meditationState = .on
setTrackForPlayerWith(trackName: "Bigsur")
player.play()
player.numberOfLoops = -1
}
Here is the whole class from viewcontroller.swift. I've added the func scrollViewDidScroll and the self part that you mentioned but it's still not working.
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
let vc0 = ViewController0(nibName: "ViewController0", bundle: nil)
var frame0 = vc0.view.frame
frame0.origin.x = self.view.frame.size.width
vc0.view.frame = frame0
self.addChildViewController(vc0)
self.scrollView.addSubview(vc0.view)
vc0.didMove(toParentViewController: self)
let vc1 = ViewController1(nibName: "ViewController1", bundle: nil)
var frame1 = vc1.view.frame
frame1.origin.x = self.view.frame.size.width
vc1.view.frame = frame1
self.addChildViewController(vc1)
self.scrollView.addSubview(vc1.view)
vc1.didMove(toParentViewController: self)
let vc2 = ViewController2(nibName: "ViewController2", bundle: nil)
var frame2 = vc2.view.frame
frame2.origin.x = self.view.frame.size.width * 2
vc2.view.frame = frame2
self.addChildViewController(vc2)
self.scrollView.addSubview(vc2.view)
vc2.didMove(toParentViewController: self)
let vc3 = ViewController3(nibName: "ViewController3", bundle: nil)
var frame3 = vc3.view.frame
frame3.origin.x = self.view.frame.size.width * 3
vc1.view.frame = frame3
self.addChildViewController(vc3)
self.scrollView.addSubview(vc3.view)
vc3.didMove(toParentViewController: self)
self.scrollView.contentSize = CGSize(width: Double(self.view.frame.size.width * 4), height: Double(self.view.frame.size.height - 66))
self.scrollView.delegate = self
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.x > self.view.frame.size.x {
player.stop()
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
ViewController3 where 'player' is declared:
import UIKit
import AVFoundation
enum MeditationState {
case on
case off
}
class ViewController3: UIViewController {
var player:AVAudioPlayer = AVAudioPlayer()
var player1:AVAudioPlayer = AVAudioPlayer()
var meditationState: MeditationState?
var replicatorLayer = CAReplicatorLayer()
var dot = CALayer()
func updateTimer(){
seconds += 1
timerclock.text = "\(seconds)"
}
// Animation starts running
func animation2() {
// A layer that creates a specified number of copies of its sublayers (the source layer), each copy potentially having geometric, temporal, and color transformations applied to it.
replicatorLayer = CAReplicatorLayer()
// The layer’s bounds rectangle. Animatable.
replicatorLayer.bounds = CGRect(x: 0.0, y: 0.0, width: 300.0, height: 300.0)
// The radius to use when drawing rounded corners for the layer’s background. Animatable.
replicatorLayer.cornerRadius = 10.0
// The background color of the receiver. Animatable.
replicatorLayer.backgroundColor = UIColor(white: 0.0, alpha: 0.0).cgColor
// The layer’s position in its superlayer’s coordinate space. Animatable.
replicatorLayer.position = view.center
// calling this method creates an array for that property and adds the specified layer to it.
view.layer.addSublayer(replicatorLayer)
// connectng the animation to the content
// An object that manages image-based content and allows you to perform animations on that content
dot = CALayer()
// The layer’s bounds rectangle. Animatable.
dot.bounds = CGRect(x: 0.0, y: 0.0, width: 12.0, height: 12.0)
//The layer’s position in its superlayer’s coordinate space. Animatable.
dot.position = CGPoint(x: 150.0, y: 40.0)
//The background color of the receiver. Animatable.
dot.backgroundColor = UIColor(white: 0.2, alpha: 1.0).cgColor
// The color of the layer’s border. Animatable.
dot.borderColor = UIColor(white: 1.0, alpha: 1.0).cgColor
// The width of the layer’s border. Animatable.
dot.borderWidth = 1.0
//The radius to use when drawing rounded corners for the layer’s background. Animatable.
dot.cornerRadius = 5.0
//Appends the layer to the layer’s list of sublayers.
replicatorLayer.addSublayer(dot)
// number of copies of layer is instanceCount
let nrDots: Int = 1000
//The number of copies to create, including the source layers.
replicatorLayer.instanceCount = nrDots
// The basic type for floating-point scalar values in Core Graphics and related frameworks.
let angle = CGFloat(2*M_PI) / CGFloat(nrDots)
// The transform matrix applied to the previous instance to produce the current instance. Animatable.
replicatorLayer.instanceTransform = CATransform3DMakeRotation(angle, 0.0, 0.0, 1.0)
// Type used to represent elapsed time in seconds.
let duration: CFTimeInterval = 10.0
// animation capabilities for a layer property.
// An object that provides basic, single-keyframe animation capabilities for a layer property.
let shrink = CABasicAnimation(keyPath: "transform.scale")
// Defines the value the receiver uses to start interpolation.
shrink.fromValue = 1.0
// Defines the value the receiver uses to end interpolation.
shrink.toValue = 0.1
// Specifies the basic duration of the animation, in seconds.
shrink.duration = duration
// Determines the number of times the animation will repeat.
shrink.repeatCount = Float.infinity
// Add the specified animation object to the layer’s render tree.
dot.add(shrink, forKey: "shrink")
// Specifies the delay, in seconds, between replicated copies. Animatable.
replicatorLayer.instanceDelay = duration/Double(nrDots)
// The transform applied to the layer’s contents. Animatable.
dot.transform = CATransform3DMakeScale(0.01, 0.01, 0.01)
}
// connecting the breathe in label
#IBOutlet weak var label: UILabel!
// instant delay
#IBOutlet weak var instantDelay: UIButton!
#IBAction func delayBtn(_ sender: Any) {
dot.removeAnimation(forKey: "shrink")
timer1.invalidate()
seconds = 0
timer2.invalidate()
timerclock.text = "\(seconds)"
time = 0
timerLabel.text = "Breathe in"
timerisOn = false
pauseBtn.isHidden = true
playBtn.isHidden = false
label.isHidden = true
replicatorLayer.isHidden = true
instantDelay.isHidden = true
instantDelay1.isHidden = false
slider.isHidden = false
}
// Delay 1
#IBOutlet weak var instantDelay1: UIButton!
#IBAction func delayBtn1(_ sender: Any) {
instantDelay1.isHidden = true
instantDelay.isHidden = false
label.isHidden = false
slider.isHidden = true
}
//Slider for changing animation speed
#IBOutlet weak var slider: UISlider!
#IBAction func slider(_ sender: Any) {
}
#IBAction func speed(_ sender: UISlider) {
view.layer.speed = sender.value
}
//Sound On button
#IBOutlet weak var soundOn: UIButton!
#IBAction func SoundOn(_ sender: Any) {
meditationState = .on
setTrackForPlayerWith(trackName: "Mute")
player.play()
soundoff.isHidden = false
soundOn.isHidden = true
}
//Sound Off button
#IBOutlet weak var soundoff: UIButton!
#IBAction func SoundOff(_ sender: Any) {
meditationState = .off
setTrackForPlayerWith(trackName: "Bigsur")
player.play()
soundoff.isHidden = true
soundOn.isHidden = false
}
//Timerclock at top of screen label
#IBOutlet weak var timerclock: UILabel!
// creating vars to set things
var animation = CFTimeInterval()
var timer1 = Timer()
var timer2 = Timer()
var time = 0
var seconds = 0
var timerisOn = false
// connecting breathe in label
#IBOutlet var question: UILabel!
var arrayOfStrings: [String] = [""]
// connecting timerclick and starting it
#IBOutlet var timerLabel: UILabel!
// changes the amount of time on the label of different labels
func increaseTimer() {
time += 1
switch time {
case 0 ... 7:
timerLabel.text = "Hold"
case 8 ... 10:
timerLabel.text = "Breathe Out"
case 11 ... 12:
timerLabel.text = "Breathe in"
default:
time = 0
}
}
// connecting the play button and vars
#IBOutlet weak var playBtn: UIButton!
#IBAction func play(sender: AnyObject) {
bell(trackName: "Bell")
player1.play()
timer1 = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController3.increaseTimer), userInfo: nil, repeats: true)
pauseBtn.isHidden = false
playBtn.isHidden = true
if timerisOn == false {
timer2 = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
timerisOn = true
}
animation2()
}
// pausing the timer with the vars
#IBOutlet weak var pauseBtn: UIButton!
#IBAction func pause(sender: AnyObject) {
dot.removeAnimation(forKey: "shrink")
timer1.invalidate()
seconds = 0
timer2.invalidate()
timerclock.text = "\(seconds)"
time = 0
timerLabel.text = "Breathe in"
timerisOn = false
pauseBtn.isHidden = true
playBtn.isHidden = false
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear( animated)
meditationState = .on
setTrackForPlayerWith(trackName: "Bigsur")
player.play()
player.numberOfLoops = -1
}
override func viewDidLoad() {
super.viewDidLoad()
time += 1
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient)
print("AVAudioSession Category Playback OK")
do {
try AVAudioSession.sharedInstance().setActive(true)
print("AVAudioSession is Active")
} catch let error as NSError {
print(error.localizedDescription)
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
func setTrackForPlayerWith(trackName: String) {
do
{
let audioPath = Bundle.main.path(forResource: trackName, ofType: "mp3")
try player = AVAudioPlayer(contentsOf: NSURL(fileURLWithPath: audioPath!) as URL)
}
catch
{
//ERROR
}
}
func bell(trackName: String) {
do
{
let audioPath = Bundle.main.path(forResource: trackName, ofType: "mp3")
try player1 = AVAudioPlayer(contentsOf: NSURL(fileURLWithPath: audioPath!) as URL)
}
catch
{
//ERROR
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Are you looking for viewWillDisappear(_:)? In that method you can just add player.stop() to stop the music when you change views.
The smartass way to do this is just ....
let's say it's a vertical table.
1. Each table view cell has an audio track associated with it. Think of one particular cell - C - it has an audio track A.
2. As the view scrolls (ie, whenever it is moving), just get the frame of C
3. Just take the height. Then take the height of the screen SH. Then get the distance of C from the center of the screen .. so Abs(SH - H). Then just get that figure as a fraction (zero to one) of the SH. So, Abs(SH - H)/H
(Depending on your situation, it may be better if that is divided by the height of cells, rather than screen height.)
4. Now ... simply set the volume of all the audio track A, to that fraction. And in fact, simply do that for every cell.
As you scroll, the audio will mix between the various tracks.
"Magic" :)
You can observe the changes made in your UIScrollView subclass in various delegate methods.
First of all, lets make sure your scrollView's delegate is assigned to the viewController. To do so, one option is to add following to viewDidLoad()
// `scrollView` should be whatever is your scrollView called in your VC
self.scrollView.delegate = self
Once this is done, lets make your UIViewController subclass conform to UIScrollViewDelegate
class ViewController: UIViewController, UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.x > self.view.frame.size.x {
player.stop()
}
}
}
UIScrollViewDelegate has numerous methods to observe changes in your scrollView. scrollViewDidScroll(_:) will be called every time there is an interaction with the scrollView, so as soon as the contentOffset is greater than the width of the view, lets stop the music.

How To Pause & Resume Animation - KDCircularProgress Circular Bar

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.)

Resources