the project I am working on needs to have a custom transition between two view controllers. I have setup and implemented UIViewControllerTransitioningDelegate by overriding the method like:
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC
{
//Creation of animation
id<UIViewControllerAnimatedTransitioning> animationController;
MyAnimator *animator = [[MyAnimator alloc] init];
animator.appearing = YES;
animationController = animator;
return animator
}
And I have setup MyAnimator subclass to correctly implement UIViewControllerAnimatedTransitioning by overriding the method:
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
//custom transition animation
}
The segue which I using to trigger this animation transition is created and called programmatically, like:
UIStoryboardSegue *segue = [[UIStoryboardSegue alloc] initWithIdentifier:#"showCardDetail" source:self destination:vc];
[self prepareForSegue:segue sender:self];
[segue perform];
Reason of creating segue programmatically: I have from(transition from) and to(transition to) view controllers in separate storyboards.
Problem / Question:
I have seen many examples, all handle the custom transition using a storyboard segue like:
[self performSegueWithIdentifier:#"storyboardSegue-id" sender:self]
How should i define in the method -perform for segue created programmatically above so that the custom transition takes place.
You don't need to create a custom segue to do what you're trying to do. In fact, since you have your 2 controllers in separate storyboards, you shouldn't use a segue at all. Just instantiate your destination view controller, and use pushViewController:animated: to initiate the transition. Your first view controller should be the delegate of the navigation controller, so the delegate method you show at the top of your question gets called.
In navigationControllerAnimationControllerForOperation:fromViewController:toViewController: you can adjust source and destination viewControllers and based on this types you will see desired animation. You do not need to use prepareForSegue: here.
Related
this is my question. I have three View Controllers (VC1, VC2 and VC3). Every View Controller inherited from UINavigationControllerDelegate and I delegate my navigation controller into the viewWillAppear method in this way:
self.navigationController.delegate = self;
And the only UINavigationControllerDelegate method that I use is for transition:
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC {
if (operation == UINavigationControllerOperationPush || operation == UINavigationControllerOperationPop) {
myTransitionController *transitionController = [[myTransitionController alloc] init];
return transitionController;
}else{
return nil;
}
}
My problem is this: when I walk from VC1 to VC2 and into this controller trigger a NSNotification, I execute popViewControllerAnimated to go back the previous View Controller (VC1). This is the code:
- (void) backToRoot: (id) sender{
[self.navigationController popViewControllerAnimated:YES];
}
and the UINavigationBar show correctly its appearance (UINavigationBar without back button). Then, when I walk from VC1 to VC2 until VC3, and programatically go back until VC1 with popToRootViewController, the transition of the views (VC3-VC2-VC1) it works perfectly, but the UINavigationBar appearance not update and it shows with back button in the VC1 (which is the root View Controller). The method in the VC3 that I implemented is this:
- (void) goToRoot{
[self.navigationController popToRootViewControllerAnimated:YES];
}
I have tried with a double call of popViewControllerAnimated, I added in viewWillAppear and viewDidAppear method into VC2 and It not works neither. Anyone have idea what happens?
According to the documentation the func Pops all the view controllers on the stack except the root view controller and updates the display but I can't figure out the problem. You can try with unwind segue, is really simple.
What are Unwind segues for and how do you use them?
I am wondering if it is possible to register a Segue on a Parent View Controller to use within a Child View Controller.
Example: I have a custom AlertViewController and all of my main view controllers inherit from BaseViewController. I use this custom AlertViewController in most of these instead of the UIAlertView and I currently have to copy the AlertViewController and connect it to each subclass of BaseViewController in my storyboard.
Is there a way for me to register the segue to the AlertViewController on the BaseViewController class so that any subclass can successfully use performSegueWithIdentifier:sender:?
I have determined a way to do what I was wanting here. I just need to manually perform the segue in my BaseViewController class in the performSegueWithIdentifier:sender: method.
Example:
In my BaseViewController
- (void)performSegueWithIdentifier:(NSString *)identifier withSender:(NSObject *)sender {
if ([identifier isEqualToString:#"MyCustomSegue"]) {
MyNewViewController *MNVC = [self.storyboard instantiateViewControllerWithIdentifier:#"MyNewViewController"];
...
[self presentViewController:MNVC animated:YES completion:nil];
return;
}
[super performSegueWithIdentifier:identifier withSender:sender];
}
In my ChildViewController
[self performSegueForIdentifier:#"MyCustomSegue" sender:nil];
With this approach I no longer have to copy the MyNewViewController storyboard object and connect it to each ChildViewController in my storyboard. I do have to create the MyNewViewController within my storyboard however and make sure it's storyboardID is whatever I call it within code.
Lets say i have a ViewController with a textField and a button.
I want to use an unwind segue so i could get the information of the textField to my other viewController after i clicked the button.
I want to use the PrepareForSegue method so i could save the text from the textField in a property before i"m "unwiding".
How do i set an identifier to my segue manually? If it was a bar Button item i could use the IB to set the identifier ( to "Save" for example) and then use it. This is not the case, just a regular button.
It is not possible to create segues programmatically. They cannot exist without storyboards.
See this question.
You can easily add a segue programmatically (without using storyboards). In the source view controller header file:
#property (nonatomic, strong) UIStoryboardSegue *segue;
In the source view controller implementation file, set up the segue property accordingly:
self.segue = [UIStoryboardSegue segueWithIdentifier:[NSString stringWithFormat:#"%lu", indexPath.item] source:self destination:[PlayerViewController sharedPlayerViewController] performHandler:^{
// Insert whatever; nothing is needed for a basic segue
}];
Also, in the source view controller, add the destination view controller set up and transition in the prepareForSegue method:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// The code in this method both sets up the destination view controller and transitions to it; you could optionally do set up here only, and then transition in an overridden -(void)perform method. This way is more convenient.
[(PlayerViewController *)segue.destinationViewController setView:[PlayerView sharedPlayerView]];
PHAsset *phAsset = (PHAsset *)AppDelegate.assetsFetchResults[segue.identifier.integerValue];
[AppDelegate.cacheManager requestAVAssetForVideo:phAsset options:AppDelegate.videoOptions resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
[[PlayerView sharedPlayerView] addSubview:[PlayerControls sharedPlayerControls]];
[[PlayerControls sharedPlayerControls] setDelegate:(PlayerViewController *)segue.destinationViewController];
[[PlayerView sharedPlayerView] addSubview:[AppDelegate playerControlsLabel]];
[(PlayerViewController *)segue.destinationViewController setupPlaybackForAsset:asset completion:^{
[((AssetsCollectionViewController *)sender).navigationController presentViewController:(PlayerViewController *)segue.destinationViewController animated:TRUE completion:^{
}];
}];
}];
}
Also, add this line anywhere in the source view controller implementation file to perform the segue:
[self prepareForSegue:self.segue sender:self];
The call to the prepareForSegue method can be made inside an IBAction handler and anywhere else.
Note that the code that presents the destination view controller references a view controller that is not set up in a storyboard; the project from which this code was taken does not use storyboards for anything. BUT THAT DOESN'T MATTER because the segue code will be the same.
Also note that you do not have to modify the destination view controller in any way, shape or form. To programmatically create an unwind segue (in other words, build on the one-way segue shown here):
#implementation RootNavigationController
- (UIStoryboardSegue*)segueForUnwindingToViewController:(UIViewController *)toViewController fromViewController:(UIViewController *)fromViewController identifier:(NSString *)identifier {
return [toViewController segueForUnwindingToViewController:toViewController fromViewController:fromViewController identifier:identifier];
}
#end
Now, that's what I've done in my own app; here's what someone else has done to create a segue without using the storyboard (vs. not having a storyboard at all):
- (void)presentSignupViewController {
// Storyboard ID
UIStoryboard *modalStoryboard = [UIStoryboard storyboardWithName:#"MyStoryboard" bundle:nil];
UINavigationController *navController = [modalStoryboard instantiateViewControllerWithIdentifier:#"MySignupViewController"];
MySignupViewController *controller = [navController viewControllers][0];
// Configure your custom view controller, e.g. setting delegate
controller.delegate = self;
// Show VC
navController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
BlurryModalSegue *segue = [[BlurryModalSegue alloc] initWithIdentifier:#"SignupScene" source:self destination:navController];
[segue perform];
}
The difference is only in the use of perform and prepareForSegue: when using no storyboard at all, you have to call your transition method of choice (push... or present...) in the performForSegue method; however, the transition method is called for you by the perform method otherwise.
<iframe width="853" height="480" src="https://www.youtube.com/embed/y8Fu2PEO2zo?rel=0&showinfo=0" frameborder="0" allowfullscreen></iframe>
I have code that uses Storyboards for seques, like so:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"ShowDiagnosis"])
{
[segue.destinationViewController setHappiness:self.diagnosis];
}
...
But I want to do it programmatically. I have myViewController class and when I click on a button I want to animate and push to myUINavigationController.
How is this done programmatically?
First things first, a segue cannot be created programmatically. It is created by the storyboard runtime when it is time to perform. However you may trigger a segue, which is already defined in the interface builder, by calling performSegueWithIdentifier:.
Other than this, you can provide transitions between view controllers without segue objects, for sure. In the corresponding action method, create your view controller instance, either by allocating programmatically or instantiating from storyboard with its identifier. Then, push it to your navigation controller.
- (void)buttonClicked:(UIButton *)sender
{
MyViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"my-vc-identifier"];
// OR MyViewController *vc = [[MyViewController alloc] init];
// any setup code for *vc
[self.navigationController pushViewController:vc animated:YES];
}
First of all segue can be used only with if you have a UINavigationController that will handle the navigation (push, pop, etc).
So if you have a UINavigationController and you want to push another UIViewController on the stack without using segue then you can use pushViewController:animated: method which also has a reverse popViewControllerAnimated:. Also UINavigationController provides other methods for adding/removing UIVIewControllers, for more info check UINavigationController class reference.
I have created a custom segue that presents a view controller inside a container that is very similar with Apple's own modal view controllers (I've implemented it as a UIViewController subclass).
I'm now trying to create a custom unwind segue but there's no way I can get the method -segueForUnwindingToViewController: fromViewController: identifier: to be called.
I've also implemented -viewControllerForUnwindSegueAction: fromViewController: withSender: on my container so I can point to the correct view controller (the one that presented this modal) but then the method that should be asked for my custom unwind segue doesn't get called anywhere.
Right now, the only way for me to dismiss this modal is to do it on the -returned: method.
Did anyone could successfully do this with a custom unwind segue?
EDIT:
A little bit more code and context
My unwind view controller is configured in the storyboard, not programatically.
I have these pieces of code related to the unwind segues in my controllers:
PresenterViewController.m
I'm using a custom method to dismiss my custom modals here (-dismissModalViewControllerWithCompletionBlock:).
- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController
fromViewController:(UIViewController *)fromViewController
identifier:(NSString *)identifier {
return [[MyModalUnwindSegue alloc] initWithIdentifier:identifier
source:fromViewController
destination:toViewController];
}
-(IBAction)returned:(UIStoryboardSegue *)segue {
if ([segue.identifier isEqualToString:#"InfoUnwindSegue"]) {
[self dismissModalViewControllerWithCompletionBlock:^{}];
}
}
MyModalViewController.m
Here I only use -viewControllerForUnwindSegueAction: fromViewController: withSender: to point to the view controller that I should be unwind to.
- (UIViewController *)viewControllerForUnwindSegueAction:(SEL)action
fromViewController:(UIViewController *)fromViewController
withSender:(id)sender {
return self.myPresentingViewController;
}
The behavior I was expecting was that MyModalViewController was called to point to the view controller that should handle the unwinding and then this view controller had his -segueForUnwindingToViewController: fromViewController: identifier: method called before -returned: gets called.
Right now -segueForUnwindingToViewController: fromViewController: identifier: never gets called.
I must also say that I already tried different configurations. Everywhere I put my method to return the unwind segue it never gets called. I've read that I can subclass a navigation controller and then it gets called but I don't know how it would fit in my solution.
EDIT 2: Additional info
I've checked that MyModalViewController has his -segueForUnwindingToViewController: fromViewController: identifier: method called when I want to dismiss a regular modal view controller presented by it. This may be because he's the top most UIViewController in the hierarchy.
After checking this I've subclassed UINavigationController and used this subclass instead to contain my PresenterViewController. I was quite surprised to notice that his -segueForUnwindingToViewController: fromViewController: identifier: method is called as well.
I believe that only view controllers that serve as containers have this method called. That's something that makes little sense for me as they are not the only ones presenting other view controllers, their children are also doing so.
It's not OK for me to create logic in this subclass to choose which segue class to use as this class has no knowledge of what their children did.
Apple forums are down for the moment so no way to get their support right now. If anyone has any more info on how this works please help! I guess the lack of documentation for this is a good indicator of how unstable this still is.
To add to the answer from #Jeremy, I got unwinding from a modal UIViewController to a UIViewController contained within a UINavigationController to work properly (I.e how I expected it to) using the following within my UINavigationController subclass.
// Pass to the top-most UIViewController on the stack.
- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)
toViewController fromViewController:(UIViewController *)
fromViewController identifier:(NSString *)identifier {
UIViewController *controller = self.topViewController;
return [controller segueForUnwindingToViewController:toViewController
fromViewController:fromViewController
identifier:identifier];
}
.. and then implementing the segueForUnwindingToViewController as usual in the actual ViewController inside the UINavigationController.
This method should be declared on the parent controller. So if you're using a Navigation Controller with a custom segue, subclass UINavigationController and define this method on it. If you would rather define it on one of the UINavigationController's child views, you can override canPerformUnwindSegueAction:fromViewController:withSender on the UINavigationController to have it search the children for a handler.
If you're using an embedded view (container view), then define it on the parent view controller.
See the last 10 minutes of WWDC 2012 Session 407 - Adopting Storyboards in Your App to understand why this works!
If you're using a UINavigationController and your segue is calling pushViewController then in order to use a custom unwind segue you'll need to subclass UINavigationController and override - (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController fromViewController:(UIViewController *)fromViewController identifier:(NSString *)identifier.
Say I have a custom unwind segue called CloseDoorSegue. My UINavigationController subclass implementation might look something like:
- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController fromViewController:(UIViewController *)fromViewController identifier:(NSString *)identifier {
UIStoryboardSegue* theSegue;
if ([identifier isEqualToString:#"CloseDoor"]) {
theSegue = [CloseBookSegue segueWithIdentifier:identifier source:fromViewController destination:toViewController performHandler:^(void){}];
} else {
theSegue = [super segueForUnwindingToViewController:toViewController fromViewController:fromViewController identifier:identifier];
}
return theSegue;
}
Set your UINavigationController subclass as the navigation controller class in the storyboard. You should be good to go provided you have setup the Exit event correctly with "CloseDoor" as the identifier. Also be sure to call 'popViewControllerAnimated' in your unwind segue instead of dismiss to keep in line with UINavigationControllers push/pop mechanism.
iOS development Library
There is a discussion on iOS development Library along with this method.
- segueForUnwindingToViewController:fromViewController:identifier:
Make sure your MyModalViewController is the container role rather than a subcontroller of a container. If there is something like [anotherVC addChildViewController:myModalViewController];,you should put the segueForUnwindingToViewController method in some kind of "AnotherVC.m" file.
Discussion
If you implement a custom container view controller that
also uses segue unwinding, you must override this method. Your method
implementation should instantiate and return a custom segue object
that performs whatever animation and other steps that are necessary to
unwind the view controllers.