Adding time to NSTimer - ios

I'm creating an iOS test game as practice and want a game over method to be ran at the end of a 15 second timer.
I also want time to be added to this timer and taken away if certain actions take place.
Not having much luck figuring out how to add time or take away time from a certain timer depending on actions by the user.
So far I have the timer in viewDidLoad and a method with some conditional formatting that will do things depending on what the user does.

Once it has been created, the period of an NSTimer cannot be changed, the timer can only be cancelled or allowed to fire after the requested period.
In order to implement a timer for your game I would suggest that you set up a repeating timer that fires every second. The actual 'time remaining' value is stored in an integer and you decrement this value each time the timer fires. When this value reaches 0, end the game.
This way you can easily add additional time by simply changing the value of the time remaining variable.
This approach also makes it simple to display the remaining time on the screen.

Once NSTimer has been created, you cannot change time interval. But here's method I've done for me:
func addTimeToTimer(timer: NSTimer, time: NSTimeInterval, target: AnyObject, selector: Selector, repeats: Bool) -> NSTimer {
let currentInterval = Float(timer.timeInterval)
let targetInterval = NSTimeInterval(currentInterval + Float(time))
let newTimer = NSTimer.scheduledTimerWithTimeInterval(targetInterval, target: target, selector: selector, userInfo: timer.userInfo, repeats: repeats)
return newTimer
}
You will need to supply a lot of parameters because NSTimer does not store it all. Hope it helps.

Related

Is it bad practice to modify view after viewDidDisappear called?

I have UITabBarController and in one of the UIViewController there I scroll UICollectionView each 5 second using Timer. Here is short code how I do it:
override func viewDidLoad() {
super.viewDidLoad()
configureTimer()
}
private func configureTimer() {
slideTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(scrollCollectionView), userInfo: nil, repeats: true)
}
#objc func scrollCollectionView() {
collectionView.scrollToItem(at: someIndexPath, at: .centeredHorizontally, animated: true)
}
It perfectly works. But I think it has a big issue. Of course, I can open another screen from this UIViewController (for example, I can tap to another tab or push another UIViewController). It means, my UIViewController's view, containing UICollectionView, disappears. In another words, viewDidDissapear will be called. But my timer still exists and I am having strong reference to it, possibly, there is retain cycle. It keeps working and each 5 second scrollCollectionView method is called even my view dissapeared. I don't know how, but iOS somehow handles it. In other words, it can modify view even it is not visible. How is that possible and is it good practice? Of course, I can invalidate my timer in viewDidDissapear and start it in viewDidAppear. But I don't want to loose my timer value and don't want to start it from zero again. Or may be it is ok to invalidate my timer in deinit?
My question covers pretty common situation. For instance, if I make network request and open another UIViewController. After that request finished, I should modify UI, but now am on another screen. Is it ok to allow iOS to modify UI even it is not visible?
A couple of thoughts:
If the timer is updating the UI at some interval, you definitely should start it in viewDidAppear and stop it in viewDidDisappear. There’s no point in wasting resources updating a view that is not visible.
By doing this, you can solve your strong reference cycle, too.
In terms of “losing” your timer value and starting at zero, we generally would just save the time you’re counting from or to and calculate the necessary value when restarting the timer later.
We do this, anyway, because you really shouldn’t be using timers to increment values because you’re technically not assured that they’ll be called with the frequency you expect.
All of that said, I don’t know what timer “value” you’re worried about losing in this example.
But definitely don’t waste time updating a UI that is no longer visible. It’s not scalable and blurs the distinction between the model (what you’re counting to or from) from the UI (the update that happens every five seconds).

Update UITableViewCell using Timer without blocking UI

I'm trying to create a UITableView that contains a bunch of cells that count down from a value. Initially I was just running an NSTimer in the ViewController on repeat to run a function that would update the labels and then run a reloadData on the table, and that does technically work, but with some issues.
To fill things in a bit more, I'm well aware NSTimer isn't accurate as a timer, so I use a date calculation for the time on the labels. I also considered using CADisplayLink so the refresh would sync with the screen, but this ran into problems with scrolling the table, making it very jumpy. Another issue is when issuing the Edit command on the table, the UI is unable to delete rows because of the refresh.
So, I've since considered moving my Timer into each UITableViewCell, and I can tell that it is running via the console, but the table doesn't update. This makes sense, given that it can't run the reloadData on the table, but I'm a bit stuck as to where to go next.
I found a similar thread here and even tried the AsyncTimer suggested here but either I'm doing something wrong, or these aren't quite the same issues? Any help is appreciated, thank you!
var timer = Timer()
self.timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.checkTimers), userInfo: nil, repeats: true)
#objc func checkTimers() {
for counter in counters {
if counter.isActive {
counter.updateSeconds()
}
}
tableView.reloadData()
}

How to make NSTimer more accurate?

