IOS/Autolayout: Change to Presenting View After Presented Controller Dismissed - ios

I am trying to get autolayout to work properly on a view controller with a scrollview. When I load the view controller the first time, it is not doing what I want. However, if I launch a second modal VC--which happens to be an Edit VC and then dismiss it, without doing anything else, the original VC lays out properly (the way I want).
At first, the size of a textview is not adapted to its content but merely reflects is placeholder view in storyboard.
After launching and dismissing the modal VC, the textview does adopt to its content.
As far as I know there is nothing that happens in the course of the 2ndVC launching that could change anything with the 1st.
My logs show ViewDidLoad is called by the first VC just once when it originally loads. Viewwillappear is called by the first VC twice: when it originally loads and then after the 2nd VC is dismissed.
Are there any methods other than view willappear that fire in a presenting VC when you dismiss a presented one?
This is the code in VC1 to launch the 2nd VC.
UIStoryboard *storyBoard = self.storyboard;
IDEditListVC *editVC =
[storyBoard instantiateViewControllerWithIdentifier:#"editlist"];
editVC.list = _list;
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController: editVC];
[self presentModalViewController:nav animated:YES];
//This is the code in VC1 viewWillAppear to obtain the height of text view. Result changes before and after launching and dismissing 2nd VC
self.textView.text = listText;
float listHeight = self.textView.contentSize.height;
NSLog(#"listHeight%f",listHeight);

Try this
-(void)viewDidLayoutSubviews
{
self.textView.text = listText;
float listHeight = self.textView.contentSize.height;
NSLog(#"listHeight%f",listHeight);
}

Related

Modal Presentation Forcing Navigation Controller to Pop to Root

I am having a strange problem that I can't seem to find the cause for.
When attempting to present a modal view controller on a navigation controller the navigation controller is popping all of my view controllers underneath when the modal is dismissed.
So after pushing a few view controllers and presenting a modal on the topViewController, I end up back at the rootViewController when the modal is dismissed.
Anyone had this happen to them lately, I can't seem to find the reasoning for why this is happening?
This answer is for #rshev:
It was actually a user error. Here's what was happening: I had a view controller with a manually added navigationController on top of it (as a subview/child VC). The nav controller then had 3 VCs in its stack. The third (and visible) VC was presenting an image picker controller. When the image picker was dismissed, I momentarily saw my third VC , then it quickly popped back to the 1st, discarding the other two VC's from memory.
So what went wrong? What I didn't realize is that viewDidAppear (and viewWillAppear) was being called on my content view controller (the one with nav controller for its subview). This content VC was actually setting its navigation controller (and adding it as a subview) on viewDidAppear, thus covering up the original nav controller.
To solve it, I just added a static boolean to determine when the first VC FIRST appears, like so:
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
static BOOL firstAppearance = YES;
if (firstAppearance)
{
firstAppearance = NO;
UINavigationController *navController = [self.storyboard instantiateViewControllerWithIdentifier:#"NavigationController"];
[navController.view setFrame:self.view.bounds];
[self.view addSubview:navController.view];
[self addChildViewController:navController];
[navController didMoveToParentViewController:self];
}
}
Hope that helps.

load viewcontroller within a tabbar control

on viewdidload of one of my tabbar viewcontrollers I'll like to display or popup another view controller. here's my code
if (_history.count == 0) {
//[self performSegueWithIdentifier:#"emptyHistorySegue" sender:self];
UIStoryboard* sb = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
MSAEmptyHistoryViewController *popupController = [sb instantiateViewControllerWithIdentifier:#"EmptyHistoryViewController"];
[self presentViewController:popupController animated:NO completion:nil];
}
The load the viewcontroller on top of the tabview controller. I need this view to display but still allow the user to see the tabbar buttons.
You will run into problems if you try to transition to another view controller in viewDidLoad. You should either switch the child view controllers of the tab bar controller or you can present a subview within one of the child view controllers.
When -viewDidLoad is called, the view controller is generally not presented. As such, if you try to present another controller, it will crash.
Try -viewDidAppear: if you want what you are describing.

Dismissing a ViewController lower in the stack does not behave as expected

I'm building a complex app that has kind of a branch in the middle.
At some point in the app, a particular UIViewController is presented, we'll call it mainViewController (shortened mainVC).
The mainVC presents another view controller, by code, using the following code (I strip out parts of it for privacy reasons):
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"SecondaryStoryboard" bundle:secondaryBundle];
SecondViewController *secondVC = [storyboard instantiateInitialViewController];
[self presentViewController:secondVC animated:YES completion:nil];
So the secondVC will later present another view controller, called thirdVC. This is done using a custom segue, set in the storyboard used in the code above, which code looks like this:
#implementation VCCustomPushSegue
- (void)perform {
UIView *sourceView = ((UIViewController *)self.sourceViewController).view;
UIView *destinationView = ((UIViewController *)self.destinationViewController).view;
UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
destinationView.center = CGPointMake(sourceView.center.x + sourceView.frame.size.width, destinationView.center.y);
[window insertSubview:destinationView aboveSubview:sourceView];
[UIView animateWithDuration:0.4
animations:^{
destinationView.center = CGPointMake(sourceView.center.x, destinationView.center.y);
sourceView.center = CGPointMake(0 - sourceView.center.x, destinationView.center.y);
}
completion:^(BOOL finished){
[self.sourceViewController presentViewController:self.destinationViewController animated:NO completion:nil];
}];
}
#end
As you can see this segue presents the destination view controller modally (by the use of presentViewController:) with a custom animation (a slide from right to left).
So basically up to here everything is fine. I present the secondVC with a classic modal animation (slide up from bottom) and present the thirdVC with my custom transition.
But when I want to dismiss the thirdVC, what I want is to go back directly to the mainVC. So I call the following from the thirdVC :
self.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self.presentingViewController.presentingViewController dismissViewControllerAnimated:_animate completion:nil];
That way, I'm calling dismissViewControllerAnimated: directly on mainVC (referenced by self.presentingViewController.presentingViewController), and I'm expecting the thirdVC to be dismissed with an animation, and the secondVC to just disappear without animation.
As Apple says in the UIViewController Class Documentation:
The presenting view controller is responsible for dismissing the view
controller it presented. If you call this method on the presented view
controller itself, it automatically forwards the message to the
presenting view controller.
If you present several view controllers in succession, thus building a
stack of presented view controllers, calling this method on a view
controller lower in the stack dismisses its immediate child view
controller and all view controllers above that child on the stack.
When this happens, only the top-most view is dismissed in an animated
fashion; any intermediate view controllers are simply removed from the
stack. The top-most view is dismissed using its modal transition
style, which may differ from the styles used by other view controllers
lower in the stack.
The issue is that it's not what happens. In my scenario, the thirdVC disappears, and shows the secondVC being dismissed with the classic modal slide to bottom animation.
What am I doing wrong ?
Edit :
So #codeFi's answer is probably working in a classic project, but the problem here is that I'm working on a framework. So mainVC would be in a client app, and the secondVC and thirdVC are in my framework, in a separate storyboard. I don't have access to mainVC in any other way than a reference to it in my code, so unwind segues are unfortunately not an option here.
I've been having this exact same issue, and I've managed to visually work around it by adding a snapshot of the screen as a subview to secondVC.view, like so:
if (self.presentedViewController.presentedViewController) {
[self.presentedViewController.view addSubview:[[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO]];
}
[self dismissViewControllerAnimated:YES completion:nil];
Not pretty, but it seems to be working.
NOTE: if your secondVC has a navigation bar, you will need to hide the navigation bar in between snapshotting the screen and adding the snapshot as a subview to secondVC, as otherwise the snapshot will appear below the navigation bar, thus seemingly displaying a double navigation bar during the dismissal animation. Code:
if (self.presentedViewController.presentedViewController) {
UIView *snapshot = [[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO];
[self.presentedViewController.navigationController setNavigationBarHidden:YES animated:NO];
[self.presentedViewController.view addSubview:snapshot];
}
[self dismissViewControllerAnimated:YES completion:nil];
I had the same issue and I've fixed it by using UnwindSegues.
Basically, all you have to do is add an IBAction Unwind Segue method in the ViewController that you want to segue to and then connect in IB the Exit action to your Unwind Segue method.
Example:
Let's say you have three ViewControllers (VC1, VC2, VC3) and you want to go from VC3 to VC1.
Step 1
Add a method to VC1 like the following:
- (IBAction)unwindToVC1:(UIStoryboardSegue*)sender
{
}
Step 2
Go in Interface Builder to VC3 and select it. Then CTRL-drag from your VC icon to Exit icon and select the method you've just added in VC1.
Step 3
While still in IB and with VC3 selected, select your Unwind Segue and in the Attributes Inspector add a Segue Identifier.
Step 4
Go to VC3 where you need to perform your segue (or dismiss the VC) and add the following:
[self performSegueWithIdentifier:#"VC1Segue" sender:self];

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.

Programmatically changing view in ECSlidingViewController

I'm trying to programmatically change the selected menu item and the displayed topViewController. In other words, I'm trying to do the same one does to change the selected tab in UITabBarController:
[self.tabBarController setSelectedIndex:2];
Therefore, according to another SO question (https://stackoverflow.com/a/20309377/1161723) I'm using this to change the displayed topViewController:
[self.slidingViewController anchorTopViewToRightAnimated:NO];
self.slidingViewController.topViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"MESettingsNavigationController"];
[self.slidingViewController resetTopViewAnimated:NO];
But it doesn't work. Well, the two first lines work correctly, displaying the menu and changing the topViewController, respectively but the last line simply doesn't hide the sidemenu so it stays there until I hide it by gesture or tapping the button. Debugging show that the last self.slidingViewController returns nil instead of instance of ECSlidingViewController. And if I skip the first and the last line, leaving only:
self.slidingViewController.topViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"MESettingsNavigationController"];
it makes the app crash.
Any idea how to change the view properly, with hiding the side menu? I'm using ECSlidingViewController 2.0.1
EDIT:
using competition block and/or creating a reference to the sliding view controller doesn't make any difference:
ECSlidingViewController *slidingViewController = self.slidingViewController;
[slidingViewController anchorTopViewToRightAnimated:NO onComplete:^{
slidingViewController.topViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"MESettingsNavigationController"];
[slidingViewController resetTopViewAnimated:NO];
}];
UPDATE 14.7.2014:
The sidemenu is not hiding only if custom view controller transitions are used. For instance in TransitionFun example.
The app crashes when leaving only the following in unwind segue handler:
- (IBAction)unwindModalView:(UIStoryboardSegue *)sender
{
ECSlidingViewController *slidingViewController = [self slidingViewController];
slidingViewController.topViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"METransitionsNavigationController"];
}
NOTE: If the last view controller (where the unwind segue is called from) is presented
by push segue instead of modal segue, the app don't crash.
You can see the hierarchy of the view controllers on the image here
(it's basically the TransitionFun example with one more VC encapsulated in NavigationVC and modally presented by segue from Settings' cell)
self.slidingViewController is a calculated variable. The calculation is done by navigating the view controller hierarchy. So, if the view controller is removed from the hierarchy as part of your changes then self.slidingViewController will cease to work.
It's also very inefficient to keep calling it. Change to:
ECSlidingViewController *slidingController = self.slidingViewController;
[slidingController anchorTopViewToRightAnimated:NO];
slidingController.topViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"MESettingsNavigationController"];
[slidingController resetTopViewAnimated:NO];
After some further investigations, I found out that the top view controller's (MyViewController) presentingViewController property returns an instance of ECSlidingViewController instead of MESettingsViewController, even though it was the MESettingsViewController that presented the MyViewController modally.
And because it's ECSlidingViewController that is actually presenting the modal view, the unwind handler wasn't working because it left the modal view on screen.
Solution:
Forget unwind segues, use the following code in the modally presented top view controller (MyViewController) to change the ECSlidingViewController's topViewController property:
- (IBAction)switchToTransitionsScreen:(id)sender
{
self.slidingViewController.topViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"METransitionsNavigationController"];
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
Or use the delegate pattern and put it into MESettingsViewController, for example.
In case someone haven't found answer, I did it in this way.
1- #import "UIViewController+ECSlidingViewController.h" to your menuViewController 2- Set stroboardID of your destinationViewController to "someID" 3- When triggering some action, in backend, use this code:
if(self.slidingViewController.currentTopViewPosition == ECSlidingViewControllerTopViewPositionCentered){
[self.slidingViewController anchorTopViewToRightAnimated:YES];
}
else{
self.slidingViewController.topViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"someID"];
[self.slidingViewController resetTopViewAnimated:YES];

Resources