RESOLVED
I was performing the segue from inside a block and it was another thread. When I specifically move the operation back to the main thread it works great.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self performSegueWithIdentifier:#"LoadToDisplay" sender:self];
}];
QUESTION
I want to programmatically present a view controller.
My app downloads some data from the internet, carries out some data handling and then loads the next view.
I use performSegueWithIdentifer successfully throughout the rest of the app however for some reason it is adding a huge delay with this specific transition.
I've used presentViewController with no delay but I can't use this, it was just to test whether I was missing something obvious.
I have an NSLog when the last data handling method completes and one when the next view controller is loaded. Using 'presentViewController' the time between logs is 14ms. When using performSegueWithIdentifer is a staggering 8.5secs!
I literally commented out one line and tested with the other. No other code changes.
Has anyone else experienced this or know what might be going on?
Thanks.
Related
I have AViewController and want to push BViewController on to the navigation stack but BViewController has a lot of UI, which takes two seconds to render.
Is it possible to pre-render the UI of BViewController before it is pushed so that it will appear immediately?
No UI should require 2 seconds to be pushed, ever, so you indeed have a problem.
You have different solutions :
Push the controller without the data loaded, and start loading straight away, while showing a spinner. For example, if you have a tableview, it will be empty, and a spinner will spin in the middle of the screen with a little label saying "Loading...". When it's done, you populate the tableview and display it.
This way, your user knows what is going on and he can decide to go back if he wants to, or just wait the 2 seconds.
If it's really mandatory that the data is loaded as the user pushes, you can load it before he pushes the controller. But this really depends what kind of data it is. If it is detail view of an element, you can't load all the elements just in case. If it's a view that is always the same, you could load it when AViewController has finished loading, which will give you the time required to load because the user will probably stay a second or two before pushing BViewController. This method is probably not recommended. You could achieve that roughly like this :
BViewController *vc = [[BViewController alloc]init];
// [vc viewDidLoad]; I'm not sure this is necessary
But seriously don't go for option 2, it's most probably bad to preload the UI in many many cases. Preloading data is common, but UI, I don't think so.
You could reduce the amount of data you want to load. Make sure your images are compressed enough (on a small screen you don't need 4K images), make sure you're not loading a complete database when you really want a single object, etc. You should only download what the user is gonna see, not 'everything'. Again, if you've done that properly it should be already good.
I strongly suggest you try for option 3strong text, make sure everything is small and exactly what you need. If yes, then go for option 1.
Don't forget : the user knows what he wants, you don't. If he does not want to wait two seconds, he should be able to go back while it's spinning. He should NEVER be forced to wait two seconds, and your app should never be hanging.
EDIT : You said you need to load "A lot of UI". Could you show us/tell us exactly what UI takes two seconds to load? I've had pretty complex views (and I mean really complex), and UI never took that long to load.
Do your loading stuff in bViewController within background thread.
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
//Background Thread
// Do your loading stuff here
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run your UI Updates
});
});
}
This is how my application is looking now:
After I perform a database update in my detail controller in view number 7 in the image above as soon as the save button is clicked the details are saved the the database. I'm taken back to tableView number 5 and expect the associated row to show latest updates by calling a special method from the parse.com framework that reloads objects and refreshes the table view e.g. [self loadObjects].
I use an unwind segue. In view 7 I make a connection between the save button and the exit symbol of it's controller window in interface builder and then in tableView number 5 I have my segue method that corresponds to this connect.
Unwind segue method:
-(IBAction)saveDetailsButtonTapped:(UIStoryboardSegue *)segue {
// alert goes here
[self performSelector:#selector(didTapRefreshButton:) withObject:self afterDelay:1.0];
}
This method clears the table and loads the first page of objects:
- (IBAction)didTapRefreshButton:(id)sender {
[self loadObjects];
}
When save is clicked on view number 7 the details are saved to the db and user is bought back to table view number 5 then the method above runs after 1.0 delay. I thought this was ok but didn't feel too right. I tried it on my phone and sometimes the delay wasn't long enough, meaning a failed refresh.
I then decided to try using a UIAlertView delegate method to detect when the ok button of the alertview was pressed and it worked ok most times but then the times I pressed OK to dismiss the alert really quickly upon arriving back on the view and the data wasn't reloaded.
Is there a better solid reliable way to refresh my data?
I need some way of knowing that the database update was successful and only then run the [self loadObjects] method and maybe do that automatically.
I have two methods that detect when objects will load (e.g. like when a button has been tapped) and when they have loaded. I have put some spinner code in there to show a spinner while loading is happening and take it away once it's done.
Isn't there some sort of way to queue methods, like some how in one method make it so one thing doesn't happen until another thing has happened?
If so, I'd really appreciate some insight and examples as I could just mark the app as complete but even though I'm not being paid and it's charity work I still have the urge to do my best.
Thanks for your time.
Kind regards
I have put some spinner code in there to show a spinner while loading is happening and take it away once it's done.
You should do something like that here.
I need some way of knowing that the database update was successful and only then run the [self loadObjects] method and maybe do that automatically.
Because you're saving to parse, it should be the parse SDK that tells you when the save is complete. If you're saving in the background (which you should be) then use the save method when provides you with a callback block that is called when the save has completed. This block being called is your trigger to remove the spinner and segue.
Side note :-
Yes, there are several different kinds of queues, most better than using performSelector:..., but there are also other ways of working with asynchronous activities and you should look at the asynchronous activity for guidance. i.e. can I get a callback when this is done, rather than how long should I wait and hope that it is done.
I have a seemingly simple problem that I cannot for the life of me seem to figure out. In my iOS App, I have a UICollectionView that triggers network operation upon tapping it that can take a few seconds to complete. While the information is being downloaded, I want to display a UIView that fills the cell with a UIActivityIndicatorView that sits in the square until the loading is done, and the segue triggered. The problem is that it never appears. Right now my code looks like:
myLoadView.hidden = NO;
//Network Operation
myLoadView.hidden = YES;
The App simply stops for a couple seconds, and then moves on the the next view. I'd imagine Grand Central Dispatch has somthing to do with the solution, however please keep in mind that this code takes place in prepareForSegue, and the network info needs to be passed to the next View. For this reason not finishing the download before switching scenes has an obvious problem. Any help would be VASTLY appreciated. Thanks!
iOS commits changes in the interfaces after working out a routine. Hence you should perform your network operation in a background thread and then get back back on the main and perform the "show my view now thing". Have a look the below code for reference.
myLoadView.hidden = NO;
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
//Network Operation
dispatch_async(dispatch_get_main_queue(), ^{
myLoadView.hidden = YES;
});
});
Your network operation seems to be carried out on the main thread, aka UI thread. This blocks all further UI calls, including the call to unhide a view, until completion.
To resolve this, make your call asynchronous.
You should read this in full, if you haven't already.
As mentioned by other answers, the problem is that the UIView change doesn't happen until the current method finishes running, which is where you are blocking. Before GCD was available I would split methods in two and use performSelector:withObject:afterDelay (to run the second part also on the UI loop) or performSelectorInBackground:withObject: at the end of the first method. This would commit all the waiting animaations first, then do the actual tasks in the second method.
Well the better option for this type of indication is by using the custom HUD libraries like SVProgressHUD or MBProgressHUD
My iOS app has a welcome screen (not to be confused with the default view). The view controller downloads and parses an XML file using NSXMLParser. When it completes it's task it makes a button visible, which when clicked calls "presentViewController" which takes the user into the actual app. This worked fine.
I then decided that I would just like the the app to automatically transition, and so I removed the button altogether and moved the call to presentViewController into the "parserDidEndDocument" delegate method. The method gets called but nothing happens. I suspect it has something to do with the context, but when I log "self" it prints an instance of the welcome view controller. What am I doing wrong? How should I fix this?
Try dispatching it to the main thread. Async objects like NSXmlParser work on separate threads, but UIKit updates must be done on the main thread.
dispatch_async(dispatch_get_main_queue(), ^{
[self presentViewController]; //Or whatever
});
I want to start a wait animation/show UIAlertView before calling a segue so that the user will know they have to wait for a couple of seconds while the segue gets processed and the next ViewController gets populated with relevant data and shows up!
The problem is performSegueWithIdentifier prevents any kind of animation/UIAlertView to show up. Only after the segue finishes then the animations get their chance to popup!
How to solve this ?
You should be able to implement your animation logic in the prepareForSegue method. That method gets called and finished before the segue is executed.
Also, if this flow doesn't fit, fire off the performSegue method when the animation is over or the user responds to the UIAlertView that you have fired off.
OR if you haven't been using MBProgressHUD, take a look at that. I use this when I am dealing with external data and want the user to know something is going on (getting data from a web-service). It is easy and simple to implement.