I'm trying to display a view because the method takes a good few seconds to complete. However, the loading UIWindow doesn't actually become key and visible until the whole method is complete. Any idea what's going on here?
// this line creates a uiwindow with view controller as root view controller, an activity indicator, and a label and makes the loading view key and visible
[XSELLoadingView presentLoadingViewWithTitle:#"Generating Report"]; // use loading view for long times
// these three lines process a lot of data and present the view on the main window
XSELCount *startCount = [[XSELCount counts] objectAtIndex:[self.startingPick selectedRowInComponent:0]];
XSELCount *endCount = [[XSELCount counts] objectAtIndex:[self.endingPick selectedRowInComponent:0
[self.navigationController pushViewController:[XSELReportView viewWithReportData:[XSELReportData compareStartCount:startCount toEndCount:endCount]] animated:false];
// this line makes the main view key and visable
[XSELLoadingView dismissLoadingView];
Apply your task inside this one.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Do background work
dispatch_async(dispatch_get_main_queue(), ^{
//Update UI
});
});
Related
I am writing an ios app, that has multiple UIViewcontrollers. They all have UITableViews that are filled with data, that is acquired from different API's. But the problem that I am facing is that, when I tap on a cell, the the app won't navigate to the next page, until the data for that page is acquired. This make the app look, mighty slow. I need some way to navigate to next page, where I can put some spinner animation to let the user know that it is acquiring data(atleast). I don't want the user to think that the app has crashed, or something(it stays in the same page for solid 7-10 seconds)
Thanks in advance
you should call all the api in the background so it wont effect the main thread, use the queue for call api in background queue like below.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(queue, ^{
//call apis here
});
My solution to this problem is,You can call the API using dispatch queue,So that it will not affect other functionalities.
Please follow these simple steps
ViewController1 = VC1
ViewController1 = VC2
in VC1
[self.navigationController pushViewController:VC2 animated:YES];
in VC2
#implementation {
BOOL hasData;
}
-(void)viewDidLoad {
hasData = NO;
[self getAndShowData];
}
-(void)getAndShowData {
// Start Showing Spinner
// load your data from server and
// on success
1.) hasData = YES;
2.) Call reload tableview
// Remove spinner
}
- (NSInteger)numberOfRowsInSection:(NSInteger)section {
if(hasData == NO) return 0;
return actual number of rows.
}
}
I loaded a UIView from a UIViewController. This UIView contains a (big) UICollectionView.
The transition from the first UIView to the second UIView is very slow: It seems that when the rendering of all collection's cells is done the second view can show up.
In the second UIView, I tried.
- (void)viewDidAppear:(BOOL)animated
{
[activityView stopAnimating];
NSLog(#"did appear %#",[NSDate date]);
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[activityView startAnimating];
NSLog(#"will appear %#",[NSDate date]);
}
In the NSLog, there is no time difference between the two events, and in fact the second UIView shows up in about 1 second after the event viewDidAppear.
At this point, I would start a UIActivityIndicator, as in the code. But the indicator is never shown.
Any hint?
Your problem here is that you're probably blocking the main thread by maybe doing some disk IO or network activity or heavy computations, and that is why you're experiencing this delay.
I'd recommend that you do all this on a secondary thread while showing a UIActivityIndicator. On the completion you can then hide the activity indicator and show the collection view.
EDIT:
N.B. There is probably a better way to go, but i'm not very familiar with collection views.
A really easy fix would be to keep a BOOL ivar in the view controller where you load the collection view. Call it shouldLoadData and set it to NO in your viewDidLoad method. Then all you need to do is to return 0 to your UICollectionViewDelegate methods numberOfSectionsInCollectionView: and collectionView:numberOfItemsInSection:.
Finally in your viewDidAppear method, you set shouldLoadData to YES and call reloadData on your collectionView. The tricky part at this point is to figure out a way to tell when the collection view finished reloading its data so that you can stop the activity indicator.
I found out that it is not even that tricky, reloadData just queues up on the main thread, so you can just queue another task on the main thread after you make the call to reloadData. Just do:
[self.collectionView reloadData];
dispatch_async(dispatch_get_main_queue(), ^{
[self.activity stopAnimating];
});
And you'll get the desired behaviour. You should be aware, however, that this would still block the main thread.
E.g. if you have a back button, it could not be pressed until the data is fully loaded (it could actually be pressed, but it would not have any visible effect until then).
I have an PlayPageViewController as rootViewController. The PlayPageViewController will display 3D models and UIImages in an method call editPages(), which will takes several seconds to wait.
I just want to add an loadingView at start and when PlayPageViewController gets fully loaded it will disappear.
Here is my solution:
Add an loadingView with activityIndicator.
When the loadingView is loaded, I will begin to implement
but seems it didn't work
STLoadingViewController *loadingView =
[[STLoadingViewController alloc]initWithNibName:#"STLoadingViewController"
bundle:nil];
loadingView.view.frame=CGRectMake(0, 0, 1024, 768);
[self.view insertSubview:loadingView.view atIndex:3];
if(loadingView.isViewLoaded && loadingView.view.window)
{
[self.view insertSubview:self.playPageViewController.view atIndex:4];
[self.playPageViewController setEditingPage:pageIndex];
[loadingView.view removeFromSuperview];
}
You have to do your respective methods to call in viewDidAppear method when this method is called all the appearing task had been finished.
What is the ViewLoad() method? Do you mean viewDidLoad:? You could setup all your views in the storyboard, including a view containing the activity indicator. Don't load the model at this point but wait until viewDidLoad: is called. At this point you may use Grand Central Dispatch to start the loading of the model. It could look a bit like this:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// You need to setup the activity indicator outlet in the storyboard
[_activityIndicator startAnimating];
}
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// Do the expensive work on the background
[NSThread sleepForTimeInterval:5.0f];
// All UI related operations must be performed on the main thread!
dispatch_async(dispatch_get_main_queue(), ^{\
// Replace the view containing the activity indicator with the view of your model.
[_activityIndicator stopAnimating];
NSLog(#"STOP");
});
});
}
Edit: The link seems to be down. This is probably due to the current problems with the Dev Center. You can find the documentation in the documentation pane of the Xcode Organizer.
I have a PhotoViewController class with an #property UIActivityIndicatorView* spinner. FlickrPhotoViewController is a subclass of PhotoViewController that downloads a photo from Flickr and tells the spinner when to start and stop animating. 'updatePhoto' is called every time the view controller is given a Flickr photo:
- (void)updatePhoto { // Download photo and set it
NSLog("updatePhoto called");
if (self.spinner) NSLog(#"Spinner exists in updatePhoto");
dispatch_queue_t downloadQueue = dispatch_queue_create("downloader",
NULL);
[self.spinner startAnimating];
dispatch_async(downloadQueue, ^{
// Download the photo
dispatch_async(dispatch_get_main_queue(), ^{
[self.spinner stopAnimating];
// Set the photo in the UI
}
});
});
}
The above methodology is exactly what I use for displaying a spinning wheel in my table view controllers while the table contents download, and it always works there.
You will notice at the beginning of updatePhoto I print a message if the UIActivityIndicatorView exists. I put a similar statement in awakeFromNib, viewDidLoad, and viewWillAppear. When I run it, this is the exact output I get:
2013-01-31 21:30:55.211 FlickrExplorer[1878:c07] updatePhoto called
2013-01-31 21:30:55.222 FlickrExplorer[1878:c07] Spinner exists in viewDidLoad
2013-01-31 21:30:55.223 FlickrExplorer[1878:c07] Spinner exists in viewWillAppear
Why does spinner not exist in awakeFromNib? Docs indicate that "When an object receives an awakeFromNib message, it is guaranteed to have all its outlet and action connections already established." Can an IBOutlet be connected without the existence of the object it is connecting to? In this case, can the spinner IBOutlet be connected to the storyboard without spinner being allocated?
Moving beyond this, I overrode the getter for spinner so that it would instantiate if it does not exist. As a result, the printing output now looks like this:
2013-01-31 21:48:45.646 FlickrExplorer[2222:c07] Spinner exists in awakeFromNib
2013-01-31 21:48:45.647 FlickrExplorer[2222:c07] updatePhoto called
2013-01-31 21:48:45.647 FlickrExplorer[2222:c07] Spinner exists in updatePhoto
2013-01-31 21:48:45.649 FlickrExplorer[2222:c07] Spinner exists in viewDidLoad
2013-01-31 21:48:45.650 FlickrExplorer[2222:c07] Spinner exists in viewWillAppear
This is what I would have expected to see earlier. Nevertheless, I still do not get any animation.
Possible problems that I have ruled out:
The spinner is the same color as the background, making it invisible. In my project, the background is black and the spinner is white.
I am attempting to animate the UIActivityIndicatorView while some expensive method is blocking the main thread. In my project, all my file system I/O and downloading methods are called in a non-main dispatch_async queue.
The spinner's IBOutlet is not hooked up. In my project, I have double checked this numerous times. It can be seen from both the storyboard and the PhotoViewController.h file that it is connected.
All three of these possibilities are ruled out by the fact that putting [self.spinner startAnimating]; in viewWillAppear makes it successfully animate throughout the download process.
You can download this project if you like. Just go to any screen that attempts to display a large photo and you will see that the spinner does not appear. There are many problems with this project, but this is the one I am focusing on now.
Edit 1:
I added the project's missing dependencies on Git, so the project will
now compile for you
Edit 2 (2 February 2013):
I am seeing this problem only on the iPhone when the updatePhoto method is called due to another view controller's prepareForSegue setting the photo in the FlickrPhotoViewController. Is it possible that this contributes to the problem?
Try to fire start animating uiactivityindictor before u call updatePoto method like this
Tru calling this iinstead of calling UpdatePhoto just all :
[self startAnimatingAndThenUpdatePhoto];
-(void)startAnimatingAndThenUpdatePhoto{
if (self.spinner) NSLog(#"Spinner exists in updatePhoto");
[self.spinner startAnimating];
[self performSelector:#selector(updatePhoto) withObject:nil afterDelay:0.01];
}
- (void)updatePhoto { // Download photo and set it
NSLog("updatePhoto called");
dispatch_queue_t downloadQueue = dispatch_queue_create("downloader",
NULL);
dispatch_async(downloadQueue, ^{
// Download the photo
dispatch_async(dispatch_get_main_queue(), ^{
[self.spinner stopAnimating];
// Set the photo in the UI
}
});
});
}
Not the best method in the world but it will do thejob
I have a button on the currently navigated to viewcontroller, connected to an IBAction.
In the IBAction I create a UIActivityIndicatorView as usual, with [self.view addSubView], then load some pictures.
I've tried setNeedsDisplay on the indicator view, the view controller, and the window, but it still loads the pictures before showing the indicator, which of course is quite useless to me.
So I'm looking for a way to either force an instant redraw (which when I think a little more about it is unlikely to make work), or a way to load the pictures after the indicator has appeared, or a way to launch a separate thread or similar to start animating / show the indicator, or put the indicator in a separate viewcontroller and somehow force it to add/show itself before going on to the picture-loading.
Recommendations?
What I do in this situation is spawn a new thread, which frees up the main thread to handle UI interaction while stuff is loading in the background.
First show the UIActivityIndicatorView, then spawn a new thread that loads the images, then on the last line of the method that is executed in the new thread, hide the UIActivityIndicatorView.
Here's an example:
//do stuff...
[activityIndicatorView startAnimating];
[NSThread detachNewThreadSelector:#selector(loadImages) toTarget:self withObject:nil];
In your loadImages method:
- (void) loadImages {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//load images...
[activityIndicatorView performSelectorOnMainThread:#selector(stopAnimating)];
[pool drain];
}