问题
From what I can gather it is not possible to use a View Controller animation as a Segue transition. Currently I have a set of View Controller animations in an existing project that create animated transitions between View Controllers. For example:
Modal Animation .h File:
//
// MVModalDismissAnimation.h
//
#import "MVBaseAnimation.h"
@interface MVModalAnimation : MVBaseAnimation
@end
Modal Animation .m File:
//
// MVModalDismissAnimation.m
//
#import "MVModalAnimation.h"
@implementation MVModalAnimation {
UIView *_coverView;
NSArray *_constraints;
}
#pragma mark - Animated Transitioning
-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
//The view controller's view that is presenting the modal view
UIView *containerView = [transitionContext containerView];
if (self.type == AnimationTypePresent) {
//The modal view itself
UIView *modalView = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view;
//View to darken the area behind the modal view
if (!_coverView) {
_coverView = [[UIView alloc] initWithFrame:containerView.frame];
_coverView.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.6];
_coverView.alpha = 0.0;
} else _coverView.frame = containerView.frame;
[containerView addSubview:_coverView];
//Using autolayout to position the modal view
modalView.translatesAutoresizingMaskIntoConstraints = NO;
[containerView addSubview:modalView];
NSDictionary *views = NSDictionaryOfVariableBindings(containerView, modalView);
_constraints = [[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-30-[modalView]-30-|" options:0 metrics:nil views:views] arrayByAddingObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-30-[modalView]-30-|" options:0 metrics:nil views:views]];
[containerView addConstraints:_constraints];
//Move off of the screen so we can slide it up
CGRect endFrame = modalView.frame;
modalView.frame = CGRectMake(endFrame.origin.x, containerView.frame.size.height, endFrame.size.width, endFrame.size.height);
[containerView bringSubviewToFront:modalView];
//Animate using spring animation
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 usingSpringWithDamping:0.8 initialSpringVelocity:1.0 options:0 animations:^{
modalView.frame = endFrame;
_coverView.alpha = 1.0;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
} else if (self.type == AnimationTypeDismiss) {
//The modal view itself
UIView *modalView = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey].view;
//Grab a snapshot of the modal view for animating
UIView *snapshot = [modalView snapshotViewAfterScreenUpdates:NO];
snapshot.frame = modalView.frame;
[containerView addSubview:snapshot];
[containerView bringSubviewToFront:snapshot];
[modalView removeFromSuperview];
//Set the snapshot's anchor point for CG transform
CGRect originalFrame = snapshot.frame;
snapshot.layer.anchorPoint = CGPointMake(0.0, 1.0);
snapshot.frame = originalFrame;
//Animate using keyframe animation
[UIView animateKeyframesWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:0 animations:^{
[UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.15 animations:^{
//90 degrees (clockwise)
snapshot.transform = CGAffineTransformMakeRotation(M_PI * -1.5);
}];
[UIView addKeyframeWithRelativeStartTime:0.15 relativeDuration:0.10 animations:^{
//180 degrees
snapshot.transform = CGAffineTransformMakeRotation(M_PI * 1.0);
}];
[UIView addKeyframeWithRelativeStartTime:0.25 relativeDuration:0.20 animations:^{
//Swing past, ~225 degrees
snapshot.transform = CGAffineTransformMakeRotation(M_PI * 1.3);
}];
[UIView addKeyframeWithRelativeStartTime:0.45 relativeDuration:0.20 animations:^{
//Swing back, ~140 degrees
snapshot.transform = CGAffineTransformMakeRotation(M_PI * 0.8);
}];
[UIView addKeyframeWithRelativeStartTime:0.65 relativeDuration:0.35 animations:^{
//Spin and fall off the corner
//Fade out the cover view since it is the last step
CGAffineTransform shift = CGAffineTransformMakeTranslation(180.0, 0.0);
CGAffineTransform rotate = CGAffineTransformMakeRotation(M_PI * 0.3);
snapshot.transform = CGAffineTransformConcat(shift, rotate);
_coverView.alpha = 0.0;
}];
} completion:^(BOOL finished) {
[_coverView removeFromSuperview];
[containerView removeConstraints:_constraints];
[transitionContext completeTransition:YES];
}];
}
}
-(NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
if (self.type == AnimationTypePresent) return 1.0;
else if (self.type == AnimationTypeDismiss) return 1.75;
else return [super transitionDuration:transitionContext];
}
@end
The above slides a modal view controller up from the bottom.
I would now like to convert this to work with storyboards and segues.
I have four other types of animation that I want to convert so is there a straightforward way to make the above class work with segue transitions?
回答1:
I may be misunderstanding you, but here's an example of a segue to use with a custom segue...
@interface TestSegue : UIStoryboardSegue
@end
@implementation TestSegue
-(void)perform
{
UIView *sv = ((UIViewController *)self.sourceViewController).view;
UIView *dv = ((UIViewController *)self.destinationViewController).view;
UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
[window insertSubview:dv aboveSubview:sv];
[dv enterRight:0.1 then:^
{
[self.sourceViewController
presentViewController:self.destinationViewController
animated:NO completion:nil];
}];
}
@end
Notice my routine "enterRight:" is just a category on UIView
handy tutorial to use custom segues ... http://www.bencz.com/hacks/2014/02/07/custom-ios-segues-in-xcode-5/
An example of my category on UIView, which is just a movement animation of the view...
note that you do and must have an "after" block, to be able to use it in a custom segue.
Note - here's a full explanation of making a block as a property for anyone reading not familiar with that https://stackoverflow.com/a/20760583/294884
-(void)enterRight:(float)delay then:(void(^)(void))after
{
CGPoint moveTo = self.center;
CGPoint moveFrom = self.center;
CGFloat simpleOffscreen = [UIScreen mainScreen].bounds.size.width;
moveFrom.x = moveFrom.x + simpleOffscreen;
self.center = moveFrom;
self.hidden = NO;
[UIView animateWithDuration:0.5
delay:delay
usingSpringWithDamping:0.4
initialSpringVelocity:0.1
options:0
animations:^
{
self.center = moveTo;
}
completion:^(BOOL finished)
{
if (after) after();
}
];
}
So, if you're beginning with categories...
In Xcode click to add a new file, then choose category. There are two boxes.
So, a category is "on" some class. In this case it is "on" UIView. In the bottom box, type in UIView.
In the top box, simply put some convenient, short, name. In fact the name used is unimportant. For example, typical choices are "Utility" "Helpers" "Tricks" "Anime" or similar.
Now go to the .h file and add this
-(void)enterRight:(float)delay then:(void(^)(void))after;
Now go to the .m file and add exactly the function above. You can now use that function anywhere in your code. Here's an example of using it
-(void)viewDidLoad
{
[super viewDidLoad];
[PFAnalytics trackEvent:@"harvestFBPage"];
[self.slideOff showMessage:@"Next step..."];
[self.slideOff enterRight:0.0 then:nil];
}
So, with any UIView, you can go [anyUIView enterRight...];
which is great. In the example self.slideOff is a UIView.
Note that in that example, there is "nothing to do afterwards." Here's an example with "something to do afterwards."
-(void)viewDidLoad
{
[super viewDidLoad];
[PFAnalytics trackEvent:@"harvestFBPage"];
[self.slideOff showMessage:@"Next step..."];
[self.slideOff enterRight:0.0 then:^
{
NSLog(@"do this afterwards!");
self.slideOff.alpha = 0.5;
etc;
etc;
}];
}
Hope that helps with categories. Don't forget to go here https://stackoverflow.com/a/20760583/294884 if you're less familiar with blocks as arguments.
Back to the question at hand.
It would not be possible to use any type of animation, in a custom segue, unless it includes a concept of "run this block when finished," because you have to message presentViewController: (with animated:NO) only when you are finished.
Assuming I have understood your question, that's the key.
回答2:
A UIStoryBoardSegue
is an object that wraps a transition animation and shows up magically in Storyboards. It is possible to use a custom viewcontroller Transition Animation in a custom segue.
Assuming you already have a custom animator object that implements the UIViewControllerAnimatedTransitioning
protocol (like above), you need to:
Create a custom segue to wrap the animator and make it available in your storyboard by subclassing
UIStoryBoardSegue
@interface MYCustomAnimationSegue : UIStoryboardSegue<UIViewControllerTransitioningDelegate> @end
Implement the
perform:
method, setting it up as the transitioningdelegate (for a modal transition) or as the navigationController's delegate (for a push or replace transition)@implementation MYCustomAnimationSegue - (void)perform { UIViewController *sourceVC = (UIViewController *)self.sourceViewController; UIViewController *destVC = (UIViewController *)self.destinationViewController; //Set the transitioningdelegate (for a modal transition) destVC.transitioningDelegate = self; //strong property, necessary to retain the Segue after perform: is finished //only needed if you want the customSegue to be the transitioningDelegate sourceVC.customSegue = self; //Set the modalPresentationStyle if (&UITransitionContextFromViewKey) { //iOS8 destVC.modalPresentationStyle = UIModalPresentationFullScreen; } else { //iOS7 destVC.modalPresentationStyle = UIModalPresentationCustom; } //Call the presentation [sourceVC presentViewController:destVC animated:YES completion:^{ //release ourselves again sourceVC.customSegue = nil; }]; } @end
Implement the
UIViewControllerTransitioningDelegate
method to returning the custom animator object- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source { return [[MyCustomAnimator alloc] init]; }
Your custom segue class will now show up in Interface Builder when you ctrl-drag from one viewcontroller to another.
Before you get to excited, there is one caveat: this only works in one direction. To make it work for Unwind Segues, your viewcontroller class needs to implement the necessary methods.
来源:https://stackoverflow.com/questions/19755208/how-to-convert-a-view-controller-animation-transition-to-a-segue-animation-trans