IOS/Objective-C: Possible to Use Custom Segue in Modal Transition in Absence of a Storyboard Segue?

为君一笑 提交于 2019-12-02 09:59:52

It seems like you're trying to accomplish many custom animation transitions within your application, so I'd recommend you roll with your own animator classes instead of trying to create custom segues. Similar to the example I posted to you previous question (linked in my comment above), you're going to want to setup some sort of delegate (in the previous example it was a UINavigationControllerDelegate) to handle determining which custom animator classes to use for presenting/dismissing (in the previous example it was for pushing/popping) certain view controllers:

Here's an example of what I mean:

ViewController.m (using this as the UIViewControllerTransitioningDelegate):

#import "ViewController.h"
#import "SlideDownAnimator.h"
#import "SlideUpAnimator.h"

@interface ViewController () <UIViewControllerTransitioningDelegate>

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

- (IBAction)_presentCustomDownward:(id)sender {
    // setup a fake view controller we're going to present
    UIViewController *fakeSecondViewController = [[UIViewController alloc] init];
    fakeSecondViewController.view.backgroundColor = [UIColor blueColor];
    // make sure to set the desination view controller's transition delegate
    // this doesn't have to be set to self here, you can set it anything that will implement: UIViewControllerTransitioningDelegate
    fakeSecondViewController.transitioningDelegate = self;
    // when we call present on the view controller it asks the transitioningDelegate for an animation coordinator which we're going to provide below
    [self.navigationController presentViewController:fakeSecondViewController animated:YES completion:^{
        // dismis after a couple seconds to show what the custom dismiss looks like (obviously this is just for the example code, you will handle your own dismissal)
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self.navigationController dismissViewControllerAnimated:YES completion:nil];
        });
    }];
}

// delegate call for presentation
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented
                                                                  presentingController:(UIViewController *)presenting
                                                                      sourceController:(UIViewController *)source {

    // return our own custom Animator class (which adheres to UIViewControllerAnimatedTransitioning protocol)
    return [[SlideDownAnimator alloc] init];
}

// delegate call for dismissal
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
    // return our own custom Animator class (which adheres to UIViewControllerAnimatedTransitioning protocol)
    return [[SlideUpAnimator alloc] init];
}

SlideDownAnimator.h (Custom Down Animator):

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface SlideDownAnimator : NSObject <UIViewControllerAnimatedTransitioning>

@end

NS_ASSUME_NONNULL_END

SlideDownAnimator.m:

#import "SlideDownAnimator.h"

@implementation SlideDownAnimator

- (NSTimeInterval)transitionDuration:(nullable id<UIViewControllerContextTransitioning>)transitionContext {
    return 0.4f;
}

- (void)animateTransition:(nonnull id<UIViewControllerContextTransitioning>)transitionContext {
    // grab the toViewController (the vc being presented)
    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    // manually add it to our container view
    [[transitionContext containerView] addSubview:toViewController.view];
    // get the frame so we can do some of our own animations on it
    CGRect toVCFrame = toViewController.view.frame;
    CGFloat toVCHeight = toVCFrame.size.height;
    // offset the y coordiante by it's height so that it's completely above our current screen
    toVCFrame.origin.y = -toVCHeight;
    // set the initial frame so it's above our screen
    [toViewController.view setFrame:toVCFrame];

    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        // animate the y position to 0 so it slides down
        CGRect finalVCFrame = toViewController.view.frame;
        finalVCFrame.origin.y = 0;
        [toViewController.view setFrame:finalVCFrame];
    } completion:^(BOOL finished) {
        // make sure to call this so we'll call back to our presentViewdController's completion block
        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
    }];
}

@end

My guess is that you're going to want a custom slide up animation upon dismissal as well, so you'll want one more animator which implements that custom transition:

SlideUpAnimator.h:

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface SlideUpAnimator : NSObject <UIViewControllerAnimatedTransitioning>

@end

NS_ASSUME_NONNULL_END

SlideUpAnimator.m:

#import "SlideUpAnimator.h"

@implementation SlideUpAnimator

- (NSTimeInterval)transitionDuration:(nullable id<UIViewControllerContextTransitioning>)transitionContext {
    return 0.4f;

}

- (void)animateTransition:(nonnull id<UIViewControllerContextTransitioning>)transitionContext {
    // grab our fromViewController (the vc being dismissed)
    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    // and our toViewController (the vc that will be shown after dismissal)
    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    // add our toViewController as a subview of our container view (make sure to add it beneath the dismissing view controller so we can let it complete it's animation first)
    [[transitionContext containerView] insertSubview:toViewController.view belowSubview:fromViewController.view];
    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        // push the view back up above the screen
        CGRect fromVCFrame = fromViewController.view.frame;
        fromVCFrame.origin.y = -fromVCFrame.size.height;
        [fromViewController.view setFrame:fromVCFrame];
    } completion:^(BOOL finished) {
        // make sure to call this so we'll call back to our presentViewdController's completion block
        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
    }];

}

@end

The complete animation will end up looking like this:

Custom_Downward_Animation_Transition

Here's some links that should be helpful:

https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/CustomizingtheTransitionAnimations.html

https://developer.apple.com/documentation/uikit/uiviewcontrollertransitioningdelegate?language=objc

https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning?language=objc

Edit:

As far as your other question as to where to put this in your own code: This is highly dependent on your application structure, so without seeing more context of the classes you're attempting to use them on, it's hard to say the best approach.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!