How To Convert A View Controller Animation Transit

2019-02-19 19:31发布

问题:

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 (note this is all objective-c)

-(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 in objective-c! (And vote it up! :) )


Back to the question at and..

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," coz you have to message presentViewController: (with animated:NO) only when you are finished.

That's the key.

Hope it helps, sorry if I misunderstood your question.



回答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:

  1. Create a custom segue to wrap the animator and make it available in your storyboard by subclassing UIStoryBoardSegue

    @interface MYCustomAnimationSegue : UIStoryboardSegue<UIViewControllerTransitioningDelegate>
    @end
    
  2. 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
    
  3. 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];
    }
    
  4. 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.