In my app, I put a label on the view controller and I want that label to display a number which is incremented by 1 every millisecond. (I know I can just update it every second by 1000, but I want it to look smooth)
#IBOutlet var label: UILabel!
var number = 0
var timer: NSTimer!
override func viewDidLoad() {
super.viewDidLoad()
timer = NSTimer.scheduledTimerWithTimeInterval(0.001, target: self, selector: Selector("addNumber"), userInfo: nil, repeats: true)
}
func addNumber () {
number++
label.text = number.description
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
timer?.invalidate()
}
The code all looks very sensible to me, but when I run the app, I see that the number is increasing by about 50 every second, not 1000 every second. That error is far from acceptable!
Surprisingly, when I lock the screen for a few seconds and unlock it again, the number suddenly increases by a few thousands!
I think this is because I am updating the UI when the timer fires so it requires more time. But how can I fix this?
Your question is asking how to make NSTimer more accurate and I have some bad news: you can't. A reasonable expectation of NSTimer is that it can be called ~30 times per second and, if you're lucky, up to ~60 times per second. And that's okay.
Thinking of this from a practical point of view: the display can only be updated up to 60 times per second, so there is no need to give user feedback more often than that.
If you're trying to max out the hardware and rely on it firing as fast as possible, then you're going to have issues on older and slower hardware where it might only fire ~20 times per second.
Relying on the time interval of NSTimer will get you in trouble quickly.
Since you're looking for milliseconds, you can set a start time and every time your function is called look at the number of milliseconds since your start time and that's the number of transpired milliseconds.
Your current method does everything Apple's documentation says it should: number is just the count of times that method has been called (not the number of transpired milliseconds).
Building on #Fennelouski's answer, what I suggest is you swap to a CADisplayLink which is pretty much made for this kind of task. It will call your method once every screen update (or once every 2nd or 3rd, or whatever you make the frameInterval property). it also has a timestamp variable that you can use to calculate how much time has passed and update your label accordingly

Objective C - Continue NSTimer after popping previous page

I have an NSTimer in a view controller which starts counting when the user presses a button. It counts down seconds for 3 minutes. What I want is that if the user wants to go back previous page (maybe go another page) and come back to this timer page, he/she should see counting timer label. I mean, I know timer continues to counting but when I load this page again, I cannot see its label as i left. Also I check my timer and get it's not considered as "valid".
I don't want to use AppDelegate for this purpose because I have many view controllers and this one is not that important. So, how can I achieve this continuous timer label? Should I use static timer or flag?
Thank you.
you cna implement a
static int counter = 0 ;
by having a selector method in the NSTimer and use that counter to continue the counting
[NSTimer scheduledTimerWithTimeInterval:180.0f
target:self
selector:#selector(updateTime:)
userInfo:nil
repeats:YES];
// Implement Method
-(void)updateTime: (NSTimer *) timer {
counter++;
self.label.text = counter;
}
Making the timer static might introduce nasty memory problems as it holds a reference to it's target.
But as that already is no problem for you seemingly, that'd be a good way to go. Better yet: create a singleton object for your timer that encapsulaten the NSTimer and the value it's counting. This way, your view controllers can use KVO on the value and are otherwise completely independent of the timer.

NSTimer to scroll UITableView is slow

I'm using NSTimer to fire a method that scrolls UITableView
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:#selector(scroller)
userInfo:nil
repeats:YES];
-(void)scroller
{
[self.row1TableView setContentOffset:CGPointMake(self.row1TableView.contentOffset.x, self.row1TableView.contentOffset.y - 50) animated:YES];
}
Problem is that the scrolling seems slow. It's closer to 1 second than .1 seconds in the interval.
What's the problem?
As far as I know, you can not change the default animation duration of setContentOffset:animated:. But what you can do is, setup a Core Animation display link (CADisplayLink - you can search for code samples on how to set up, but it is quite straight-forward. The class documentation should be a good place to start) and it will fire every frame, calling back a method you provide.
Inside that callback method, you can calculate how much you want to scroll your table view (how many points per frame), and call setContentOffset:animated: with the second parameter set to NO (immediate scrolling). You should implement some sort of easing to achieve better results.
Note: The reason for using CADisplayLink instead of NSTimer is, it is more reliable. It is what you would use in games before SpriteKit was available.
Addendum: This blog post has some sample code on how to setup the display link and the respective callback method.
Addendum 2: You can setup an instance variable to act as a "counter", and increment it by the ammount of time ellapsed since last frame, within each call of your callback (use properties duration and/or frameInterval). Once the counter reaches a critical value (that is, the animation has run for enough time) you can stop the display link update by calling the method:
-[CADisplayLink invalidate].
NSTimer that calls a selector on the current threads run loop. It may not be 100% precise time-wise as it attempts to dequeue the message from
the run loop and perform the selector.

Resources