I have a tab bar application with three tabs. In my second tab view i have a UIProgressView. I have a NSTimer in my viewDidLoad method which calls a method which updates the progressView
progressTimer = [NSTimer scheduledTimerWithTimeInterval:1.5 target:self selector:#selector(updateProgress) userInfo:nil repeats:YES];
All is fine so far.
I want to know how do i run the timer in the background throughout the lifecycle of the app, so that progressView is updated no matter which view the user is in. It will be great if you can show me with a code snippet.
Thanks in advance.
It's not advisable to update UI elements from background threads. Also, it's not advisable to modify UI elements of a view when the user is not in that view. You are using precious system resources.
Better way is to update the ProgressBar as soon as that view becomes active...
UPDATE: You can see here to know how to run a task in background thread. You could set up your NSTimer to start in the selector you specify there. But again, this kind of stuff could lead to some weird bugs. Better to avoid..
You could keep a property in the ProgressViewController (or even in the app delegate), say a float that tracks the progress. When you need to update the progress, another ViewController can change the value of the property.
Once the view becomes visible, update the UI (UIProgressView) from viewWillAppear:.
u can add the method in background this way
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Add code here to do background processing
// increment the progress here and keep the property of that count in appdelegate
//
dispatch_async( dispatch_get_main_queue(), ^{
// Add code here to update the UI/send notifications based on the
// results of the background processing
});
});
in diffent view's viewWillAppear u can set this way:
[loadingProgressView setProgress: appDelegate.count];
Related
I know how to animate,display the Activity Indicator.
But I want to know how to immediately show the Activity Indicator.
Now When I am click the button it will load another view after 5 or many seconds. Clicking that button is loading a subview. When that Button Click method is called, it will call more than 7 methods after that and then it will load the subview. But the ActivityIndicator is displayed only after it complete executing all the 7 methods.
What I am trying to do is , I want to display the ActivityIndicator immediately after that Button Click method.
Any Idea ?
-(IBAction)button_click{
..
..
..
[self performSelector:#selector(afterDelay) withObject:nil afterDelay:0.3];
}
-(void)afterDelay{
YOUR_Code
}
The problem is that you perform your massive calculations (or loading from the internet?) on the main thread, but all the view mutation operations are not called immediately — on the start of the runloop the framework creates a new implicit Core Animation transaction, then it collects all the information about views' mutations, then commits the transaction on the end of the runloop. By blocking the main thread you are not allowing the transaction to commit and start your indicator's animation.
You can read about this architecture in the documentation: Core Animation Programming Guide: Transactions.
There are three options:
(Preferred) Perform your operations on the background.
Link your binary against QuartzCore.framework, then #import <QuartzCore/QuartzCore.h> and call [CATransaction commit] after your startAnimating call. This way you commit the implicit transaction.
Create your own explicit CATransaction.
If you have put the code to display or unhide the activityIndicator but it is not shown you have to use performSelector:withObject:afterDelay: method for doing any thing after unhide or displaying the activityIndicator with the delay of 0.001 will show the indicator immediately after clicking the button
Happy Coding :)
If your are talking about the system activity indicator, use this :
[[UIApplication sharedApplication] performSelector:#selector(setNetworkActivityIndicatorVisible:) withObject:[NSNumber numberWithBool:YES ] afterDelay:0.0];
Is this code using UIActivityIndicatorView flawed? It appears that I don't actually get to see the indicator/spinner at all here, so is this because the view isn't drawn until the who viewDidLoad completes?
Is the only way around this to do the viewDidLoad custom work (e.g. data updates) on a separate thread? (I was hoping in this case for an easier single-thread operation). Is there a way to force the view to refresh after the "startAnimating" line perhaps prior to the data loading commencment?
Code from UITableViewController implementation:
- (void)viewDidLoad {
// Wait indicator - Start
self.waitView = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge] autorelease];
self.waitView.hidesWhenStopped = true;
[self.view addSubview: self.waitView];
// Load data into tableview
[NSThread sleepForTimeInterval: 5.0]; // Test code to simulate
[self.waitView stopAnimating];
}
You should also call startAnimating. Sleeping is not a good idea. I would prefer the performSelector-methods which starts a not recurring NSTimer under the hood.
Try this:
-(void) doStuff:(id)aSender
{
[self.waitView stopAnimating];
}
-(void)viewDidLoad
{
...
[self performSelector:#selector(doStuff:) withObject:self afterDelay:5.0];
}
in addtion: also set the frame- or bounds-property of the ActivityIndicatorView somewhere like sosborn said in his comment
Actually the answer from Thomas should work as it is, I will add a little explanation as to why not use sleep as you have done it.
All the UI processing on iPhone (and most of OSs as well) is being done in only one thread - the main thread, the thread that executes the so called run loop. If you stop that thread the UI will stop, nothing will be drawn.
Putting sleep into viewDidLoad, which runs in the main thread, will do just that - stop UI from doing anything. So because immediately after wakeup you've called [self.waitView stopAnimating] and the activityview should hide when not animating, you can't see it at all - you just didn't give it any time to show.
Thomas used a NSTimer to call stopAnimating after 5 seconds - now this lets the main thread to execute code before stopping animation and hiding waitView and this will work for your test.
Better yet you just let it animate without any timer and use a delegate patter to be informed by the tableView loading code after the data has been loaded, then stop animating. You don't know how long loading of data will last, so it's better to wait until it's finished than stop animating after any specific time.
Oh well, and the size and position, makes sense, but for testing it doesn't matter and is not the cause of not seeing it - if not specified it will be added at 0,0 and have a default size so you will see it anyway.
I am currently working on a project where I request and parse multiple html sites in a controller. To give some feedback to the user I have created a second view, which gets displayed during processing of the data. It displays a status label and a progressbar. In my controller I have several points where I update my labels text. Unfortunately this works only sometimes. I guess thats because the label gets redrawn only once in a while and I try to do it probably more often than once a second.
It does not work to force a redraw with [label setNeedsDisplay];
I also made a thread which updates the labels text with the text of a global variable and also calls setNeedsDisplay on the label every second. But the results in the same. I only see certain changes, but not all.
Everything is setup properly and the label never is nil. When I log my updateMethod everything seems allright. It just does not get displayed!
Cheers
Here is the code of my threads
- (void)startUpdateStatusThread{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self performSelectorOnMainThread:#selector(updateFrequently) withObject:nil waitUntilDone:NO];
[pool release];
}
- (void)updateFrequently{
NSLog(#"updateFrequently: %#", currentStatus);
test++;
[self.statusLabel setText:[NSString stringWithFormat:#"%# - %i", currentStatus, test]];
[self.statusLabel setNeedsDisplay];
[NSTimer scheduledTimerWithTimeInterval:0.0001 target:self selector:#selector(updateFrequently) userInfo:nil repeats:NO];
}
Am I right if I assume that you call your label's text-property as well as setNeedsDisplay from within your "Thread" that is parsing your websites?
Note that: changes to UIViews (or subclasses of UIView like your label) must be performed on the main thread.
What I recommend you to do is write a helper method that updates your label and calls setNeedDisplay, the from within your Parser-Thread call
[mainViewController performSelectorOnMainThread:#selector(yourUpdateLabelHelper:) withObject:nil waitUntilDone:NO];
that should get your job done.
You mention threads. Be aware that UIKit controls, such as UILabel, can only be updated from the main thread. Make sure you are only attempting to set the label's text from the main thread and then verify if you are still having issues.
EDIT due to question edit:
First, -setNeedsDisplay only tells the view it needs to redisplay the next time the screen is refreshed, it does not force a display at that time.
Second, the iPhone screen draws at about 60 hz. This means there is an update every 0.016666 or so seconds. You're trying to update much faster than that, so only about every 160th of your updates will actually be displayed.
Third, and probably most important, you don't seem to be making any threads, unless you call -startUpdateStatusThread from a background thread. Either way, once you hit -updateFrequently everything is back on the main thread. With the frequency you are scheduling that timer, you are probably overloading the CPU and making it so that the system does not have time to draw the label. So even though you are setting new text, you aren't giving it a chance to render. You need to reduce the frequency of that timer, and you probably need to rethink whatever it is you're trying to do.
I think that the creation of the timer should be in separate function:
Allow the repetition and also store the timer in a member variable you can close on dealloc.
[NSTimer scheduledTimerWithTimeInterval:0.0001 target:self selector:#selector(updateFrequently) userInfo:nil repeats:YES];
}
The update function should operate as a callback function.
Regards
Assayag
I'm making a simple iPhone app whose sole function is to update a UIView forever (until it exits).
I tried this in applicationDidFinishLaunching and viewDidLoad:
while(1) {
// update view here
}
but that doesn't work- the app never finishes loading. I'm sure there's a simple solution to this, I just don't know what it its.
Also: ideally, this process should consume very little resources.
You can't have a while (1) statement like there, as it not allow viewDidLoad to return, and your app will never get any other calls such as tap processing, screen draw updates, etc.
In viewDidLoad, set up a timer task using:
updateTimer = [NSTimer scheduledTimerWithTimeInterval: kUpdateTimeInterval target:self selector:#selector(updateView) userInfo:nil repeats:YES];
And have a method called updateView that actually does the updating. Instead of updateView you could also use setNeedsDisplay which will trigger a call the -drawRect method of your view class, and do the actual drawing there.
What ends up happening now is that your viewDidLoad will set up a repeating task and at every kUpdateInterval, your view will be updated.
Instead of updating the view all the time, maybe you could just call its -setNeedsDisplay method when the data that you're displaying changes.
How do I create a loading screen that can be reused at any given time. I'm aware of the Default.png but I need the flexibility to plug in a loading screen at any point during the application life cycle.
This is what I have thus far.
//inside a method that gets called by a UIButton
LoadingViewController* loadController = [[LoadingViewController alloc] initWithNibName:#"Loading" bundle:nil vertical:NO];
[self.view addSubview: loadController.view];
//some method call that takes a few seconds to execute
[self doSomething];
//This loads some other view, my final view
[self.view addSubview: someOtherView]
but it seems that the loading view is never displayed. Instead the previous view stays there until the "someOtherView" gets added. I put trace logs and the code does seem to get executed, I even replaced [self doSomething] with a sleep(2), but the intermediate loading view is never displayed.
If I remove [self.view addSubview:someOtherView]; then after a few seconds...(after doSomething finishes executing) the load view is displayed since there is no view that is pushed on top of it, however this is obviously not the functionality I want.
Can explain this behavior? Is there something about the rendering cycle that I am misunderstanding because it doesn't seem like the view (on the screen at least) is instantly updated, even though I call a [self.view addSubview: loadController.view];
Would I need to create a separate thread?
In general, for changes in the UI to be made visible to the user, control must return to the main runLoop. You are only returning to the runLoop after taking the loading view down and replacing it with the other view. One strategy for dealing with this is to move the code that does the loading onto another thread. NSOperation and NSOperationQueue can be used for this.
An even simpler approach is to use performSelectorInBackground:withObject to do the processing. Once processing is complete the UI can be updated again to show the data. It is important to remember that the UI updates must be carried out on the main thread. Use performSelectorOnMainThread:withObject:waitUntilDone: to accomplish this from the loading thread.
This sounds like a lot of complication but it is really as simple as breaking your single method up into three separate methods as follows:
Display the loading view and start the background process - this is the button action method.
Do the background loading - called from the button action function with performSelectorInBackground:withObject.
Remove the loading view and update the display with the data - called from the background thread with performSelectorOnMainThread:withObject:waitUntilDone.
I created a subclass of UIView where I initialized how my loading-view should work and look like. (My view appeared and slided in from the bottom with an nice animation).
I then added code that handled whether the loading-view should be visible or not in a subclass of UIViewController.
I then let all my viewcontrollers be an subclass of my new viewcontrollerclass which made it possible for me to do:
[self showloadingMessage:#"loading..."];
in all my viewcontrollers...