How do I structure a Timer in an external class in Swift? - ios

I am trying to create a new stopwatch timer.
I have my:
Main Timer ViewController
a timer Model - where I store my timer.
When my timer finishes in my model how can I let the main ViewController know that it's been completed?
Ideally I would like to run a function the main UIViewController when the timer is completed in the model.
Class myTimer: NSObject {
func stopTimer() {
timer?.invalidate()
print("timer Stopped at \(currentTime)")
}
}

I'd do this by posting, registering and deallocating notifications.
-registering for notifications, do this in viewDidLoad of viewController.
NSNotificationCenter.defaultCenter().addObserver(self, selector: "timerExpired", name: "TimerCompleteNotification", object: nil)
where selector is the function to call when the timer complete notification is posted.
-posting notifications. Add this in whatever function updates the timer label and calls this when the timer expires.
NSNotificationCenter.defaultCenter().postNotificationName("TimerCompleteNotification", object: nil)
Just make sure "TimerCompleteNotification" is the spelled the same in both posting and addObserver. What you are doing here is saying, "I'd like to observe a notification that may happen, and when it does, I'll call this function," And then when the timer ends, you post that notification and perform the designated function.
Forgot to tell you to add this to your view controller or you will get an access error
deinit{
//Unregister for notificationsd
NSNotificationCenter.defaultCenter().removeObserver(self)
}

Have your MyTimer class do exactly the kind of thing an NSTimer already does. The "user" of MyTimer registers a callback function when it creates / starts MyTimer, and the MyTimer calls that function when it fires.

Related

How to restart or stop a Timer in Swift without destroying it?

I'm developing an iOS application using a bluetooth device with a button that is communicating with the iPad. Basically I want a help request to be issued when the button is held for 3 seconds or longer.
From all the documentation I found, I couldn't find a way to stop a Timer without invalidating it, with the invalidate() method. From Apple's documentation:
The run loop then removes the timer (and the strong reference it had to the timer), either just before the
invalidate()
method returns or at some later point. Once invalidated, timer objects cannot be reused.
So the idea in my code is that when the button is pressed, the boolean buttonWasHeld is set to true and a timer is fired. If the button is released, buttonWasHeld is set to false and, when the timer calls the handler it knows the button wasn't held long enough. Then if the button is pressed again within the 3 seconds, the timer is set over again.
Problem is: every button press makes a new timer, which means that repeatedly pressing the button will also issue the help request. Furthermore, all those timers are addressed by the same variable so I can't tell them apart.
Is there a way to uniquely tell what was the last timer created? Or an obscure way to pause/stop it?
Here's the piece of code controlling this feature:
var buttonTimer: Timer?
var buttonWasHeld: Bool = false
func didUpdateModule() {
// gpioListener takes a handler to be called whenever a button is
// pressed or released. isPushed is a self-explanatory boolean.
self.controller.gpioListener() { isPushed in
if isPushed {
self.buttonWasHeld = true
self.buttonTimer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { _ in
if self.buttonWasHeld {
// Issue a help request
self.delegate?.notifyDevice(message: .HELP)
print("Asking for help")
}
}
print("Button was pressed")
}
else {
self.buttonWasHeld = false
// Also tried "self.buttonTimer = nil" here. Didn't work
print("Button was released")
}
}
}
As usual, the answer was quite simple.
If the Timer is declared as a weak var, and not just var, only the weak instantiation will be invalidated. So the code should be:
weak var buttonTimer: Timer?
var buttonWasHeld: Bool = false
func didUpdateModule () {
(...)
else {
// This will only invalidate the current running timer,
// not the whole variable :)
self.buttonTimer.invalidate
// I removed buttonWasHeld, it's not necessary anymore ;)
print("Button was released")
}
}
}

View controller with strong references causing memory leaks

