Adding & Removing NSNotificationCenter Observer - UIApplicationDidEnterBackgroundNotification - The Correct Way? - ios

I have a UIView that plays audio, has a NSTimer and progress circular bar animation.
To keep things simple, if the user switches apps or takes a call, I would like all processes to stop and reset themselves.
I propose to use:
Call the Observer - perhaps in viewWillAppear:
override func viewWillAppear(animated: Bool) {
// set observer for WillEnterForeground
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(WorkoutFullFace1Exercise7TVC.willEnterBackground), name: UIApplicationDidEnterBackgroundNotification, object: nil)
...}
and
Stop relevant tasks:
// Stop all audio, timer and animation if app enters background.
func willEnterBackground() {
myAudioPlayer.stop()
myAudioPlayer.currentTime = 0
swiftTimer.invalidate()
swiftCounter = 60
timerLabel.text = String(swiftCounter)
pauseBtn.alpha = 1.0
playBtn.alpha = 1.0
stopBtn.alpha = 1.0
currentCount = 0
circularProgressView.animateFromAngle(circularProgressView.angle, toAngle: 0, duration: 0.5, completion: nil)
}
Dismiss the observer:
override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIApplicationDidEnterBackgroundNotification, object: nil)
.... }
Where is the correct place to load and dismiss the observer? I have read threads that state to use viewDidLoad/ deinit, viewWillAppear / Disappear, etc..
Can anyone please shed some light on what is recommended for my scenario and also what is current and likely to remain 'usable' in the future (language and practise seems to change rapidly in the programming World).
And am I using the correct syntax above?
Thanks!

Everything seems correct to me
Just make sure to call super implementation in each method. That can
lead to some issues when you are subclassing things later
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated);
// other code
...}
// same for viewWillDisappear

Related

Does iOS/UIKit have built in support for scheduling scroll like dinging

