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.
Related
While reading an audio file (which can be big), I want to display a progress view on a main view controller subview (it is an UIView).
I wrote a method for that in my UIViewController:
- (void) displayFileReadingView
{
self.fileReadingView = [[UIView alloc] initWithFrame:CGRectMake(20., 100., 300., 120.)];
self.readingProgress = [[UIProgressView alloc] initWithFrame:CGRectMake(20., 90., 200., 8.)];
self.readingMessage = [[UILabel alloc] initWithFrame:CGRectMake(45., 10., 200., 20.)];
... elements parameter settings ...
[fileReadingView addSubview:self.readingMessage];
[fileReadingView addSubview:self.readingProgress];
[self.view addSubview: self.fileReadingView];
}
In the viewDidAppear methods, after all last initializations, I read an audio file (using AVURLAsset) and analyse the data to plot it.
When displayFileReadingView is called from viewDidLoad, or viewWillAppear, the view is displayed correctly. When it is called from viewDidAppear, as a first line, it is not displayed until several seconds after the end of the viewDidAppear execution. This is my first question:
• Why an UI method to allocate views, subviews and add them to the appropriate view, allow display or not depending of the calling method, since the displayFileReadingView method is not dependent of any initialization parameter from the main view managed by the owning UIViewController?
Here is the second problem. My UIProgressView is supposed to be updated every second, using a NSTimer scheduled to call updateReadingProgress
- (void) updateReadingProgress {
[self.readingProgress setProgress:myNewValue animated:YES];
[self.fileReadingView setNeedsDisplay];
}
But unfortunately, the NSTimer is not fired in time. If the allocation (using either ScheduledTimerWithTimerInterval... or TimerWithInterval... + adding in main loop (all modes tested) is made normally, it is never fired. If I put the same allocation in a dispatch_main_async() (or dispatch_after...) the timer is fired many seconds after for the first time, sometimes up to 25 seconds.
The result, depending on my code choices explained here: sometimes the view is not displayed, sometimes not. When it is, its UIProgressView subview is never updated, or updated dozen of seconds after all other calculations are done (after the owner view should be removed from display).
I also tried to replace NSTimer with CADisplayLink to fire updateReadingProgress method, using an "ordinary" call or "dispatched in main queue" included call, but in this CADisplayLink case it is never fired. Spent two days and nights in trying combinations, reading developer websites... no solution!
This is my second question:
• How can I be sure that a method updating the UI get called periodically without giant delay (some .2 or .5 second delay is not a problem?
[Using Xcode9 for iOS10 target, tested on Simulator and devices (iPhone/iPad)]
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.
I'm creating a game view controller using an NSTimer to update a progress bar representing the remaining time of the current game round. This game view also displays a button performing some light core data updates.
When the remaining time is up, the timer is invalidated and a new view is pushed.
The first time the game view loads in the application life cycle, it works perfectly: no slowdowns when pressing the button and updating core data.
But when I'm pushing back the same game view in the application life cycle, button presses make the progress bar choppy and irresponsible.
Is the NSTimer not properly invalidated in the run loop? Should I use CADisplayLink instead, though my view isn't using a lot of resources?
Thanks in advance.
Here is my code:
Timer declaration in .h file :
#property (weak, nonatomic) NSTimer *updatetimer;
Timer creation in viewDidLoad:
self.updatetimer = [NSTimer timerWithTimeInterval:counterStep target:self selector:#selector(updateProgress) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:self.updatetimer forMode:NSRunLoopCommonModes];
updateProgress function :
- (void)updateProgress
{
//compute current time
currentTime = currentTime - counterStep;
//set timer label to int value of current time
self.timerLabel.text = [[NSString alloc] initWithFormat:#"%d",[[NSNumber numberWithDouble:currentTime] intValue]];
//update progress bar accordingly
[self.progressbar setProgress: currentTime / totalTime animated:YES];
if(currentTime <= 0)
{
//call the method that invalidates the timer + pushes to the next view
[self overallTimeEnd];
}
}
Timer invalidation:
[self.updatetimer invalidate];
self.updatetimer = nil;
There's not enough here to diagnose the problem. Given that you've confirmed that your initial timer is successfully invalidated, the next thing to check is to make sure that the problem is really that updateProgress is not getting called with the desired frequency (rather than whatever is updating the state variables that updateProgress relies upon).
But, let's assume that the problem is truly that updateProgress is not getting called with the desired frequency. If so, then it's probably something that you're running on that main thread that is preventing the timer from being able to run with the desired frequency. It's hard to say without seeing what updateProgress is doing, much less anything else you might have running on the main thread. WWDC 2012 video Building Concurrent User Interfaces on iOS illustrates how to use Instruments to find what might be adversely affecting the concurrent UX.
Finally, it's not clear underlying process that updateProgress is keeping us informed of, but if you can go to an event-driven approach (e.g. if network connection, update UI as didReceiveData is called rather than using a timer). Sometimes you have to use timers, but if you can avoid it, you might be able to improve the efficiency of the app.
So, finally found what was causing my timer not being deallocated over the application life cycle: I wasn't using unwind segues in my storyboard to go back between views but standard push segues.
Never used those before and didn't knew about them ...
Thanks to all of you for helping.
On my button to start my game i have a few NSTimers to make things scroll, i want to add a delay to these timers, not too sure how to though.
any advice?
Here's the buttons with 1 of the NSTimers
-(IBAction)StartGame:(id)sender{
StartGame.hidden = YES;
backgroundMovement = [NSTimer scheduledTimerWithTimeInterval:0.06 target:self selector:#selector(backgroundMoving)
userInfo:nil repeats:YES];
}
Thank you
When you call [NSTimer scheduledTimerWithTimeInterval:...] the time interval is the delay. You will be called back after the time interval you specify.
Alternatively, call performSelector:withObject:afterDelay:.
However, note please that there are deep flaws in your proposed architecture. A repeating timer with a .06 interval is a terrible idea, and more than one is an atrocious idea. You need to rethink this completely. Consider using real animation, or a display link, or sprite kit. Or something. Anything, really.
If you'd like to add a delay only once, when starting, you could implement a - (void)startTimers method, where you create and start your timers, then do
[self performSelector:#selector(startTimers) withObject:nil afterDelay:1.0f];
in your IBAction.
^ Don't copy-paste the performSelector method, as I might have made a typo, since I don't have Xcode open to verify.
Options to do this:
1) call a method with performSelector using a delay. In that method you create the NSTimer
[self performSelector:#selector(createTimer_backgroundMovement) withObject:nil
afterDelay:0.1];
2) if the delay is dependent of an action you can use this information in a flag and that flag can be used in the backgroundMoving method:
-(void) backgroundMoving {
if (!blAction_whichEnablesTimer) {
return;
}
// your backgroundMoving timer code
}
3) consider moving views with eg. block animations. Using block animations you can use delay also. Please see doc: https://developer.apple.com/library/ios/documentation/windowsviews/conceptual/viewpg_iphoneos/animatingviews/animatingviews.html
In my app I wish to have in a certain view a list of images that change on a UIImageView; something like an automated "presentation".
I'm looking for a way to perform this "automation". The idea is that I will have a list of images, each one will also have a timing property >> something like "Image_1" + "00:00", "Image_2" + "00:27", "Image_3" + "01:04" etc...
I guess I should probably use an NSDictionary to hold the file names and timing.
The only thing I'm not clear about, is which method I should use to "catch" the timing property from the NSDictionary and relate to them as "events" that run my code that will deal with the image change.
Anyone?
You can set up a NSTimer to fire after the required time, or you could use – performSelector:withObject:afterDelay:. NSTimer is probably the best option because you can cancel them if the view goes away.
Create a timer to trigger after the first delay.
When the timer fires, change the image (using a nice transition if you like) and set up the timer for the next delay.
If the view is removed, cancel the current timer.
I would use NSTimer you can set it to fire at a set time or at intervals, by using the current date. Id have NSTimer call a method which changes the images.
- (id)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
This declaration method should do the trick
NSTimer Developer Doc
Hope this helps