I have an issue with strong references in one of my view controllers which is causing a memory leak. First, my setup:
2 view controllers (v1 and v2). v1 segues to v2, and v2 has a close button that pops itself back to v1. v2 contains code that tries to reconnect infinitely until a connection is made. (video streaming using red5pro). Here is the code:
func reconnect(){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(5.0 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) { () -> Void in
self.connectToStream()
}
}
The continuous reconnecting is desirable in my situation, but when the user exits v2, I want the reconnecting to stop. But currently, the reconnect goes on infinitely, even when the user has left v2.
I have come to know that this is because v2 has strong references and continues to live even after the user exits from it. So this is causing the code that is infinitely calling the reconnect() method to continue running. I'm going to try to clean up v2 to convert everything to weak references, but I am also considering some alternatives, and I had a couple questions regarding it:
Is there a way to kill the reconnecting on viewDidDisappear or something, so even if my view controller doesn't get destroyed, at least my reconnecting process stops?
After exiting from v2 back to v1, if the user again segues to v2, is it possible to assign the same instance of v2 rather than creating a new instance of it each time?
The dispatch_after cannot be canceled, but there are a couple of options:
Use weak references, which will allow self to be deallocated:
func reconnect() {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(5.0 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) { [weak self] in
self?.connectToStream()
}
}
This admittedly keeps the timer going, but prevents it from retaining the view controller, so the view controller is released and connectToStream will not be called.
Use NSTimer and cancel it when the view disappears:
weak var timer: NSTimer?
func reconnect() {
timer?.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: #selector(handleTimer(_:)), userInfo: nil, repeats: false)
}
func handleTimer(timer: NSTimer) {
self.connectToStream()
}
override func viewDidDisappear() {
super.viewDidDisappear()
timer?.invalidate()
}
Note, because this selector-based NSTimer keeps a strong reference to its target, you cannot cancel in deinit (because there's a strong reference cycle). So you have to find some other appropriate event to resolve this (e.g. viewDidDisappear).

Changing scenes with sprite kit after a certain time in Swift?

I would like to change from scene to scene after a certain amount of time on one scene in Swift. I am trying to create a survival type game that the player has to survive a level for so long before they can advance to the next level.
I know that after I get to func being called I will be using this to go to the next scene.
self.view?.presentScene(GameScene2())
I am sure that something along the lines of NSTimer is going to be used, but anything that can be given to point me in the right direction would be of great help. Thank you in advance.
An NSTimer is one option. Depending on what sort of accuracy you need and how long your duration will be you may or may not want to use an NSTimer. Example of an NSTimer ...
var myTimer = NSTimer()
func startMyTimer() {
myTimer = NSTimer.scheduledTimerWithTimeInterval(1.5, target: self, selector: "myFunction", userInfo: nil, repeats: true)
//1.5 is the time interval that you want to call the timer, in this case every 1.5 seconds
//the selector calls myFunction which is the function that you want to call every time the timer reaches its time interval
//if you want the timer to repeat and call myFunction() every 1.5 seconds then repeat should be true. if you only want it to be called once then repeat should be false
}
func myFunction(){
//do whatever i am supposed to do when the timer reaches my specified interval
}
Now this may not be solution you are looking for. Another way is to use GCD's (grand Central Dispatch) dispatch_after . A very nice and extremely easy to use function can be found here, dispatch_after - GCD in swift? , in the second answer down, compliments of stackoverflow's #matt
When your view is instantiated, you want to begin an NSTimer. See this page for help creating this: How can I use NSTimer in Swift?. This page may help as well: Using an NSTimer in Swift.
override func viewDidLoad() {
super.viewDidLoad()
var timer = NSTimer.scheduledTimerWithTimeInterval(30.0, target: self, selector: "update", userInfo: nil, repeats: true)
Then, you can just put the
self.view?.presentScene(GameScene2())
in a func update like so:
func update() {
self.view?.presentScene(GameScene2())
Hope this helps. If so, please check off my answer. If not, comment and I will try and help again.

Show/Hide UIButton in another controller

I have a button in my ViewController.swift:
#IBOutlet weak var exampleButton: UIButton!
I would like to show/hide that button from the AppDelegate, when something specific happens (i.e. the app enter background etc.).
How can i do that?
One Approach can be
- You can use Notifications for this
Add Observer in your view controller where button needs to be hidden
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: "hideButton",
name: #"HIDE_BUTTON_NOTIFICATION",
object: nil)
func hideButton() -> Void {
// Hide your button here
// Remember to hide on main thread
}
From wherever you want to hide the button (like from AppDelegate), you can post this notification
NSNotificationCenter.defaultCenter().postNotificationName(#"HIDE_BUTTON_NOTIFICATION", object: nil)
Rather than let another object directly manipulate it, use a notification to indicate a state change. The state dictates whether the button appears or not.
Make sure in your notification listener that you only touch the button on the main thread.
Notification is a great idea, but what if your ViewController is not your initial ViewController, or hasn't been initialized yet? It will not be able to catch this notification. Possible solution (maybe not elegant) in extension to other answers is to provide a flag. Your ViewController will be checking it, e.g. in viewDidLoad(). Your flag could be stored in a singleton object which will be catching notification from AppDelegate.
To sum up, you should add notification observer in your ViewController, to catch event from AppDelegate. (like in other answers)
Create singleton class to store appropriate information.
Check condition in viewDidLoad:
if YOUR_SINGLETON.YOUR_FLAG == true {
showButton()
} else {
hideButton()
}
Don't forget to add notification observer also in your singleton class.

Do I need to add observer if I have a func to take NSNotification as parameter

Just like the question title
for say I have code like
func receieveNotification(notification : NSNotification) {
......verify notification
......retrieve userInfo
}
Do I still need to add observer to NSNotificationCenter.defaultCenter() ?
If I do. How to?
An NSNotification is sent when some object calls the post method on an NSNotificationCenter. The notification center then calls the designated receiving method on each object that has been registered with it.
If you do not register with a notification center, then there is no way for the system to know that it should send the notification to you. Although there can be other registration centers, in iOS you will almost always use the default.
When you register for a notification, you specify which object is to receive the notification, what method on that object to call, which notification you are registering for, and what sender you want to receive notifications from. If you want to receive every one of a particular kind of notification (that is, you don't care which object sent it), you can specify nil for the sender.
Thus, to register for the notification, "MyNotification", and you don't care what object sent it, you call the following:
NSNotificationCenter.defaultCenter().addObserver(self, "gestureHandler", "MyNotification", nil)
Where to place this call depends on when you want this object to listen for it. For example, if the receiver is a UIView, you probably want to register when the view is about to be shown, not when the view is created.
It is extremely important that you unregister when you want to stop receiving notifications, such as when the receiver goes out of scope. You do this by calling 'removeObserver()`.
You should search through Xcode's documentation and read the Notification Programming Topics.
Yes it's required.
Use it like: Snippet taken from a great tutorial:
http://natashatherobot.com/ios8-where-to-remove-observer-for-nsnotification-in-swift/
class FirstViewController: UIViewController {
#IBOutlet weak var sentNotificationLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateNotificationSentLabel", name: mySpecialNotificationKey, object: nil)
}
// 2. Post notification using "special notification key"
#IBAction func notify() {
NSNotificationCenter.defaultCenter().postNotificationName(mySpecialNotificationKey, object: self)
}
func updateNotificationSentLabel() {
self.sentNotificationLabel.text = "Notification sent!"
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
}
Further topic: Swift and removing Observers:
http://natashatherobot.com/ios8-where-to-remove-observer-for-nsnotification-in-swift/

Resources