I want to implement an action that when I press and hold begins to repeatedly do an action (similar to a scroll button on a Desktop UI). Is there first class support for this in the UIGestureRecognizer/events framework, or do I just roll my own?
E.g.
var timer:Timer?
func killDing() {
self.timer?.invalidate()
self.timer = nil
}
func startDing() {
self.killTimer()
self.timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) {
self.ding() // this is where the repeated code happens
}
}
override func beginTracking(_ touch:UITouch, with event:UIEvent?) -> Bool {
self.startDing()
}
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
super.endTracking(touch, with: event)
self.killDing()
}
I can of course do this with a LongPressGestureRecognizer as well. My question is whether I need to roll my own ding loop as shown above, or if there's something more first class in UIKit that I'm currently not aware of and should be taking advantage of.
I think you are on the right way. You can use use timers to repeat some actions, but you should add the created timer into a run loop with a common mode, without this mode, the run loop will not call the timer's action while a user is touching the screen
let timer = Timer(timerInterval: interval, repeats: true, block: block)
RunLoop.current.add(timer, forMode: . common)
Also you can use CADisplayLink, to call your action. You can find example of using CADisplayLink in my library, witch can help to you implement animation based on CADisplayLink:
let link = CADisplayLink(target: self, selector: #selector(updateAction(sender:)));
link.add(to: RunLoop.main, forMode: .commonModes);

Definitively, do you have to invalidate() a CADisplayLink when the controller disappears?

Say you have an everyday CADisplayLink
class Test: UIViewController {
private var _ca : CADisplayLink?
#IBAction func frames() {
_ca?.invalidate()
_ca = nil
_ca = CADisplayLink(
target: self,
selector: #selector(_step))
_ca?.add(to: .main, forMode: .commonModes)
}
#objc func _step() {
let s = Date().timeIntervalSince1970
someAnime.seconds = CGFloat(s)
}
Eventually the view controller is dismissed.
Does anyone really definitively know,
do you have to explicitly call .invalidate() (and indeed nil _ca) when the view controller is dismissed?
(So perhaps in deinit, or viewWillDisappear, or whatever you prefer.)
The documentation is worthless, and I'm not smart enough to be able to look in to the source. I've never found anyone who truly, definitively, knows the answer to this question.
Do you have to explicitly invalidate, will it be retained and keep running if the VC goes away?
A run loop keeps strong references to any display links that are added to it. See add(to:forMode:) documentation:
The run loop retains the display link. To remove the display link from all run loops, send an invalidate() message to the display link.
And a display link keeps strong reference to its target. See invalidate() documentation:
Removing the display link from all run loop modes causes it to be released by the run loop. The display link also releases the target.
So, you definitely have to invalidate(). And if you're using self as the target of the display link, you cannot do this in deinit (because the CADisplayLink keeps a strong reference to its target).
A common pattern if doing this within a view controller is to set up the display link in viewDidAppear and remove it in viewDidDisappear.
For example:
private weak var displayLink: CADisplayLink?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
startDisplayLink()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
stopDisplayLink()
}
private func startDisplayLink() {
stopDisplayLink() // stop previous display link if one happens to be running
let link = CADisplayLink(target: self, selector: #selector(handle(displayLink:)))
link.add(to: .main, forMode: .commonModes)
displayLink = link
}
private func stopDisplayLink() {
displayLink?.invalidate()
}
#objc func handle(displayLink: CADisplayLink) {
// do something
}
Method definition of invalidate():
Removing the display link from all run loop modes causes it to be
released by the run loop. The display link also releases the target.
For me this means that displaylink holds the target and run loop holds DispayLink.
Also, according to this link I found, it looks like its rather important to call invalidate() for cleanup of CADisplayLink.
We can actually validate using XCode's wonderful Memory graph debugger:
I have created a test project where a DetailViewController is being pushed on a navigation stack.
class DetailViewController: UIViewController {
private var displayLink : CADisplayLink?
override func viewDidAppear() {
super.viewDidAppear()
startDisplayLink()
}
func startDisplayLink() {
startTime = CACurrentMediaTime()
displayLink = CADisplayLink(target: self,
selector: #selector(displayLinkDidFire))
displayLink?.add(to: .main, forMode: .commonModes)
}
}
This initiates CADispalyLink when view gets appeared.
If we check the memory graph, we can see that DetailViewController is still in the memory and CADisplayLink holds its reference. Also, the DetailViewController holds the reference to CADisplayLink.
If we now call invalidate() on viewDidDisappear() and check the memory graph again, we can see that the DetailViewController has been deallocated successfully.
This to me suggests that invalidate is a very important method in CADisplayLink and should be called to dealloc CADisplayLink to prevent retain cycles and memory leaks.

UIProgressView not working as expected with NSTimer - Swift

I am using an NSTimer to let the user know the app is working. The progress bar is set up to last 3 seconds, but when running, it displays in a 'ticking' motion and it is not smooth like it should be. Is there anyway I can make it more smooth - I'm sure just a calculation error on my part.
Here is the code:
import UIKit
class LoadingScreen: UIViewController {
var time : Float = 0.0
var timer: NSTimer?
#IBOutlet weak var progressView: UIProgressView!
override func viewDidLoad() {
super.viewDidLoad()
// Do stuff
timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector:Selector("setProgress"), userInfo: nil, repeats: true)
} //close viewDidLoad
func setProgress() {
time += 0.1
progressView.progress = time / 3
if time >= 3 {
timer!.invalidate()
}
}
}
As per Apple iOS SDK docs you can achieve it with the use of next API:
func setProgress(_ progress: Float, animated animated: Bool)
It adjusts the current progress shown by the receiver, optionally animating the change.
Parameters:
progress - The new progress value.
animated - true if the change should be animated, false if the change should happen immediately.
More info on this:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIProgressView_Class/index.html#//apple_ref/occ/instm/UIProgressView/setProgress:animated:
So in your case you should do it like this:
func setProgress() {
time += 0.1
dispatch_async(dispatch_get_main_queue()) {
progressView.setProgress(time / 3, animated: true)
}
if time >= 3 {
timer!.invalidate()
}
}
Also please note that it is a good practice to perform UI updates on main thread, so I just dispatched progress update on main queue.
Hope it will help you.

Progress for LongPressureGesture on a Button

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.

How to pause an animation in SWIFT?

I have seen several answers here on stackoverflow but they are all diferent to what I really need, and also they are all in Objective C...
So what I have is an animation of an object called stick that starts up and then goes down to the screen. When I press a button, I want it to pause and I want the stick to be precisely at that spot!
Also, hand is the button that stops the animation, that only stops when hand and stick and overlap.
Here's my code:
#IBAction func touchHand(sender: UIButton) {
let stickPresentationFrame = (stick.layer.presentationLayer() as! CALayer).frame
if !CGRectIsNull(CGRectIntersection(stickPresentationFrame, hand.frame)){
println("sup")
}
}
override func viewDidAppear(animated: Bool) {
UIView.animateWithDuration(2, animations: {self.stick.center = CGPointMake(self.stick.center.x, 760)}, completion: nil)
}
Any ideas? Thanks

Resources