These 4 files are relevant to this post:
The FirstViewController has a button (not on the nav bar, a separate button), when it is pressed, the page should curl up to present FilterViewController.
FirstViewController.h
- (IBAction)searchOptions:(id)sender;
FirstViewController.m:
- (IBAction)searchOptions:(id)sender {
FilterViewController *ctrl = [[FilterViewController alloc] initWithNibName:#"FilterViewController" bundle:nil];
[UIView transitionFromView:self.view toView:ctrl.view duration:1 options:UIViewAnimationOptionTransitionCurlUp completion:nil];
[self.navigationController pushViewController:ctrl animated:NO];
}
On FilterViewController it has some UI stuff, you press a button, it saves the UI stuff and then the page curls back down to show the FirstViewController.
FilterViewController.h:
- (IBAction)backToMap:(id)sender;
FilterViewController.m:
- (IBAction)backToMap:(id)sender {
FirstViewController *ctrl = [[FirstViewController alloc] initWithNibName:#"FirstViewController" bundle:nil];
[UIView transitionFromView:self.view toView:ctrl.view duration:1 options:UIViewAnimationOptionTransitionCurlDown completion:nil];
[self.navigationController popViewControllerAnimated:YES];
}
The issue here is with the retention of UIView. How can I retain the UIView?
When I click the button on FirstViewController the animation works and the page is presented. However on FilterViewController when I click the button it crashes to the debugger with the error:
EXC_BAD_ACCESS(code=2,address=0x8)
In the output console it says: (lldb)
After the page curl up I have a stepper, when I click the stepper I get the same error in the debugger.
UPDATE: I have tracked the memory location error: http://i.imgur.com/dL18H9Z.png
Thanks.
One thing I notice is that you're pushing a view controller, then pushing another view controller with the syntax "back". This may be the issue: A nav stack is a stack. If you start with view 0, push view 1, if you want to get back to view 0 you "pop" view 1 as opposed to pushing view 0 again.
So in:
- (IBAction)backToMap:(id)sender {
FirstViewController *ctrl = [[FirstViewController alloc] initWithNibName:#"FirstViewController" bundle:nil];
[UIView transitionFromView:self.view toView:ctrl.view duration:1 options:UIViewAnimationOptionTransitionCurlDown completion:nil];
[self.navigationController popViewControllerAnimated:YES];
}
The issue here is that you try to make animation between view controllers with UIView's transition method.
According to documentation:
fromView
The starting view for the transition. By default, this view is removed
from its superview as part of the transition.
toView
The ending view for the transition. By default, this view is added
to the superview of fromView as part of the transition.
So, when you call this method, your ViewController's view replaced by another view with animation, and after on stack placed next ViewController without animation, so it's seems like all right (but your first controller's view already replaced).
But when you try to return some error behavior occurs - you replace view of controller, that will be removed.
So, i want to say, that i must be done more carefully, there several different approaches to make custom transition between viewControllers.
For example, you can watch next solution (it's similar to yours) - http://www.vigorouscoding.com/2011/05/custom-uiviewcontroller-transitions/
or
https://gist.github.com/jeksys/1507490
Related
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];
I have an UITabBarController that has 3 buttons. The second button points to ViewController1 which is connected to another view called ViewController2. After I tap a button in ViewController2 I programmatically present ViewController1 again, that works perfect except one thing. After I "arrived" to ViewController1 the tab bar disappears.
I'm using this method to navigate back to ViewController1. (exactly I navigate to its navigation controller, but already tried with the view)
- (void)presentViewControllerAnimated:(BOOL)animated {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"storyboard" bundle:nil];
UINavigationController *firstViewNavigationController = [storyboard instantiateViewControllerWithIdentifier:#"destination"];
[self presentViewController:firstViewNavigationController animated:animated completion:nil];
}
I call here the first method
- (void)didTapButton:(id)sender {
UIButton *button = (UIButton *)sender;
CGPoint pointInSuperview = [button.superview convertPoint:button.center toView:self.tableView];
[self presentViewControllerAnimated:YES];
}
This method hides the tab bar in the ViewController2, I already tried without it, therefore there is no problem with it.
-(BOOL)hidesBottomBarWhenPushed
{
return YES;
}
I can't figure out why this thing happens, I think it's a fair solution, that worked well for a several times when I needed to present views. I've read it can happen with segues, but I'm doing it with code without segues.
Actually your code works right. There should not be tab bar when you present FirstViewController from SecondViewController. Because when you call instantiateViewControllerWithIdentifier its basically creates a new instance of that view controller, and of course, there is no tab bar.
The right way to go back to your first view controller is to pop SecondViewController (or dismiss it, if it presented modally). So your final code should be like this
- (void)didTapButton:(id)sender {
// If this view controller (i.e. SecondViewController) was pushed, like in your case, then
[self.navigationController popViewControllerAnimated:YES];
// If this view controller was presented modally, then
// [self dismissViewControllerAnimated:YES completion:nil];
}
And of course, your view controller hierarchy in storyboard must be like this:
-- UINavigationController -> FirstViewController -> SecondViewController
|
->UITabBarController____|
-...
-...
I've tried the same and got the same result.
My solution was simple, on the push do this :
UINavigationController *firstViewNavigationController = [storyboard instantiateViewControllerWithIdentifier:#"destination"];
firstViewNavigationController.hidesBottomBarWhenPushed = true; // Insert this and set it to what you want to do
[self presentViewController:firstViewNavigationController animated:animated completion:nil];
and then remove your
-(BOOL)hidesBottomBarWhenPushed
{
return YES;
}
I am having major memory management issues. After small use of the program it will crash running out of memory. I have finally found the cause, every time I create a new ViewController rather than accessing the instance, I am creating a new instance.
So app loads and instantiates the FirstViewController. You click a button which instantiates FilterViewController. From here when going back to FirstViewController I am creating a new instance of this as follows:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName
:#"MainStoryboard" bundle:nil];
FirstViewController *fvc = [storyboard
instantiateViewControllerWithIdentifier:#"FirstViewController"];
fvc.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
And repeat process. Any way of presenting the view controller without re-instantiating it? I am close to submitting the app (tomorrow hopefully) so I need to try get this sorted. Thanks!
Here is the presentation of the ViewController.
[self presentViewController:fvc animated:YES completion:nil];
Presenting FilterViewController from FirstViewController
- (IBAction)searchOptions:(id)sender {
FilterViewController *ctrl = [[FilterViewController alloc] init];
[UIView transitionFromView:self.view toView:ctrl.view duration:1 options:UIViewAnimationOptionTransitionCurlUp completion:nil];
self.filterViewController = ctrl;
[self.navigationController pushViewController:self.filterViewController animated:NO];
}
If you're using presentViewController, you get back to the previous view by calling [self dismissViewControllerAnimated:YES];. You would do that in the method where you're currently creating the new controller.
If you are pushing into a navigation controller you would pop from the navigation controller: [self.navigationController popViewControllerAnimated:YES];.
Based on your last update it seems like you don't have a navigation controller and you're just adding the view as a subview and storing the filter view controller. That makes life more complicated really and the correct way to remove it is to setup a delegate relationship so that the filter view controller calls back to the first view controller when it's done and the first controller then transitions the views and nil's the reference.
If you can, change to use a navigation controller properly. You already have half the code, but the first view controller seems to not be in a navigation controller. If you use a nav controller life will be easy...
I am really confused regarding few things in UIViewController, I have already read the View Controller Programming Guide and searched lot on the Internet but still confused.
When I want to jump or switch from firstVC to secondVC how many types of methods are available? I am listing which I know:
UINavigationController
UITabBarController
presentModalViewController:
Add secondVC to root view
If secondVC is added to root view then how firstVC object will be released?
Is it a good practice to add every view I want to jump/switch to root view?
transitionFromView:
I dont understand this portion from Apple doc:
This method modifies the views in their view hierarchy only. It does
not modify your application’s view controllers in any way. For
example, if you use this method to change the root view displayed by a
view controller, it is your responsibility to update the view
controller appropriately to handle the change.
If I do this:
secondViewController *sVc = [[secondViewController alloc]init];
[transitionFromView:self.view toView:sVc.view...
Still viewDidLoad:, viewWillAppear:, viewDidAppear: are working fine: I don't need to call them. So why did Apple say this:
it is your responsibility to update the view controller appropriately to handle the change.
Are there any other methods available?
Actually the standard methods used are :
1) Using NavigationController
//push the another VC to the stack
[self.navigationController pushViewController:anotherVC animated:YES];
//remove it from the stack
[self.navigationController popViewControllerAnimated:NO];
//or presenting another VC from current navigationController
[self.navigationController presentViewController:anotherVC animated:YES completion:nil];
//dismiss it
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
2) Presenting the VC
//presenting another VC from current VC
[self presentViewController:anotherVC animated:YES completion:nil
//dismiss it
[self dismissViewControllerAnimated:YES completion:nil];
Never use the method you described in points 4. It's not a good practice to change root view controller's dynamically. window's root VC is usually defined on applicationdidfinishlaunchingwithoptions after that it shouldn't be changed , if you are to follow apple standards.
Example for transitionFromView:toView
-(IBAction) anAction:(id) sender {
// assume view1 and view2 are some subviews of self.view
// view1 will be replaced with view2 in the view hierarchy
[UIView transitionFromView:view1
toView:view2
duration:0.5
options:UIViewAnimationOptionTransitionFlipFromLeft
completion:^(BOOL finished){
/* do something on animation completion */
}];
}
}
I'm using a custom segue which looks like this:
#implementation ModalPushSegue
- (void)perform {
UIViewController *fromController = self.sourceViewController;
UIViewController *toController = self.destinationViewController;
UIView *fromView = fromController.view;
UIView *toView = toController.view;
CGPoint centerStage = toView.centerStage;
toView.center = toView.rightStage;
[fromView.window addSubview:toView];
[fromController addChildViewController:toController];
[UIView transitionWithView:toView
duration:0.5 options:0
animations:^{
toView.center = centerStage;
}
completion:nil];
}
This works well in that the view is slide on from the right as expected and the controller is added to the controller hierarchy.
But later in the added controller I do this:
[self presentViewController:anotherController animated:YES completion:nil];
I would expect this to slide the new controller's view up the screen ala modal style. But what happens instead is the the new view doesn't appear. And when I later remove this controller, it's view flashes up and slides off the screen, leaving a black background instead of the view that was originally there.
I've been playing around with this for a while and if I change the code to
//[self presentViewController:oauthController animated:YES completion:nil];
[self.view addSubview:oauthController.view];
[self addChildViewController:oauthController];
Then the view appears as expected, although not resized.
My problem appears to be with the way that the segues setup the hierarchy vs the way that presentViewController does things. I've done lots of reading and searching but so far have not been able to get a clear picture of exactly what is going on.
I've also played around with using presentViewController in the segue but instead of laying the new view over the old one, the screen goes black and the new view then slides on.
Any help appreciated.
Set a Storyboard ID on your destination view controller (in the storyboard), then try the following code:
AnotherController *viewC = [self.storyboard instantiateViewControllerWithIdentifier:#"AnotherController"];
ModalPushSegue *segue = [[ZHCustomSegue alloc] initWithIdentifier:#"coolSegueName" source:self destination:viewC];
[segue perform];