pushViewController Extremely slow - ios

What could cause pushViewController to be extremely slow? (it takes 30+seconds for the new view to appear)
Basically, I'm doing something like this:
SecondViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"correctID"];
vc.something = something;
[self.navigationController pushViewController:vc animated:YES];
CLS_LOG(#"Pushed Controller...");
and i'm logging at the beginning of viewdidload inside the second view controller.
I'm not subclassing other methods.
Between Pushed Controller... and the next log from viewdidload there's a huge delay.
How would you debug this?
I already tried with the TimeProfiler but apparently it shows nothing.

Had similar problem before, try the following
dispatch_async(dispatch_get_main_queue(), ^{
// your navigation controller action goes here
});

Dino's answer saved my life. He got my upvote. I'm just adding this for modern swift users.
DispatchQueue.main.async {
self.navigationController?.pushViewController(vc, animated: true)
}

Setting the background color e.g. vc.view.backgroundColor = .white solves the issue for me.

Related

Navigating to ViewController containing ScrollView Going too Slow

This is how am pushing from ViewController A to ViewController B
UIStoryboard *mainStory = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
ListingViewController *listView = [mainStory instantiateViewControllerWithIdentifier:#"listing"];
[self.navigationController pushViewController:listView animated:YES];
and i've tried this way as well using a segue in StoryBoard
[self performSegueWithIdentifier:#"Associate3" sender:sender];
Pushing to this ViewController for the first time freezes (around 4 sec) before start pushing, knowing that VC B contains UIScrollView Object in its XIB,
Did this happened to any of you, any one knows how to solve this delay?
EDIT:
i've already commented all webservice calling methods, nothing Changed! I think its an allocating delay, am using the storyboard initiating with identifier to push VC B, but when i used allocating method: VC *B = [[VC alloc] init], then pushing to this view works without delay, but the issue that i don't need to use the allocating method!!
Finally found the issue, it was the custom font that am using in the labels of the VC B, just set them back to System Font instead of Roboto solved my problem, i think the system takes time to find the Font with specific name. Hope this would be useful for others
It seems you are performing some heavy operation on main thread in ViewWillAppear or ViewDidLoad of ViewController B
Comment whole code of both above mentioned methods and then run your application which verifies problem in app.
So if its verified then you must move your heavy operation from main thread to background thread.
This might be because pushing is executed in different thread. Try pushing the viewController in main thread. i.e.,replace your push controller code with
dispatch_async(dispatch_get_main_queue(), ^{
UIStoryboard *mainStory = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
ListingViewController *listView = [mainStory instantiateViewControllerWithIdentifier:#"listing"];
[self.navigationController pushViewController:listView animated:YES];
});
Try to change in ListingViewController class viewDidLoad method to call async
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
//move your code here
// if you need to do something on ui (reload table view e.t.c) - call async in main thread
dispatch_async(dispatch_get_main_queue(), ^{
//Your UI code
});
});
}
You can use 'Instruments-Time Profiler', a developer tool with Xcode. It can check the time expense. Then, I think you will find you problem.

How do I present a ViewController over another with that VC having no knowledge of the new one?

I had a method to do this, but it stopped working at some point.
The motivation here is for debugging. I have a button that shows a debugging action sheet from whatever VC calls it. This works great. However, in the action sheet, after I select one, the action wanted is in some cases the presentation of a new VC. The first example of this was a VC that displays my internal log. It's very valuable when not debugging in a "tethered" mode.
Each debugging VC is represented as a scene in the Main storyboard. I instantiate the VC with instantiateViewControllerWithIdentifier:. Then I am trying to get it presented.
The tricky part is that the new VC has to be presented and then dismissed without writing any code in the VC that is currently active. Neither do I want to create a Segue from every VC where this might be called. The whole point is that the DebugActionSheet is self contained except for the single call to fire it up.
You should be able to access the top most view controller like this from your ActionSheet delegate method.
+ (UIViewController*) topMostController
{
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
while (topController.presentedViewController) {
topController = topController.presentedViewController;
}
return topController;
}
then in the calling code:
[MyDebugController.topMostController presentViewController:myLoggingView
animated:YES
completion:nil];
and your myLoggingView can dismiss itself by calling
[self.presentingViewController dismissViewControllerAnimated:YES
completion:nil]
Try presenting it on the main thread?
dispatch_async(dispatch_get_main_queue(), ^ {
[self presentViewController:vc animated:YES completion:nil];
});
It turned out my problem was that the current top controller is using a Navigation controller, so the required code is different.
UIStoryboard *story = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
_paletteVC = [story instantiateViewControllerWithIdentifier:#"PaletteDisplayVC"];
[[_delegate navigationController] pushViewController: _paletteVC
animated: YES];
I pass the current top controller to my DebugActionSheet as delegate, so I do not need the topMostController method above. However, I presume it would work with that also.

Return to initial view controller when user logs out

I'm working on an app which uses Facebook integration, and the log in system works fine now. However, I can't seem to return to my initial view controller when the user clicks log out.
Here's an overview of my storyboard:
I would like to return to the start when the user clicks the blue button (on the top). What would I do to achieve that? As you can see I have multiple Navigation Controllers, and I only use Push-seguesto get there. However, I do use the SWRevealViewController, which you can see in the middle.
I've tried [self.navigationController popToRootViewControllerAnimated:YES]; which doesn't do anything.
Any advice? Anyone familiar with the SWRevealViewController and what it might have done to my Navigation stack? Any help will be appreciated!
Thanks.
Try this,
UIStoryboard* storyboard = [UIStoryboard storyboardWithName:#"NameOfYourStoryBoard"
bundle:nil];
LoginViewController *add =
[storyboard instantiateViewControllerWithIdentifier:#"viewControllerIdentifier"];
[self presentViewController:add
animated:YES
completion:nil];
Write Below Method in root viewcontroller
- (IBAction)returnToDashboard:(UIStoryboardSegue *)segue;
Give segue connection to destination view controller like below
Give identifier to segue and assign method to that segue
use below method in destination view controller
[self performSegueWithIdentifier:#"pushtoDashboard" sender:self];
First of all, I don't think you need that many UINavigationControllers. Using only one in your application should be enough.
The reason popToRootViewController is not working in your case is because it will go to the first view controller withing a UINavigationController. You have nested many UINavigationControllers, thus when you click the blue button in the settings view controller it will go to the sidebar view controller (can't read it properly, the image is small).
You can do the following to get to the root view controller of your app:
UINavigationController *rootController =[(UINavigationController*)[(AppDelegate*)
[[UIApplication sharedApplication]delegate] window] rootViewController]];
Replace AppDelegate with however it's called in your app.
But my advice is to remove all intermediate UINavigationControllers. Then just doing popToRootViewController should do the trick.
Problem
You'd like to return from a view controller (source) to the start (a
destination view controller) when the user clicks the blue button (on the
top)
Recommendation
I recommend you take a quick look at the highly rated SO answer which demonstrates how to use the unwind segue (that's the little green exit box on your view controller in the storyboard). Unwind segues are the modern way of accomplishing your goal, but you can also call dismissViewController:animated from the source controller. You should also take a quick read of a very small Apple note (TN2298) on using the unwind segue.
Essentially you will want to add the following method to your destination view controller:
- (IBAction)unwindToMainMenu:(UIStoryboardSegue*)sender
{
}
Then use ctrl+drag and click from the blue button down to the green exit icon on the source view controller. This will popup a menu and you can select unwindToMainMenu from the list. You will need to give the new segue an identifier in the Identity Inspector (e.g. segueToMain).
Manual Unwind
The technical note above (TN2298) will also show you how you can create a manual unwind segues that may be called programmatically (similar to how one might say performSegueWithIdentifier...).
I was working on a very similar problem. I am using a storyboard with a navigation controller & implemented the highly recommended SWRevealViewController, with iOS 7 & Xcode 5.1. I tried unsuccessfully to implement some of the solutions mentions. Either the screen didn't change or I got blank table. I used a hybrid version of the programatic examples provided in SWRevealController & other answers here to get a working solution. I added this as apart of my login button action
UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
InboxTableViewController *viewController = [storyBoard instantiateViewControllerWithIdentifier:#"inbox"];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:viewController];
[self.revealViewController pushFrontViewController:nav animated:YES];
I retrieved my storyboard & initiated the view controller I wanted from the storyboard & then added to a navigation controller. Finally I used the SWRevealViewController method to Push the view I desired to the front.
I'm using swift and what worked for me was this:
var loginVC: UIViewController? = self.storyboard?.instantiateViewControllerWithIdentifier("UILogin") as? UIViewController
self.presentViewController(loginVC!, animated: true, completion: nil)
When you have different storyboards simply "presenting" the required VC from the initial storyboard does the trick:
Swift 3
if let loginVC = UIStoryboard(name: "Login", bundle: nil).instantiateInitialViewController() {
present(loginVC, animated: true, completion: nil)
}
In some cases there might be leaking UITransitionView's. You might remove them right after the "presenting" code, but not before it and not in it's completion:
if let subviews = UIApplication.shared.keyWindow?.subviews,
let transitionViewClass = NSClassFromString("UITransitionView") {
for subview in subviews where subview.isKind(of: transitionViewClass) {
subview.removeFromSuperview()
}
}
<– This works as of Xcode 8.2.1 and iOS 10.2 but no guarantee if will work forever. Be careful with it.

Navigation controller popViewControllerAnimated : yes is not working as expected

I am using following line of code:
[self.navigationController popViewControllerAnimated:YES];
But it is not behaving in ios 7 as it doing in ios 6.Some times it does not pop controller while we are pressing back button 2- 3 times in succession.
Resulting in abrupt behaviour in navigation bar and deallocating a controller but showing the same on ui .
So when we press anything on that controller it results to a crash since controller is already deallocated.
Check if you're running the code on the UI thread
[self.navigationController popToRootViewControllerAnimated:YES];
This method will navigate to the root of your navigationController.
You can check your viewController hierachy With following code.
NSLog(#"%#",self.navigationController.viewControllers);
I resolved this problem with this way:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
UINavigationController * nav = tabbarControl.selectedViewController;
[nav.viewControllers objectAtIndex:0];
[nav setViewControllers:#[[nav.viewControllers objectAtIndex:0]] animated:NO];
tabbarControl.selectedIndex = 0;
});
When you delay one second the view will pop from UI, then the view will pop from the navigation stack, I think is the problem of the animation serial.
I had the same problem on iOS 8.
I solved by subclassing UINavigationController and adding this code:
- (void)viewDidLoad
{
[super viewDidLoad];
self.delegate = self;
}
- (UIViewController *)popViewControllerAnimated:(BOOL)animated
{
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
return [super popViewControllerAnimated:animated];
}
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
{
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
}
I basically block all the user interactions during the pop animation. I know it's a dirty solution, but it's the only one that I found that solves the problem.
I think that should be working without dispatch_async.
I got to the same issue, but i got to know the reason.
We should check it if the current scene is assigned to a proper view controller name in the storyboard.(identity inspector -> class)
If you connect a button action to m file and then insert the name of the view controller, that is not working.
So, you should delete the connect, and you insert the proper view controller name, and then you should connect the action to m file again.
I created my project from master-detail template, that uses split view controller. In my case, removing the split view controller resolved this issue.
It is important that that calls to popViewController(animated:), popToRootViewController(animated:) and related calls be made in the main queue, but under some conditions this doesn't seem to be good enough, and the animation doesn't occur.
I was able to fix it as described in some other answers here, performing the pop navigation later in the main queue. The Swift equivalent:
DispatchQueue.main.async {
self.rootViewController.popViewController(animated: true)
}
This might be explained by other animations that are still in progress, and by scheduling the block this way it happens at the end of the current work taking place or currently scheduled in the main queue, which allows the animation to execute correctly.
Try this code for popup a view controller from navigation stack
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:self.navigationController.viewControllers.count -2] animated:YES];

Unbalanced calls to begin/end appearance transitions for <UITabBarController: 0x197870>

I read SO about another user encountering similar error, but this error is in different case.
I received this message when I added a View Controller initially:
Unbalanced calls to begin/end appearance transitions for
<UITabBarController: 0x197870>
The structure of the app is as follow:
I got a 5-tab TabBarController linked to 5 View Controllers. In the initial showing tab, I call out a new View Controller to overlay as an introduction of the app.
I use this code to call the introduction view controller:
IntroVC *vc = [[IntroVC alloc] init];
[self presentModalViewController:vc animated:YES];
[vc release];
After this IntroVC view controller shows up, the above error shows.
p.s. I am using xCode 4.2 & iOS 5.0 SDK, developing iOS 4.3 app.
Without seeing more of the surrounding code I can't give a definite answer, but I have two theories.
You're not using UIViewController's designated initializer initWithNibName:bundle:. Try using it instead of just init.
Also, self may be one of the tab bar controller's view controllers. Always present view controllers from the topmost view controller, which means in this case ask the tab bar controller to present the overlay view controller on behalf of the view controller. You can still keep any callback delegates to the real view controller, but you must have the tab bar controller present and dismiss.
I fixed this error by changing animated from YES to NO.
From:
[tabBarController presentModalViewController:viewController animated:YES];
To:
[tabBarController presentModalViewController:viewController animated:NO];
As posted by danh
You can generate this warning by presenting the modal vc before the app is done initializing. i.e. Start a tabbed application template app and present a modal vc on top of self.tabBarController as the last line in application:didFinishLaunching. Warning appears. Solution: let the stack unwind first, present the modal vc in another method, invoked with a performSelector withDelay:0.0
Try to move the method into the viewWillAppear and guard it so it does get executed just once (would recommend setting up a property)
Another solution for many cases is to make sure that the transition between UIViewControllers happens after the not-suitable (like during initialization) procedure finishes, by doing:
__weak MyViewController *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf presentViewController:vc animated:YES];
});
This is general for also pushViewController:animated:, etc.
I had the same problem. I called a method inside viewDidLoad inside my first UIViewController
- (void)viewDidLoad{
[super viewDidLoad];
[self performSelector:#selector(loadingView)
withObject:nil afterDelay:0.5];
}
- (void)loadingView{
[self performSegueWithIdentifier:#"loadedData" sender:self];
}
Inside the second UIViewController I did the same also with 0.5 seconds delay. After changing the delay to a higher value, it worked fine. It's like the segue can't be performed too fast after another segue.
I had the same problem when I need to Present My Login View Controller from another View Controller If the the User is't authorized, I did it in ViewDidLoad Method of my Another View Controller ( if not authorized -> presentModalViewController ). When I start to make it in ViewDidAppear method, I solved this problem. I Think that ViewDidLoad only initialize properties and after that the actual showing view algorithm begins! Thats why you must use viewDidAppear method to make modal transitions!
If you're using transitioningDelegate (not the case in this question's example), also set modalPresentationStyle to .Custom.
Swift
let vc = storyboard.instantiateViewControllerWithIdentifier("...")
vc.transitioningDelegate = self
vc.modalPresentationStyle = .Custom
I had this problem because of a typo:
override func viewDidAppear(animated: Bool) {
super.viewWillAppear(animated)
instead of
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
It was calling "WillAppear" in the super instead of "DidAppear"
I had lot of problem with the same issue. I solved this one by
Initiating the ViewController using the storyboad instantiateViewControllerWithIdentifier method. i.e Intro *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"introVC"];
[self.tabBarController presentModalViewController : vc animated:YES];
I have the viewcontroller in my storyboard, for some reason using only [[introvc alloc] init]; did not work for me.
I solved it by writing
[self.navigationController presentViewController:viewController
animated:TRUE
completion:NULL];
I had this problem with a third party code. Someone forgot to set the super inside of viewWillAppear and viewWillDisappear in a custom TabBarController class.
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// code...
}
or
- (void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// code...
}
I had the same error. I have a tab bar with 3 items and I was unconsciously trying to call the root view controller of item 1 in the item 2 of my tab bar using performSegueWithIdentifier.
What happens is that it calls the view controller and goes back to the root view controller of item 2 after a few seconds and logs that error.
Apparently, you cannot call the root view controller of an item to another item.
So instead of performSegueWithIdentifier
I used [self.parentViewController.tabBarController setSelectedIndex:0];
Hope this helps someone.
I had the same problem and thought I would post in case someone else runs into something similar.
In my case, I had attached a long press gesture recognizer to my UITableViewController.
UILongPressGestureRecognizer *longPressGesture = [[[UILongPressGestureRecognizer alloc]
initWithTarget:self
action:#selector(onLongPress:)]
autorelease];
[longPressGesture setMinimumPressDuration:1];
[self.tableView addGestureRecognizer:longPressGesture];
In my onLongPress selector, I launched my next view controller.
- (IBAction)onLongPress:(id)sender {
SomeViewController* page = [[SomeViewController alloc] initWithNibName:#"SomeViewController" bundle:nil];
[self.navigationController pushViewController:page animated:YES];
[page release];
}
In my case, I received the error message because the long press recognizer fired more than one time and as a result, my "SomeViewController" was pushed onto the stack multiple times.
The solution was to add a boolean to indicate when the SomeViewController had been pushed onto the stack. When my UITableViewController's viewWillAppear method was called, I set the boolean back to NO.
I found that, if you are using a storyboard, you will want to put the code that is presenting the new view controller in viewDidAppear. It will also get rid of the "Presenting view controllers on detached view controllers is discouraged" warning.
In Swift 2+ for me works:
I have UITabBarViewController in storyboard and I had selectedIndex property like this:
But I delete it, and add in my viewDidLoad method of my initial class, like this:
override func viewDidLoad() {
super.viewDidLoad()
self.tabBarController?.selectedIndex = 2
}
I hope I can help someone.
This error will be displayed when trying to present an UINavigationController that is lazily initialized via a closure.
Actually you need to wait till the push animation ends. So you can delegate UINavigationController and prevent pushing till the animation ends.
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
waitNavigation = NO;
}
-(void)showGScreen:(id)gvc{
if (!waitNavigation) {
waitNavigation = YES;
[_nav popToRootViewControllerAnimated:NO];
[_nav pushViewController:gvc animated:YES];
}
}
As #danh suggested, my issue was that I was presenting the modal vc before the UITabBarController was ready. However, I felt uncomfortable relying on a fixed delay before presenting the view controller (from my testing, I needed to use a 0.05-0.1s delay in performSelector:withDelay:). My solution is to add a block that gets called on UITabBarController's viewDidAppear: method:
PRTabBarController.h:
#interface PRTabBarController : UITabBarController
#property (nonatomic, copy) void (^viewDidAppearBlock)(BOOL animated);
#end
PRTabBarController.m:
#import "PRTabBarController.h"
#implementation PRTabBarController
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (self.viewDidAppearBlock) {
self.viewDidAppearBlock(animated);
}
}
#end
Now in application:didFinishLaunchingWithOptions:
PRTabBarController *tabBarController = [[PRTabBarController alloc] init];
// UIWindow initialization, etc.
__weak typeof(tabBarController) weakTabBarController = tabBarController;
tabBarController.viewDidAppearBlock = ^(BOOL animated) {
MyViewController *viewController = [MyViewController new];
viewController.modalPresentationStyle = UIModalPresentationOverFullScreen;
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:viewController];
[weakTabBarController.tabBarController presentViewController:navigationController animated:NO completion:nil];
weakTabBarController.viewDidAppearBlock = nil;
};
you need make sure -(void)beginAppearanceTransition:(BOOL)isAppearing animated:(BOOL)animated and -(void)endAppearanceTransition is create together in the class.
I had the same issue. When developing I wanted to bypass screens. I was navigating from one view controller to another in viewDidLoad by calling a selector method.
The issue is that we should let the ViewController finish transitioning before transitioning to another ViewController.
This solved my problem: The delay is necessary to allow ViewControllers finish transitioning before transitioning to another.
self.perform(#selector(YOUR SELECTOR METHOD), with: self, afterDelay: 0.5)
For me this error occurred because i didn't have UIWindow declared in the upper level of my class when setting a root view controller
rootViewController?.showTimeoutAlert = showTimeOut
let navigationController = SwipeNavigationController(rootViewController: rootViewController!)
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()
Ex if I tried declaring window in that block of code instead of referencing self then I would receive the error
I had this problem when I had navigated from root TVC to TVC A then to TVC B. After tapping the "load" button in TVC B I wanted to jump straight back to the root TVC (no need to revisit TVC A so why do it). I had:
//Pop child from the nav controller
[self.navigationController popViewControllerAnimated:YES];
//Pop self to return to root
[self.navigationController popViewControllerAnimated:YES];
...which gave the error "Unbalanced calls to begin/end etc". The following fixed the error, but no animation:
//Pop child from the nav controller
[self.navigationController popViewControllerAnimated:NO];
//Then pop self to return to root
[self.navigationController popViewControllerAnimated:NO];
This was my final solution, no error and still animated:
//Pop child from the nav controller
[self.navigationController popViewControllerAnimated:NO];
//Then pop self to return to root, only works if first pop above is *not* animated
[self.navigationController popViewControllerAnimated:YES];
I encountered this error when I hooked a UIButton to a storyboard segue action (in IB) but later decided to have the button programatically call performSegueWithIdentifier forgetting to remove the first one from IB.
In essence it performed the segue call twice, gave this error and actually pushed my view twice. The fix was to remove one of the segue calls.
Hope this helps someone as tired as me!
Swift 5
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
//Delete or comment the below lines on your SceneDelegate.
// guard let windowScene = (scene as? UIWindowScene) else { return }
// window?.windowScene = windowScene
// window?.makeKeyAndVisible()
let viewController = ListVC()
let navViewController = UINavigationController(rootViewController: viewController)
window?.rootViewController = navViewController
}

Resources