I have a connected UILabel
#property (strong, nonatomic) IBOutlet UILabel *label;
And an Action, which is triggered by the button
- (IBAction)buttonPressed:(UIButton *)sender;
When button is pressed, i'd like to update the label to display running seconds up to 3 minutes, so i
- (IBAction)buttonPressed:(UIButton *)sender {
for (int i =0; i < 180; ++i) {
[label setText:[NSString stringWithFormat:#"%d", i]];
sleep(1);
}
}
Confirmed, method is called, timer is ticking ..the label text however does not change. What am i doing wrong please?
The sleep() does not allow the UI thread to update itself.
Here is a sample using GCD that closely matches you original code. Note: there are better ways to do this (see: dispatch_after()).
- (IBAction)buttonPressed:(UIButton *)sender {
[label setText:[NSString stringWithFormat:#"%d", 0]];
dispatch_queue_t queue = dispatch_queue_create("com.test.timmer.queue", 0);
dispatch_async(queue, ^{
for (int i = 1; i < 180; ++i) {
sleep(1);
dispatch_async(dispatch_get_main_queue(), ^{
[label setText:[NSString stringWithFormat:#"%d", i]];
});
});
}
your sleep() is in the main thread , your view cannot refresh , you can ues NSTimer to do it.
- (void)update
{
[label setText:[NSString stringWithFormat:#"%d", i]];
i++;
if(i>=100)
{
[timer invalidate];
}
}
- (IBAction)buttonPressed:(UIButton *)sender
{
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(update) userInfo:nil repeats:YES];
[timer fire];
}
You have to exit back to your runloop to allow the control to refresh. You should probably use an NSTimer that fires every second or so instead of a tight loop.
Although not really recommended, the following might also work if you call it right after setText:
[[NSRunLoop currentRunLoop] acceptInputForMode:NSDefaultRunLoopMode beforeDate:nil];
All UI related things are done in the main thread. Forcing your runtime to sleep on the main thread will literally freeze your UI. Do it 180 times and you got one frustrated end-user.
Related
I make a Cocoa application.I create a NSTimer,it can work,but can't stop with invalidate.The following is my code.Anyone else can help me?
#implementation Document
{
NSTimeInterval elapsedTime;
NSTimer *timer;
}
- (IBAction)start:(id)sender
{
elapsedTime = 0.002f;
dispatch_async(dispatch_get_main_queue(), ^{
if(timer == nil)
{
NSLog(#"Starting");
captureFrame = [[CaptureFrame alloc] init];//captureFrame is another object
timer = [NSTimer scheduledTimerWithTimeInterval:elapsedTime target:captureFrame selector:#selector(sendSingleImageImage) userInfo:nil repeats:YES];
NSThread *thread = [NSThread currentThread];
NSLog(#"Current thread is%#",thread);// I want to see the thread.
}
else
{
NSLog(#"Exist!");
[timer invalidate];
timer = nil;
}
});
[_stopButton setEnabled:YES];
[_startButton setEnabled:NO];
}
- (IBAction)stop:(id)sender
{
dispatch_async(dispatch_get_main_queue(), ^{
[timer invalidate];
timer = nil;
NSThread *sthread = [NSThread currentThread];
NSLog(#"S thread is %#",sthread);// I want to see the thread
});
[_startcastButton setEnabled:YES];
[_stopButton setEnabled:NO];
NSLog(#"Stopping");
}
I tried use the following method,but didn't work.
dispatch_async(dispatch_get_main_queue(), ^(void)block)
I also tried to find whether two actions in the same thread,and the result is yes.
So please tell me how to stop the NSTimer.
Any help is appreciated,thanks a lot in advance.
I have a NSTimer that works fine and counts down in 1 second intervals. But I want the timer to trigger immediately without that 1 second delay.
I thought calling [timer fire] should work for this (described here) but it doesn't make a difference. I want the timer to be triggered as fast as if I scheduled the interval to be 0.
- (void)onStartButtonPressed:(UIButton*)sender
{
CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:tv];
NSIndexPath* indexPath = [tv indexPathForRowAtPoint:buttonPosition];
NSInteger index = indexPath.row;
// starts timer for cell at the index path
if (indexPath != nil)
{
NSTimer* timer = [timerArray objectAtIndex: index];
if ([timer isEqual:[NSNull null]]) {
NSLog(#"It's empty");
// start timer
NSTimer timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(onTick:)
userInfo:indexPath
repeats:YES];
// [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
NSLog(#"before timer fire");
[timer fire];
// update data array of timer objects and countdowns
NSInteger selectedTimeIdx = [[selectedTimeIdxArray objectAtIndex: index] integerValue];
NSInteger selectedTime = [pickerTimeArray[selectedTimeIdx] integerValue];
[timerArray replaceObjectAtIndex:index withObject:timer];
[countdownArray replaceObjectAtIndex:index withObject:[NSNumber numberWithInteger:selectedTime*60]];
} else {
NSLog(#"It's not empty");
}
}
- (void)onTick:(NSTimer *)timer
{
NSLog(#"on tick method starts");
// get the timer's owner's index path and update label
NSIndexPath* indexPath = [timer userInfo];
NSInteger index = indexPath.row;
// update countdown
NSInteger countdown = [[countdownArray objectAtIndex: index] integerValue];
[countdownArray replaceObjectAtIndex:index withObject:[NSNumber numberWithInteger:--countdown]];
// NSLog(#"countdown: %ld", (long)countdown);
[tv reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
// NSLog(#"Tic indexPath: %#", indexPath);
if (countdown == 0)
{
[timer invalidate];
timer = nil;
}
}
The timer works but I don't want there to be a 1 second delay for it to be initially triggered. I want the timer to start immediately.
Edit: I added logs that rmaddy suggested. Here are my results (I changed the interval time to 3):
2015-05-19 14:41:02.827 restaurant[4206:77915] before timer fire
2015-05-19 14:41:02.827 restaurant[4206:77915] on tick method starts
2015-05-19 14:41:05.827 restaurant[4206:77915] on tick method starts
2015-05-19 14:41:08.828 restaurant[4206:77915] on tick method starts
Why not just call your method before the timer
[self onTick:nil];
//usual timer code here
Edit: as stated by rmaddy
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(onTick:)
userInfo:indexPath repeats:YES];
[self onTick:timer];
Some of the comments were really helpful, especially adding the logs as suggested by rmaddy. It turns out [timer fire] was working fine.
I had confused myself because my timerLabel which displayed the countdown was being updated in my cell with a 1 second delay, and I thought that meant the timer instantiation was delayed.
Once I saw what the problem really was, all I had to do was update the cell in the same block that I instantiated the timer (instead of just onTick).
I just added this to my onStartButtonPressed and everything works as I want it to.
// update countdown
NSInteger countdown = [[countdownArray objectAtIndex: index] integerValue];
[countdownArray replaceObjectAtIndex:index withObject:[NSNumber numberWithInteger:--countdown]];
// cell label gets updated right at start button press
[tv reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
Leaving here in case it helps anyone.
I have a pretty large array I get from my database peopleArray that consists of all the users of my app. This array is used for searching for friends. My problem is, when the user begins to type in the search bar the app often freezes for a moment before display the searched user.
#pragma mark - SEARCH BAR
- (void) filterContententForSearchText: (NSString *) searchText scope:(NSString *) scope{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K beginsWith[cd] %#",#"Name", searchText ];
self.searchArray = [self.peopleArray filteredArrayUsingPredicate:predicate];
}
- (BOOL) searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString{
[self filterContententForSearchText:searchString scope:[[self.searchDisplayController.searchBar scopeButtonTitles] objectAtIndex:[self.searchDisplayController.searchBar selectedScopeButtonIndex]]];
return YES;
}
- (void) searchDisplayControllerDidEndSearch:(UISearchDisplayController *)controller{
[self.tableView reloadData];
}
I would like to do this in the background so I can put a UIActivityIndicator in the tableView as it loads but am not sure where or how to implement which method in the background.
First of all, I would suggest using a timer so that you don't reload the users on EVERY keypress. I do that like this:
//I put this in my private #interface
#property (nonatomic, strong) NSTimer *searchTimer;
//Then we have the method called on keypress
- (void)whateverMethodIsCalledOnKeypress {
[self.searchTimer invalidate];
self.searchTimer = nil;
//put some logic that returns out of the function for empty strings etc. here
self.searchTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(runSearch) userInfo:nil repeats:NO];
}
- (void)runSearch {
//do whatever you need to run the search this way
//it's only ever done at most once per second
//so fast typists don't overload the processor
}
And here is some code to do an async filtering.
//Show your activity indicator
dispatch_async(dispatch_get_global_queue(0,0), ^{
//call whatever you need to do on the filtering here
dispatch_async(dispatch_get_main_queue(), ^{
//Hide your activity indicator
[self.tableView reloadData];
});
});
I have the code below displaying an ActivityIndicator before calling a GCD process. The background process throws a Notification when it's complete or encounters an error. I am calling the stopAnimating method in the error handler but the spinner keeps spinning. Why?
UIActivityIndicatorView *mIndicator;
#interface VC_Main ()
#end
- (void)viewDidLoad
{
[super viewDidLoad];
[NSLog(#"viewDidLoad");
// create indicator for download activity
mIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[mIndicator setCenter:CGPointMake([self getScreen].x /2.0, [self getScreen].y / 2.0)]; // landscape mode
[self.view addSubview:mIndicator];
// fire off a single interval
[NSTimer scheduledTimerWithTimeInterval:0.0
target:self
selector:#selector(timerTask:)
userInfo:nil
repeats:NO];
...
}
- (void) timerTask:(NSTimer *) timer
{
NSLog(#"DEBUG: timertask timeout");
[mIndicator startAnimating];
...
}
// if there is an error parsing xml downloaded from server, it notifies here
- (void) xmlError:(NSNotification *)note
{
NSLog(#"error parsing xml");
[mIndicator stopAnimating]; // this doesn't work
// fire off a refresh using retry timeout
[NSTimer scheduledTimerWithTimeInterval:TIMEOUT_RETRY_MINS
target:self
selector:#selector(timerTask:)
userInfo:nil
repeats:NO];
NSLog(#"will retry in %d", TIMEOUT_RETRY_MINS);
}
As with every UIKit call, you need to do it on the main thread.
Just do:
dispatch_async(dispatch_get_main_queue(), ^{
[mIndicator stopAnimating];
});
and it should work
Perhaps you retry too soon?
scheduledTimerWithTimeInterval:TIMEOUT_RETRY_MINS
It's supposed to be seconds, not minutes.
I'm missing something about how UIActivityIndicatorView and NSTimer work together.
I've added this UIActivityIndicatorView in Interface Builder with the following settings:
The UIWebView is instantiated as self.webV and the UIActivityIndicatorView as self.indicator.
I have the following code in the implementation file:
-(void)viewDidLoad
{
[super viewDidLoad];
//Create UIWebView.
if (!self.webV)
{
self.webV = [[UIWebView alloc] init];
}
self.webV.delegate = self;
//Load web page.
NSString *baseURLString = #"myURL.com";
NSString *urlString = [baseURLString stringByAppendingPathComponent:#"myURL.com"];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0/2.0 target:self selector:#selector(timerLoad) userInfo:nil repeats:YES];
[self connectWithURL:urlString andBaseURLString:baseURLString];
}
-(void)timerLoad
{
if (!self.webV.loading)
{
[self.indicator stopAnimating];
}
else
{
[self.indicator startAnimating];
}
}
But when the UIWebView loads, no activity indicator shows up. What am I doing wrong or leaving out?
Thanks for the help, folks.
I'm really not sure on what the behaviour of the UIActivityIndicatorView is supposed to be if you repeatedly call start/stop on it. I am reasonably sure it isn't meant to be used that way :)
So, even though your question is specific to NSTimer and UIActivityIndicatorView, it may be helpful to understand that you should approach your solution differently.
Instead of using a timer that repeatedly calls [self.indicator startAnimating] every half-second, you should use the webview delegate methods to toggle the UIActivityIndicatorView on and off.
-(void)viewDidLoad
{
[super viewDidLoad];
//Create UIWebView.
if (!self.webV)
{
self.webV = [[UIWebView alloc] init];
}
self.webV.delegate = self;
//Load web page.
NSString *baseURLString = #"myURL.com";
NSString *urlString = [baseURLString stringByAppendingPathComponent:#"myURL.com"];
//self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0/2.0 target:self selector:#selector(timerLoad) userInfo:nil repeats:YES];
[self connectWithURL:urlString andBaseURLString:baseURLString];
}
- (void)webViewDidStartLoad:(UIWebView *)webView{
//start animating
[self.indicator startAnimating];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView{
//stop animating
[self.indicator stopAnimating];
}
There could be several reasons. It could load so fast that the loading is already done, or it never loaded at all because something is wrong with the URL.
This isn't the cause of your problem, but you never invalidate your timer, which you should.
I was also going to make the point that you should use delegate methods instead of a timer, but pdriegen beat me to it.