Present Modal View Controller from inside Popover View - ios

So in my universal app I have a section where a person can look at an existing list of notes from our system (retrieved through a simple web service) and then also create a new note if they want. So for the iphone it's pretty simple layout, a TableViewController for displaying the list with a "Add" button on the NavigationBar that presents the modalview for adding the new item. On the iPad though, the same layout has a lot of wasted space so I opted to go with the popOver method to show the list in a popOver and then let them add from there. My problem is that when the user clicks on the Add button within the PopOver view, the modal view comes up full screen instead of just coming up within the popover view. Here's the code I have so far:
-(void) AddButtonPressed:(id)sender {
NewNoteVC *newNote = [[[NewNoteVC alloc] initWithNibName:#"NewNoteVC" bundle:nil] autorelease];
newNote.defaultClientID = defaultClientID;
UINavigationController *navCon = [[[UINavigationController alloc] initWithRootViewController:newNote] autorelease];
if ([isPopOver isEqualToString:#"YES"]) {
[navCon setModalInPopover:YES];
[self.navigationController setModalInPopover:YES];
[self.navigationController presentModalViewController:navCon animated:YES];
}
else {
[self.navigationController presentModalViewController:navCon animated:YES];
}
}
The "isPopOver" string is just a placeholder sent from the previous screen that called this TableView (I know I can switch this to a boolean for better performance I just put this together real quick to try it out). I know I messed up somewhere, I just don't know what setting I need to get this working correctly.

You need to define the view controller's modalPresentationStyle to be "current context".
navCon.modalPresentationStyle = UIModalPresentationCurrentContext;
This will result in modal view controller filling the popover like the popover's root controller.

Try using the presentViewController:animated:completion: instead of presentModalViewController:animated: and set self.navigationController.definesPresentationContext = YES

Related

iOS8 - Presenting a modal view removes sub view

I am updating our app to be compiled with xcode6/iOS8.
One issue I am running into is that when a modal view is presented. the underlying subview is removed. It is completely blacked out.. until the modal view is dismissed.. then it re-appears.
Has anyone run into this with iOS8? The same code has worked since iOS4.
Code:
PigDetailViewController *pigDetailViewController = [[PigDetailViewController alloc] initWithNibName:#"PigDetailViewController" bundle:nil];
self.navigationController.modalPresentationStyle = UIModalPresentationCurrentContext;
self.navigationController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentViewController:pigDetailViewController animated:YES completion:nil];
In iOS 8 they've added a new presentation style that behaves like UIModalPresentationCurrentContext in the circumstance you've described, it's UIModalPresentationOverCurrentContext. The catch here is that unlike with UIModalPresentationCurrentContext, you want to set the view controller to be PRESENTED with this presentation style, like so:
PigDetailViewController *pigDetailViewController = [[PigDetailViewController alloc] initWithNibName:#"PigDetailViewController" bundle:nil];
pigDetailViewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
self.navigationController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentViewController:pigDetailViewController animated:YES completion:nil];
Note that to support iOS 7 and below you'll likely need to check the OS version and decide how to present the view controller based on that.
edit: I'd like to add one more note to this. In iOS7 with UIModalPresentationCurrentContext, when the presented VC was dismissed, the underlying VC had its viewDidAppear method called. In iOS8 with UIModalPresentationOverCurrentContext, I've found the underlying VC does not have its viewDidAppear method called when the VC presented over top of it is dismissed.
Adding a point to BrennanR's answer.. even viewWillAppear doesn't call when the VC presented over top of it is dismissed.
I think you are misunderstanding how a modal view controller works.
When you present a view controller modally it will control the entire screen. It has an opaque background (normally black) and then draws its view on top of that.
So, if you set the view.backgroundColor to yellow (for example) it will have a yellow background. If you set it to clear then it will show through to the black background.
What I think you want is for the other view to "show through" so it looks like the modal view is sitting on top of it.
The best way I have found of doing this is to use this method...
// in the view controller that is presenting the modal VC
modalVC = // the modal VC that you will be presenting
UIView *snapshotView = [self.view snapshotViewAfterScreenUpdates:NO];
[modalVC.view insertSubView:snapshotView atIndex:0];
// present the modal VC
This will take a "screenshot" of the current view hierarchy and then place that snapshot underneath everything in the modal VC.
That way your black screen will be replaced by a screenshot of the previous view controller.

Having issues displaying modal view programmatically

I am very very new to iOS programming / Objective C.
The flow of events I want to have happen is: user selects tab from tab bar view controller. Once VIEW A has been loaded it will open a modal window to get some information
VIEW A - (void)viewDidLoad
ModalYearPickerViewController *modalYearPickerViewController= [[ModalYearPickerViewController alloc] init];
[self presentViewController:modalYearPickerViewController animated:NO completion:nil];
I am trying to have my year picker view load up right away so the user can select a year from my picker (in VIEW B), then close the modal window after the value has been passed back to VIEW A.
Now, the fist view loads, then goes to a black screen automatically. I am unsure why as my view controller for modalYearPickerViewController has a picker etc on it.
Any tips or help loading a modal view controller programmatically would be greatly appreciated!
Thanks!
If you are using storyboards :
UIStoryboard *storyBoard = [self storyboard];
This will return the storyboard of your current view controller. I am assuming your View Controller A is also on your storyboard.
ModalYearPickerViewController *modalYearPickerViewController = [storyBoard instantiateViewControllerWithIdentifier:#"ModalYearPickerViewController"];
This will instantiate your view controller from the storyboard. But one other thing you have to do is set your view controllers storyboard id to ModalYearPickerViewController. You can set this right below where you set your custom view controller class in the storyboard.
[self presentViewController:modalYearPickerViewController animated:NO completion:nil];
and done.
If you have a xib file for that viewContrioller, it has to be loaded as well, to do that you have to call:
ModalYearPickerViewController *modalYearPickerViewController =
[[ModalYearPickerViewController alloc] initWithNibName:#"ModalYearPickerViewController" bundle:nil];

newly created UIViewController is not available in a storyboard

I'm running into an issue with one of my view controllers and am hoping someone can help me out: my storyboard behaves as if the view controller does not exist, preventing me from making connections to the controller.
I've created a custom freeform view controller in a storyboard, it's purpose is to be displayed in a popover, allowing a user to edit a UITextLabel using the popover's UITextView.
I'm dynamically instantiating the view controller for the popover using the code below:
//create a popover with a text view under a label to be edited,
// allowing the user to edit the label with the popover
GLAppDelegate* appDelegate = (GLAppDelegate*)[[UIApplication sharedApplication] delegate];
UIViewController *mvc = (UIViewController *)appDelegate.window.rootViewController;
PopoverTextViewController* textViewController = [mvc.storyboard instantiateViewControllerWithIdentifier:#"popoverTextViewController"];
textViewController.textView.text = ((OutlinedLabel*)v.attributedView).text;
textViewController.label = (OutlinedLabel*)v.attributedView;
[self.popover dismissPopoverAnimated:YES];
self.popover = [[UIPopoverController alloc] initWithContentViewController:textViewController];
[self.popover presentPopoverFromRect:v.attributedView.frame inView:v permittedArrowDirections:UIPopoverArrowDirectionUp|UIPopoverArrowDirectionDown animated:YES];
It appears to me that the view controller I made is a typical view controller:
The problem that I'm running in is that autocomplete behaves as if there is no such view controller available in my app. Even if I manually paste it's name here, I cannot connect the textView property from the storyboard to the view controler.
When I test the code, it works - the label can be edited with the popover, but I cannot set the text within the popover, because I cannot link the text view to the view controller.
I appreciate your help!
I figured out the answer, it is a composite, 3 piece thing.
I did a "clean" on the project, then quit xCode and restarted it - this clears some weird bugs, and did work in this case. I was able to add the controller in storyboard
I forgot IBOutlet next to textview: #property(nonatomic,strong)IBOutlet UITextView* textView;
In case of popover, the textView property is nil, until viewDidLoad gets called after the popover is presented, so the code had to be reworked as follows:
//create a popover under the label with a text view, allowing the user to edit the label with the popover
GLAppDelegate* appDelegate = (GLAppDelegate*)[[UIApplication sharedApplication] delegate];
UIViewController *mvc = (UIViewController *)appDelegate.window.rootViewController;
PopoverTextViewController* textViewController = [mvc.storyboard instantiateViewControllerWithIdentifier:#"popoverTextViewController"];
[self.popover dismissPopoverAnimated:YES];
self.popover = [[UIPopoverController alloc] initWithContentViewController:textViewController];
//before this call, all views of the controller are not loaded
[self.popover presentPopoverFromRect:v.attributedView.frame inView:v permittedArrowDirections:UIPopoverArrowDirectionUp|UIPopoverArrowDirectionDown animated:YES];
//once popover is presented, the textView is created and is available
textViewController.textView.text = ((OutlinedLabel*)v.attributedView).text;
textViewController.label = (OutlinedLabel*)v.attributedView;
I hope someone finds this useful.

My viewController doesn't show up right

in my iPad-app I am trying to present one of my views with a modal formsheet-style.
Here's some code:
-(void)present
{
SecondViewController *modal = [[SecondViewController alloc]init];
modal.modalPresentationStyle = UIModalPresentationStyleFormSheet;
[self presentModalViewController:modal animated:YES];
}
I am using Storyboard, and I have put stuff like a textView and toolbars in the view I'd like to show. I have set the right class in Identity Inspector, and in the class-files I have checked that it's the right view appearing with putting NSLog(#"Right view");
When calling the void present, a view is appearing, but only as a dark-white square. Nothing og my content from Storyboard is in it, I even tried changing the background color of the view and the textView to see if something was just outside the square, but the whole thing stayed white. It feels like it's not using the view I created in storyboard, but I have set it to the correct class, and the NSLog gets printed out when calling it. I have not connected the two views in any way in Storyboard, the SecondViewController is just floating around, so that might be the problem? The button that calls for -(void)present is created programmatically, so I can't ctrl+drag it to the button either.
Why is it showing an empty version of my class?
In the "Identity Inspector" set a "Storyboard ID" for your ViewController, and then present it like this:
-(void)present
{
SecondViewController *modal = [self.storyboard instantiateViewControllerWithIdentifier:#"myStoryboardID"];
modal.modalPresentationStyle = UIModalPresentationStyleFormSheet;
[self presentModalViewController:modal animated:YES];
}
And if you're using iOS6, presentModalViewController:animated: is deprecated, so use this:
-(void)present
{
SecondViewController *modal = [self.storyboard instantiateViewControllerWithIdentifier:#"myStoryboardID"];
modal.modalPresentationStyle = UIModalPresentationStyleFormSheet;
[self presentViewController:modal animated:YES completion:nil];
}
Your problem is that you're assuming the program will intrinsically know where to find the, already laid out, view for this controller when that's simply not how storyboards work. The code you list about will create a view controller, but without an associated view it will simply show as a black square.
There's a few ways to solve your dilemma:
Add the modal transition as a segue in the view controller, this would be the simplest way and is what iOS storyboards expect you to do.
Move the view from the storyboard to an external .xib and call the initWithNibName:bundle: method to load this as your view controller's view. This is the best solution if you just want to programmatically load the view.
Load the view from your storyboard programmatically with the instantiateViewControllerWithIdentifier: method, this is probably a bad idea as it goes against the design of storyboards.
I can elaborate on those if you want.

Popovers cannot be presented from a view which does not have a window

What does this error indicate:
"Popovers cannot be presented from a view which does not have a window."
the thing that saved my life:
if (self.view.window != nil)
[popoverController presentPopoverFromRect:CGRectMake(44, yCoord, 111, 111) inView:self.view permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
by adding if condition it doesn´t crash anymore. I don´t really get it because the presentPopoverFromRect function is ALWAYS called. There is no situation where window would be nil but anyway it did the trick.
edit: I have this code in viewDidAppear.
Nevertheless in most cases it's enough to move presentPopoverFromRect to viewDidAppear or didMoveToWindow but in my case for some reason the if condition was necessary.
the view you're adding the popover to has to already have been added to a window with the "addSubview:" method.
Try waiting until
- (void) didMoveToWindow
is called for the view and then load the popover
I got this problem.
I had a UITabBarController as the detail view, and I set the barButtonItem as the leftBarButtonItem on all three navigation controllers in the tab bar.
vcChart.navigationItem.leftBarButtonItem = barButtonItem;
vcAnalysis.navigationItem.leftBarButtonItem = barButtonItem;
vcTechnicals.navigationItem.leftBarButtonItem = barButtonItem;
Turns out only the last one added is valid, and the previous two would throw the exception when tapped on.
To fix it, I only set the leftBarButtonItem for the visible view controller, and just switched the barButtonItem to the visible view controller every time the user switched tabs.
Just encountered this issue. Turned out that the inView: parameter was using an IBOutlet that wasn't connected in IB. Thus, an attempt was made to launch the popover in nil. That doesn't work.
So, make sure you are using a valid view.
There are many ways to get to this error. Basically you need to wait to call the presentPopover command until your calling view is added to a window. I did it this way.
- (void)viewDidAppear:(BOOL)animated
{
[self methodThatDisplaysPopOver];
}
My presentPopoverFromRect call is inside my methodThatDisplaysPopOver function.
You could protect every presentPopover call like MobiMaciek suggests with this.
if (self.view.window != nil)
[popoverController presentPopoverFromRect:CGRectMake(10, 10, 100, 100) inView:self.view permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
However, I think it would be better to understand when self.view.window gets assigned and make sure that you present you popover after the view has a window.
I received the same error message when assigning the same UIBarButtonItem to multiple navigation items as did Lewis. My example was slightly more complicated as I was using a UISplitViewController.
In my RootViewController I have an array of arrays to accomplish multiple sections within my table. Each time that the user clicks a row in the table, a new "detail" view controller is placed in the right pane of my splitViewController. Prior to setting the leftBarButtonItem = nil, I would receive a segfault after 3-4 clicks of the "Menu" button with the same error as a111. I updated my code to actually retrieve the previous detail view controller and set the leftBarButtonItem item to nil.
allData is my NSMutableArray that contains several other NSMutableArrays as objects.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Retrieve the new detail view controller
UIViewController *detailViewController = [[self.allData objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
// Add the detail view controller to a navigation controller and set the bar style
UINavigationController *detailNavigationController = [[UINavigationController alloc] initWithRootViewController:detailViewController];
detailNavigationController.navigationBar.barStyle = [[NSUserDefaults standardUserDefaults] integerForKey:#"UIBarStyle"];
// Retrieve previous detail view controller and remove the leftBarButtonItem
UINavigationController *previousDetailNavigationController = [splitViewController.viewControllers objectAtIndex:1];
UIViewController *previousDetailViewController = [[previousDetailNavigationController viewControllers] lastObject];
previousDetailViewController.navigationItem.leftBarButtonItem = nil;
// Update the split view controller's view controllers array.
NSArray *viewControllers = [[NSArray alloc] initWithObjects:self.navigationController, detailNavigationController, nil];
splitViewController.viewControllers = viewControllers;
[detailNavigationController release];
[viewControllers release];
// Dismiss the popover if it's present.
if (popoverController != nil) {
[popoverController dismissPopoverAnimated:YES];
}
// This sets the left bar to nil when in landscape and equal to "Menu" when in portrait.
// We need to remove rootPopoverButtonItem from the previous viewController...
detailViewController.navigationItem.leftBarButtonItem = rootPopoverButtonItem;
}
The error message was slightly deceiving at first but the answers above helped me out. I wonder why I could click the "Menu" button up to 3-4 different times before the segfault... I'll investigate further.
This error also occurred when the inView: Parameter is incorrect - to test try self.view
yes, you are right but still we can add subview from parent class in it. so it can be represented from a view which have a window:
[popoverController.contentViewController.view addSubview:mySubView];
I had the same error message as the OP, in a very similar situation to that reported by TPoschel, except I had a split view controller with an embedded tab bar controller in the detail pane, and a navigation controller within this. The bar button item is added as the navigation bar leftBarButtonItem.
Only on iOS5.0 (not 5.1) does it seem to require you invalidate the bar button item on the tab bar you are leaving by setting it to nil. Before then adding the bar button to the navigation bar on the tab you are going to.
If I don't do that, from debugging my own code, the window property of the bar button item stays set to nil, and causes the exception, on returning to a screen you'd previously been to. I'm guessing as a side effect of setting the leftBarButtonItem in the navigation item, it goes off and sets the frame. But it doesn't seem to bother unless the button is different from what is currently set there. Hence, the need to set it to nil when leaving a tab, even though it is technically the same button that's being passed around.
I would upvote TPoschel's answer, except SO won't let me.
I had a problem like this. Received this message when clicking a customized UIBarButton item that invoked a selector method with did performSeque.
The problem was my segue was still attached to the UIBarButton item. It should have been attached to the main view of of the view controller. Changed this and worked fine.
P.S., all this got started because I wanted to add and "info" button to my UIToolBar. This isn't one in the system provided list and should be.
There will be a view from which you asks to display your popover.The reason for this error is because you didn't made this view as a subview of the window.
[self.view addSubview:displayPopOverVC];
where displayPopOverVC is the view controller from which the popOver appears
i had the same problem, after adding PresentPopOver in viewDidAppear this was solved
- (void) viewDidAppear:(BOOL)animated{
CGRect popoverRect = screenBounds;
popoverRect.size.width = MIN(popoverRect.size.width,0) ;
popoverRect.origin.x = screenBounds.origin.x;
[popoverController
presentPopoverFromRect:popoverRect
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
this was happening as inView:self.view should be called after viewDidLoad as suggested by #hey68You and MobiMaciek..
I replaced
[actionSheet showFromBarButtonItem:self.navigationController.navigationItem.leftBarButtonItem animated:YES];
with
[actionSheet showInView:self.view];

Resources