I'm very new to IOS programming and i have doubt with NSThread.
My problem is, i have a UILabel in my view and i want to hide and make it visible Successively after every 5 second.
For this purpose i've used NSThread as below.
[NSThread detachNewThreadSelector:#selector(animate) toTarget:self withObject:nil];
-(void) animate
{
while(animateLabel){
[NSThread sleepForTimeInterval:5];
if(label.hidden){
NSLog(#"Label is hidden");
[label setHidden:NO];
}else
{
NSLog(#"Label is vissible");
[label setHidden:YES];
}
}
}
Now i'm getting "Label is hidden" and "Label is vissible" Successively in log after every 5 seconds. But my label is not getting hide.
I did with NSTimer and it's working.
But, what is the problem with above code ?. If no problem with this code, Why NSThread couldn't do ?
You need to perform this on a main thread instead.
Try this -
[NSThread performSelectorOnMainThread:#selector(animate) toTarget:self withObject:nil];
since you have while loop
remove sleep and add runloop
[[NSRunLoop currentRunLoop] runUntilDate:(NSDate*)]
UI can be changed only by main thread. instead of creating new thread you can use selector
try this -
[self performSelectorOnMainThread:#selector(animate) withObject:nil afterDelay:5.0 ];
My Bad, didn't read that you purposely intended to do it with NSThread, here's how to do it with NSTimer anyway:
If you create NSTimer from the main thread you don't even need to deal with threading safety-issues.
declare an NSTimer in the Header file so you can reach it in case you want to cancel it or something (I'm also assuming your label is named 'mainLabel' and is declared properly):
NSTimer *labelVisibilityTimer;
#property (nonatomic,retain) NSTimer *labelVisibilityTimer;
in your implementation file, properly synthesize the Timer and initialize it with the method that will trigger the visibility change.
#synthesize labelVisibilityTimer;
- (void)viewDidLoad{
self.labelVisibilityTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(toggleVisibilityOfLabel) userInfo:nil repeats:YES];
}
-(void)toggleVisibilityOfLabel{
mainLabel.hidden = !mainLabel.hidden;
}
- (void)viewDidUnload{
[super viewDidUnload];
self.labelVisibilityTimer = nil;
}
- (void) dealloc{
[super dealloc];
[labelVisibilityTimer release];
}
Related
I have view controller with UIImageView . In view did load i want to set image on image view for particular time interval.After that image view should be cleared and application should switch to next screen. I have tried following code which is not working:
welcomeImage.image=[UIImage imageNamed:#"abc.png"];
sleep(5);
homeScreenController *controller=[[homeScreenController alloc]initWithNibName:#"homeScreenController" bundle:nil];
controller.modalTransitionStyle=UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:controller animated:YES];
In this case it is sleeping before view did load and it is not going to the next screen also. So what is wrong with the code?
Use the below code/..
- (void)viewDidLoad
{
[super viewDidLoad];
[self performSelector:#selector(goToNextView) withObject:nil afterDelay:5.0];
}
- (void)goToNextView
{
homeScreenController *controller=[[homeScreenController alloc]initWithNibName:#"homeScreenController" bundle:nil];
controller.modalTransitionStyle=UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:controller animated:YES];
}
The easiest solution is to fire a event after a time. I would recommend using performSelector.
- (void) hide {
//For example.
[self dismissModalViewController];
}
- (void) viewDidLoad {
//Setup my image.
[self performSelector:#selector(hide) withObject:nil afterDelay:3];
}
you can use NSTimer
something like this
- (void) viewDidLoad {
.......
NSTimer *timer = [[NSTimer scheduledTimerWithTimeInterval:(2.5)
target:self selector:#selector(hideImage)
userInfo:nil repeats:NO] retain];
....
}
- (void)hideImage
{
yourImage.hidden = YES;
}
Also, you made a sleep(5) inside the Main Thread, that's bad.
try something like
[self performSelectorOnBackground:#selector(hide) withObject:nil]
and do the thing you want (and the sleep(5)) inside the method -(void)hide like
-(void) hide {
sleep(5)
// ...
}
Good luck ;)
How to switch from view1 to view 2 without having any button pressed. In my views i have is uiimageview and uitextview
With the NSTimer i m trying to do this
in the viewdidload method by using the following code:
In the firstviewcontroller.h file
#interface FirstViewController : UIViewController
{
NSTimer *SwitchingTimer;
}
In the firstviewcontroller.m file
- (void)viewDidLoad
{
[super viewDidLoad];
SwitchingTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(SwitchView) userInfo:nil repeats:YES];
}
-(void)SwitchViews:(id)sender
In the secondviewcontroller.m file
-(void) SwitchView
{
SecondViewController *SecondView = [[SecondViewController alloc]
initWithNibName:#"SecondViewController" bundle:nil];
SecondView.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:SecondView animated:YES];
[SwitchingTimer invalidate];
self.SwitchingTimer = nil;
}
but nothing is happening. Can someone please tell me what i m missing in my code. Will appreciate help.
Thanks in advance.
There are a few issues in your code that are worth mentioning though I am not sure if those will provide you a solution.
Why do you want to repeat the timer every 2 seconds. I think you just want to switch to next view only once and if so then dont repeat the timer. So no need to invalidate the timer.
Your code for the SwitchView method is leaking memory. Please make sure that the SecondView is released after presenting the modal view(in case you are not using ARC).
Please follow the standard naming conventions. For eg: methods and variables should start with lowercase.
Regarding your issue please make sure that the nib name is correct and you are getting a valid object for the second view controller. You can check by using NSLog. Also ensure that the method Switchview is called. Try putting a break point and verify that it is called.
Another Option
If you just want to switch the view only once you can go for another option which does not make use of the NSTimer. For this, you can use performSelector:withObject:afterDelay:. This is just another option for the scenario I mentioned above.
You need to add it to the run loop:
- (void)viewDidLoad
{
[super viewDidLoad];
SwitchingTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(SwitchView) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer: SwitchingTimer forMode:NSDefaultRunLoopMode];
}
Also, you could rename -(void)SwitchView to -(void)switchViewTimerDidFire:(NSTimer *)timer. From the Documentation:
[...] The selector must have the following signature:
- (void)timerFireMethod:(NSTimer*)theTimer
The timer passes itself as the argument to this method.
I'm pretty sure this is really simple, and I'm just missing something obvious. I have an app that needs to download data from a web service for display in a UITableView, and I want to display a UIAlertView if the operation takes more than X seconds to complete. So this is what I've got (simplified for brevity):
MyViewController.h
#interface MyViewController : UIViewController
<UITableViewDelegate, UITableViewDataSource> {
NSTimer *timer;
}
#property (nonatomic, retain) NSTimer *timer;
MyViewController.m
#implementation MyViewController
#synthesize timer;
- (void)viewDidLoad {
timer = [NSTimer scheduledTimerWithTimeInterval:20
target:self
selector:#selector(initializationTimedOut:)
userInfo:nil
repeats:NO];
[self doSomethingThatTakesALongTime];
[timer invalidate];
}
- (void)doSomethingThatTakesALongTime {
sleep(30); // for testing only
// web service calls etc. go here
}
- (void)initializationTimedOut:(NSTimer *)theTimer {
// show the alert view
}
My problem is that I'm expecting the [self doSomethingThatTakesALongTime] call to block while the timer keeps counting, and I'm thinking that if it finishes before the timer is done counting down, it will return control of the thread to viewDidLoad where [timer invalidate] will proceed to cancel the timer. Obviously my understanding of how timers/threads work is flawed here because the way the code is written, the timer never goes off. However, if I remove the [timer invalidate], it does.
I think there is a problem with scheduling a timer and doing a blocking call on the same thread. Until the blocking call is completed, the run-loop cannot fire the timer.
I suggest you to detach a thread to perform the long operation. Once the long operation is finished, call back on the main thread to invalidate the timer.
Note: it is important to invalidate the timer on the same thread it was scheduled.
- (void)viewDidLoad {
timer = [NSTimer scheduledTimerWithTimeInterval:20
target:self
selector:#selector(initializationTimedOut:)
userInfo:nil
repeats:NO];
[NSThread detachNewThreadSelector:#selector(doSomethingThatTakesALongTime:) toTarget:self withObject:nil];
}
- (void)doSomethingThatTakesALongTime:(id)arg {
sleep(30); // for testing only
// web service calls etc. go here
[self performSelectorOnMainThread:#selector(invalidate) withObject:nil waitUntilDone:NO];
}
- (void)invalidate {
[timer invalidate];
}
- (void)initializationTimedOut:(NSTimer *)theTimer {
// show the alert view
}
Have you tried to use [NSThread sleepforTimeInterval:30]; ?
The sleep() occurs on the main thread and the associated run loop never has the chance to invoke the selector for the timer.
If you would do real work in -doSomething that doesn't block the thread, e.g. non-blocking calls to web-services, it would work as expected. Blocking calls however would have to be done in a different thread so the main run loop does not get blocked.
I'm having trouble with a simple threading example. I know my methods aren't complete, but as of now, I want to press the start button and have that fire off a thread that increments a counter every second. I know it's connected in IB correctly since the NSLog tells me it gets to my timerThread method. But then it immediately jumps back to the initial myThread, without ever reaching the updateDisplay method and releases the pool, which is why I am guessing my program doesn't actually increment a counter. I thought I would then put it in a sleep interval or something, but in the end I think I am missing the correct way to achieve this. Any thoughts would be great. Thanks!
#implementation MainController
-(id)initWithLabel:(UILabel *)label {
if (self = [super init]) {
countLabel = label;
[countLabel retain];
}
return self;
}
-(int)count {
return count;
}
-(void)setCount:(int) value {
count = value;
}
-(void)updateDisplay:(NSTimer *)timer {
NSLog(#"%s", __FUNCTION__);
countLabel.text = [NSString stringWithFormat:#"%i", count];
count++;
}
-(void)timerThread {
NSLog(#"%s", __FUNCTION__);
[NSTimer timerWithTimeInterval:1.0
target:self
selector:#selector(updateDisplay:)
userInfo:nil
repeats:YES];
//NSNumber *threadID = [NSNumber numberWithInt:(int)threadID];
// threadLabel = [NSString stringWithFormat:#"%#", threadID];
}
-(void)myThread {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//NSNumber *threadID = [NSNumber numberWithInt:(int)threadID];
[self performSelectorOnMainThread:#selector(timerThread)
withObject:nil
waitUntilDone:NO];
// NSLog(#"threadID in myThread: %#", threadID);
[pool release];
}
-(void)startThread {
// threadIndex = 0;
// numThreads = 0;
// NSNumber *threadID = [NSNumber numberWithInt:threadIndex++];
[self performSelectorInBackground:#selector(myThread) withObject:nil];
// NSLog(#"%i", threadIndex);
numThreads++;
}
-(void)myThreadStop {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[NSThread exit];
[self performSelectorOnMainThread:#selector(updateDisplay)
withObject:nil
waitUntilDone:NO];
[pool release];
}
-(void)stopThread {
[self performSelectorInBackground:#selector(myThreadStop) withObject:nil];
}
-(void) dealloc {
[countLabel release];
[super dealloc];
}
#end
The short answer is that you did not schedule the timer.
iOS (and others) use run loops. Each thread may have a run loop, and the main UI thread has a run loop set up for you. Simplistically, a run loop keeps a queue of things to do and either does them in an ordered fashion or blocks until there is something to do.
Anything that you do with the active UI, like setting the text of a UILabel, must be done on the main thread. In your case, you have set up (but not scheduled) a timer on the main thread to update the timer. Scheduling a timer just means adding it to the run loop.
If you had a lengthy task to perform that would update the UI at the end, you could use performSelectorInBackground to start the lengthy task and performSelectorOnMainThread when the task finishes to update UI.
If you have a short periodic task, such as updating a clock or counter UI, you can just create an NSTimer on the same thread you want the timer to fire on. When creating the timer, use a scheduledTimerWithTimeInterval variant so it will start firing automatically.
When you create a repeating timer, you must keep a reference to it so you can invalidate the timer before the target is released. At the latest, in dealloc you should invalidate the timer.
Instead of calling startThread, turn timerThread into startTimer.
-(void) startTimer {
timerMember = [[NSTimer
scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(updateDisplay:)
userInfo:nil
repeats:YES] retain];
}
-(void) dealloc {
[timerMember invalidate];
[timerMember release];
...
}
I have a tableView that loads an XML feed as follows:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if ([stories count] == 0) {
NSString *path = #"http://myurl.com/file.xml";
[self parseXMLFileAtURL:path];
}
}
I'd like to have the spinner to show on the top bar at the app launch and dissapear once the data is displayed on my tableView.
I thought that putting the beginning at viewDidAppear and the end at -(void)parserDidEndDocument:(NSXMLParser *)parser but it didn't work.
I'd appreciate a good explained solution on how to implement this solution.
Here's the problem: NSXMLParser is a synchronous API. That means that as soon as you call parse on your NSXMLParser, that thread is going to be totally stuck parsing xml, which means no UI updates.
Here's how I usually solve this:
- (void) startThingsUp {
//put the spinner onto the screen
//start the spinner animating
NSString *path = #"http://myurl.com/file.xml";
[self performSelectorInBackground:#selector(parseXMLFileAtURL:) withObject:path];
}
- (void) parseXMLFileAtURL:(NSString *)path {
//do stuff
[xmlParser parse];
[self performSelectorOnMainThread:#selector(doneParsing) withObject:nil waitUntilDone:NO];
}
- (void) doneParsing {
//stop the spinner
//remove it from the screen
}
I've used this method many times, and it works beautifully.
Starting a new thread can be overkilling and a source of complexity if you want to do things that are supposed to start on the main thread.
In my own code, I need to start a MailComposer by pushing a button but it can take some time to appear and I want to make sure the UIActivityIndicator is spinning meanwhile.
This is what I do :
-(void)submit_Clicked:(id)event
{
[self.spinner startAnimating];
[self performSelector:#selector(displayComposerSheet) withObject:nil afterDelay:0];
}
It will queue displayComposerSheet instead of executing it straight away. Enough for the spinner to start animating !
I typically implement an NSTimer that will call my spinner method, which I fire off right before I go into doing the heavy work (the work that will typically block the main thread).
The NSTimer fires and my spinner method is called. When the main work is finished, I disable the spinner.
Code for that is like:
IBOutlet UIActiviyIndicatorView *loginIndicator;
{
...
[loginIndicator startAnimating];
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(executeAuthenticationRequest)
userInfo:nil repeats:NO];
...
}
- (void) executeAuthenticationRequest
{
/* Simulate doing a network call. */
sleep(3);
[loginIndicator stopAnimating];
...
}
You can also do:
IBOutlet NSProgressIndicator *pIndicator;
Start:
[pIndicator startAnimation:self];
[pIndicator setHidden:NO];
And Stop:
[pIndicator stopAnimation:self];
[pIndicator setHidden:YES];
In Cocoa (and most other app frameworks) the user interface is updated by the main thread. When you manipulate views, they are typically not redrawn until control returns to the run loop and the screen is updated.
Because you are parsing the XML in the main thread, you are not allowing the screen to update, and that is why your activity indicator is not appearing.
You should be able to fix it by doing the following:
In viewDidAppear, show/animate the spinner and then call
[self performSelector:#selector(myXMLParsingMethod) withObject:nil afterDelay:0];
In myXMLParsingMethod, parse your XML, then hide/stop the spinner.
This way, control will return to the run loop before parsing begins, to allow the spinner to begin animating.