iOS how present Modal ViewController from Uiview - ios

i have subclass a UIView and now i need to show a view controller but UIView not have method to present view controller.
this is my problem
thank's
this is a piece of code inside my uiview subclass
-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
if ([tabella isEqualToString:#"Articoli"]) {
NSDictionary *itm=(NSDictionary*)[comanda objectAtIndex:indexPath.row];
Articoli *aboutViewController = [[Articoli alloc] initWithNibName:#"Articoli" bundle:nil];
aboutViewController.modalPresentationStyle = UIModalPresentationFormSheet;
aboutViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
aboutViewController.idarticolo=[itm objectForKey:#"idarticolo"];
CGRect aboutSheetFrame = aboutViewController.view.frame;
UIViewController *viewController = [UIViewController new];
viewController.view = self;
//here xcode give me a red error
[self presentViewController:viewController animated:YES completion:nil] ;
aboutSheetFrame =CGRectZero;
aboutViewController.view.superview.bounds = aboutSheetFrame;
}
}

When you need a communication between UIView instance and UIViewController, there are a few known iOS concepts, which you should adhere to. As you have figured out that UIView cannot really present a new controller (missing either presetViewController:animated:completion methods or navigationController property, which are both present in UIViewController).
Views are supposed to be the most reusable parts of your code, so you must think of a way to design your views to be completely blind to where they are at. They usually only know about user interaction.
So first, what you must do is refactor your view.
If your UIView is supposed to be a UIControl (has some kind of target selectors), you need to use add target in your controller to get callback from view interaction.
You can use delegate pattern as used in UITableView or UICollectionView, which is designed as a protocol.
You can use gesture recognizers added to a view (UITapGestureRecognizer for example), so the controller knows about user interaction.
You can even mix and match those architectural patterns.
But you should really look into iOS programming basics, to understand this better.
In addition the first error I see in your code is that you create a generic UIViewController, when you should really be creating custom subclasses of it, defined in Storyboard and separate subclass of UIViewController.
The second error I see is that your UIView responds do tableView:didSelectRowAtIndexPath: method, which should in fact never happen. All this code must be moved back to one UIViewController subclass.

You can do this without any view hierarchy issues using the below code.
ObjectiveC
UIViewController *currentTopVC = [self currentTopViewController];
currentTopVC.presentViewController.........
- (UIViewController *)currentTopViewController
{
UIViewController *topVC = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
while (topVC.presentedViewController)
{
topVC = topVC.presentedViewController;
}
return topVC;
}
Swift
var topVC = UIApplication.sharedApplication().keyWindow?.rootViewController
while((topVC!.presentedViewController) != nil){
topVC = topVC!.presentedViewController
}
topVC?.presentViewController........

PresentViewController is method of UIViewController class not of UIView, you can do one thing, create UIViewController instance and set its view to the view you have and then present it.
Something like below
YourCustomView *customView = [[YourCustomView alloc]initWithFrame:someFrame];
UIViewController *viewController = [UIViewController new];
viewController.view = customView;
//From currentViewController present this
[self presentViewController:viewController animated:YES completion:nil] ;
Customize this code as per your requirement
But as you are in view you need to pass this event to viewController, so better implement delegate method and at place where you calling present viewController call delegate which is implemented in ViewController and in side that presentViewController with customView set to its view property

You can also present your viewcontroller from the navigation controller object
Create Global Navigation Object in App Delegate or anywhere, you can access navigationcontroller object from view
#property (strong, nonatomic) UINavigationController *gblNavigation;
//Present viewcontroller from NavigationController object
[gblNavigation presentViewController:YOUR_VC_Object animated:YES completion:nil];

You can't present a view controller from a view. You can only present a view controller from a view controller.
Apple wants views to be dumb. That is views should only know how to display content. View should not respond to user interaction: that should be passed to a view controller.
You may want to consider using a delegate pattern, target action, or something similar to allow a view controller to control the interaction.

iOS 15, compatible down to iOS 13
Based on Shamsudheen TK solution for anyone who comes across this question in the future.
let presentedWindow = UIApplication.shared.connectedScenes.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }.first { $0.isKeyWindow }
guard let currentViewController = presentedWindow?.rootViewController else {
return
}
currentViewController.present(UIViewController(nibName: nil, bundle: nil), animated: true)
Note that connectedScenes is available only since iOS 13. If you need to support earlier versions of iOS, you have to place this in an if #available(iOS 13, *) statement.

Related

How to have an action in one view controller start another in a different view controller?

New to iOS development & Objective-C and am a little unsure how to go about solving this issue.
I'm working on a project that works like a video player. There are two ViewControllers:
MenuViewController (has a list of titles that act as buttons)
PlayerViewController (view where video plays)
In the MenuViewController, I want to be able to click onto a button (video title) :
- (IBAction)videoOne:(id)sender
{
PlayerViewController * vc = [[PlayerViewController alloc] initWithNibName:#"PlayerViewController" bundle:nil];
[self presentModalViewController:vc animated:YES];
}
and have an action that's currently defined in the PlayerViewController automatically execute as soon as its loaded.
Is there a way to have a button in one ViewController call the action in another ViewController as soon as that second ViewController has loaded?
Thanks!
The right way to solve this problem will be set up a delegate pattern between the two view-controllers.
Example:
#protocol PlayerViewControllerDelegate<NSObject>
{
-(void)playerViewControllerViewDidLoad:(PlayerViewController *)playerVC;
}
Then, in PlayerViewController.h create a weak delegate variable:
#property (nonatomic, weak) id<PlayerViewControllerDelegate> delegate;
In PlayerViewController.m, notify the delegate on viewDidLoad:
-(void)viewDidLoad {
[super viewDidLoad];
[self.delegate playerViewControllerViewDidLoad:self]
}
In MenuViewController:
- (IBAction)videoOne:(id)sender
{
PlayerViewController * vc = [[PlayerViewController alloc] initWithNibName:#"PlayerViewController" bundle:nil];
vc.delegate = self
[self presentViewController:vc animated:YES];
}
Finally, implement the protocol in MenuViewController, and you're ready to go:
#interface MenuViewController : UIViewController<PlayerViewControllerDelegate>
#end
#implementation MenuViewController
-(void)playerViewControllerViewDidLoad:(PlayerViewController*)playerVC
{
[playerVC playVideo];
}
#end
It is usually not good practice to have one controller controlling the behavior of another. Instead, you can have the MenuViewController create the PlayerViewController and set variables so the new player knows how to behave based on its internal state.
There are several UIViewController methods that you can override in order to perform actions during the controller's lifecycle. Based on your question it seems like you want the viewDidLoad method.
I am not sure how you are passing videos between controllers, but if you were using URLs (to Youtube videos for example) then you could do something like the following:
// MenuViewController.m
- (IBAction)videoOne:(id)sender {
PlayerViewController* vc = [[PlayerViewController alloc] initWithNibName:#"PlayerViewController" bundle:nil];
// Pass any necessary data to the controller before displaying it
vc.videoURL = [self getURLForSender:sender];
[self presentViewController:vc animated:YES completion:nil];
}
// PlayerViewController.m
- (void)viewDidAppear {
// View did appear will only be called after the controller has displayed
// its primary view as well as any views defined in your storyboard or
// xib. You can safely assume that your views are visible at this point.
[super viewDidAppear];
if (self.videoURL) {
[self playVideo];
}
}
You would need to define the property videoURL on PlayerViewController and expose it publicly. If you are using local files (such as from the user's photo storage) you could pass the video to the new view controller before presenting it.
There are other UIViewController lifecycle methods that you can override. They are explained in more depth in this post as well as Apple's UIViewController Documentation.
Edit: changed presentModalViewController:animated: to presentViewController:animated:completion: and changed viewDidLoad to viewDidAppear as it seems more appropriate for the question.
presentModalViewController:animated: is deprecated. Use presentViewController:animated:completion: instead.
In the completion Block, you can call a method on the presented view controller:
[self presentViewController:otherVC
animated:YES
completion:^{ [otherVC startPlaying]; }];
The completion is run after the presented controller's viewDidAppear.

Transition Delegate for UITabBarController animation

I have a custom UIViewControllerAnimationTransition class created already, and need to make this animate a UITabBarController when it switches tabs.
The tabBarController does not use the regular tab bar, though. I have a custom implementation that acts like it, and when a button is pressed, it calls this code:
tabBarController.selectedIndex = index
Currently I have the tabBarController (subclass) as the delegate for its own transitionDelegate. The delegate method animationControllerForPresentedController is never actually called, though.
Is it fine for the tab bar controller to be its own delegate? If so, why is the transition code never actually called?
animationControllerForPresentedController is the wrong approach for the tab bar controller.
In the UITabBarController subclass, adopt the UITabBarControllerDelegate protocol and set it as its own delegate. Then, use tabBarController: animationControllerForTransitionFromViewController: toViewController: to return the custom UIViewControllerAnimatedTransitioning object.
To get a better visualization, look at VCTransitionsLibrary in the TabBarDemo folder.
Did you use these delegate methods like this?
#interface BTSlideInteractor : NSObject <UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate>
- (IBAction)showModalButtonWasTouched:(id)sender
{
BTSlideInteractor *interactor = [[BTSlideInteractor alloc] init];
interactor.presenting = YES;
BTModalViewController *modalController = [self.storyboard instantiateViewControllerWithIdentifier:#"ModalViewController"];
modalController.modalPresentationStyle = UIModalPresentationCustom;
modalController.transitioningDelegate = interactor;
[self presentViewController:modalController animated:YES completion:nil];
}
Use this link for Reference: https://github.com/brightec/CustomViewControllerTransition/blob/master/test/BTViewController.m
If you didnt find the solution kindly add your codes.

Using multiple storyboards with a TabBarController

Okay, so in the process of developing my newest app, I found that my storyboard got huge, so in an effort to clean it up some, i have divided it into multiple storyboards before it gets out of hand. just for settings alone i have roughly 20 tableviewcontrollers that branch out from a root NavigationController. That navigationcontroller was a TabItem on a TabBarController, which is the application's root view controller.
I've moved the TabBar into it's own StoryBoard as the Root_Storyboard and the Navigation controller is now the initial view of the Settings_Storyboard.
Just for testing purposes, I placed a few UIViewControllers as tab items in the TabBarController (Root_Storyboard) and subclassed one and added the following code to it's viewWillAppear method. It works great, but I know that the presentViewController displays the NavigationController modally and hides the tabBar. Obviously I don't want that, how do I get it to push properly so that the TabBar remains visible?
- (void) viewWillAppear:(BOOL)animated {
UIStoryboard *settingsStoryboard = [UIStoryboard storyboardWithName:#"Settings_iPhone" bundle:nil];
UIViewController *rootSettingsView = [settingsStoryboard instantiateInitialViewController];
[self.tabBarController presentViewController:rootSettingsView animated:NO completion:NULL];
}
Edit - To clarify. The above code is the subclassed method for a UIViewController (child of UITabBarController:index(1)) in the Root_iPhone.storyboard. The UINavigationController/UITableViewController that I am trying to load is found in Settings_iPhone.storyboard. Not sure how to implement the linkView suggested below in this situation.
This is quite possible and a smart move - decluttering your Storyboards presents cleaner interface files to dig through, reduced loading times in XCode, and better group editing.
I've been combing across Stack Overflow for a while and noticed everyone is resorting to Custom Segues or instantiating tab based setups programmatically. Yikes. I've hacked together a simple UIViewController subclass that you can use as a placeholder for your storyboards.
Code:
Header file:
#import <UIKit/UIKit.h>
#interface TVStoryboardViewController : UIViewController
#end
Implementation file:
#import "TVStoryboardViewController.h"
#interface TVStoryboardViewController()
#property (nonatomic, strong) UIViewController *storyboardViewController;
#end
#implementation TVStoryboardViewController
- (Class)class { return [self.storyboardViewController class]; }
- (UIViewController *)storyboardViewController
{
if(_storyboardViewController == nil)
{
UIStoryboard *storyboard = nil;
NSString *identifier = self.restorationIdentifier;
if(identifier)
{
#try {
storyboard = [UIStoryboard storyboardWithName:identifier bundle:nil];
}
#catch (NSException *exception) {
NSLog(#"Exception (%#): Unable to load the Storyboard titled '%#'.", exception, identifier);
}
}
_storyboardViewController = [storyboard instantiateInitialViewController];
}
return _storyboardViewController;
}
- (UINavigationItem *)navigationItem
{
return self.storyboardViewController.navigationItem ?: [super navigationItem];
}
- (void)loadView
{
[super loadView];
if(self.storyboardViewController && self.navigationController)
{
NSInteger index = [self.navigationController.viewControllers indexOfObject:self];
if(index != NSNotFound)
{
NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
[viewControllers replaceObjectAtIndex:index withObject:self.storyboardViewController];
[self.navigationController setViewControllers:viewControllers animated:NO];
}
}
}
- (UIView *)view { return self.storyboardViewController.view; }
#end
Description:
The view controller uses its Restoration Identifier to instantiate a storyboard in your project.
Once loaded, it will attempt to replace itself in its
UINavigationController's viewController array with the Storyboard's
initial view controller.
When requested, this subclass will return the UINavigationItem of the Storyboard's initial view controller. This is to ensure that navigation items loaded into UINavigationBars will correspond to the view controllers after the swap.
Usage:
To use it, assign it as the subclass of a UIViewController in your Storyboard that belongs to a UINavigationController.
Assign it a Restoration ID, and you're good to go.
Setup:
And here's how you set it up in the Storyboard:
This setup shows a tab bar controller with navigation controllers as its first tab controllers. Each navigation controller has a simple UIViewController as its root view controller (I've added UIImageViews to the placeholders to make it easy to remember what it links to). Each of them is a subclass of TVStoryboardViewController. Each has a Restoration ID set to the storyboard they should link to.
Some wins here:
It seems to work best for modal presentations where the subclass is the root view controller of a navigation controller.
The subclass doesn't push any controllers on the stack - it swaps. This means you don't have to manually hide a back button or override tab behaviour elsewhere.
If you double tap on a tab, it will take you to the Storyboard's initial view, as expected (you won't see that placeholder again).
Super simple to set up - no custom segues or setting multiple subclasses.
You can add UIImageViews and whatever you like to the placeholder view controllers to make your Storyboards clearer - they will never be shown.
Some limitations:
This subclass needs to belong to a UINavigationController somewhere in the chain.
This subclass will only instantiate the initial view controller in the Storyboard. If you want to instantiate a view controller further down the chain, you can always split your Storyboards further and reapply this subclass trick.
This approach doesn't work well when pushing view controllers.
This approach doesn't work well when used as an embedded view controller.
Message passing via segues likely won't work. This approach suits setups where sections of interface are unique, unrelated sections (presented modally or via tab bar).
This approach was hacked up to solve this UITabBarController problem, so use it as a partial solution to a bigger issue. I hope Apple improves on 'multiple storyboard' support. For the UITabBarController setup however, it should work a treat.
This is a bit late for Hawke_Pilot but it might help others.
From iOS 9.0 onwards you can create a Relationship Segue to another storyboard. This means that Tab Bar View Controllers can link to View Controllers on another storyboard without some of the mind-bending tricks seen in other answers here. :-)
However, this alone doesn't help because the recipient in the other storyboard doesn't know it's being linked to a Tab Bar View Controller and won't display the Tab Bar for editing. All you need to do once you point the Storyboard Reference to the required View Controller is select the Storyboard Reference and choose Editor->Embed In->Navigation Controller. This means that the Nav Controller knows it's linked to a Tab Bar View Controller because it's on the same storyboard and will display the Tab Bar at the bottom and allow editing of the button image and title. No code required.
Admittedly, this may not suit everyone but may work for the OP.
Not sure if your question is answered, and for others looking for a solution to this problem, try this method.
Create the Tab Bar Controller with Navigation Controllers in one storyboard file. And add an empty view controller (I named it RedirectViewController) as shown in the picture.
The child view controller (let's call it SettingsViewController for your case) is located in Settings_iPhone.storyboard.
In RedirectViewController.m, code this:
- (void)viewWillAppear:(BOOL)animated
{
UIStoryboard *settingsStoryboard = [UIStoryboard storyboardWithName:#"Settings_iPhone" bundle:nil];
UIViewController *rootSettingsView = [settingsStoryboard instantiateInitialViewController];
[self.navigationController pushViewController:rootSettingsView animated:NO completion:nil];
}
SettingsViewController will be pushed into view instantly when Settings tab is touched.
The solution is not complete yet! You will see "< Back" as the left navigationItem on SettingsViewController. Use the following line in its viewDidLoad method:
self.navigationItem.hidesBackButton = YES;
Also, to prevent the same tab bar item from being tap and causes a jump back to the blank rootViewController, the destination view controllers will need to implement UITabBarControllerDelegate
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
return viewController != tabBarController.selectedViewController;
}
It works for me.
Add Following code to your LinkViewController
-(void) awakeFromNib{
[super awakeFromNib];
///…your custom code here ..
UIStoryboard * storyboard = [UIStoryboard storyboardWithName:self.storyBoardName bundle:nil];
UIViewController * scene = nil;
// Creates the linked scene.
if ([self.sceneIdentifier length] == 0)
scene = [storyboard instantiateInitialViewController];
else
scene = [storyboard instantiateViewControllerWithIdentifier:self.sceneIdentifier];
if (self.tabBarController)
scene.tabBarItem = self.tabBarItem;
}
Here is the screenShot for LinkViewController .
LinkViewController is just a placeholder where new viewController would be placed. Here is the sample code which I used for my app.
RBStoryboardLink . Its working great for me. Let me know if it is helpful for you.

Get the UIViewController in TableView's CustomCell

I have one UIViewController named "MainView" and that has one UITableView named "tblLists"
tblLists generating the customCell - "customCellList".
My question is can I get the MainView's instance(self) in customCellList class.
I tried with superview thing but not get MainView. I want to achieve this without protocol.
So need your help in this.
You can use the responder chain to gain access to the view controller. Assuming your customCell class is a UITableViewCell subclass, the following method should do the job:
#implementation customCell
- (UIViewController *)getViewController
{
id vc = [self nextResponder];
while(![vc isKindOfClass:[UIViewController class]] && vc!=nil)
{
vc = [vc nextResponder];
}
return vc;
}
#end
The above code is courtesy of the Sensible TableView framework.
What you are asking is not a good idea, you should find another way around. It breaks the MCV pattern.
By the way, if you are worried with memory concern using ARC and targeting iOS>=5 you can create a weak reference to the table view itself and get the view controller as its delegate or data source property (of course if the VC is one them). Or you can create a weak reference to the VC itself.
As pointed in the comments is not a good idea, better find another way around. If you need to update you cells value there are a lot of methods to reload tableview data! By means of using KVO, notification, delegation etc on your VC from the model, you can simply trigger a reload to the table view without involving weird references in cells.
Hope this helps.
For those rare times when you want to break MVC.. This assumes you are using a Navigation controller as the rootVC on your window. Updated for Swift 2
func visibleVC() -> UIViewController? {
if let navVC: UINavigationController = UIApplication.sharedApplication().keyWindow?.rootViewController as? UINavigationController {
if let vc: UIViewController = navVC.visibleViewController as? MyViewControllerClass {
return vc
}
}
return nil
}
Also you can access to rootViewController:
UIViewController *controller = [UIApplication sharedApplication].keyWindow.rootViewController;
I have similar question, my case is to change array in View Controller when the value of textfield in Table View custom cell changed.
My solution is add delegate for UITextFiled in cellForRowAt method of tableview, then I can do all my data changing in textFieldDidEndEditing method. Because they are all in one class, the ViewController Class.

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