如何像这样在 IOS 中从底部呈现模态 UIViewController 对话框(附图片)

how to present modal UIViewController dialog from bottom in IOS like this (image attached)

如何呈现模态的 UIViewController(从故事板说),并从呈现视图控制器的底部向上滑动。要求是:

查看Apple documentation

Presenting a View Controller Using Custom Animations

To present a view controller using custom animations, do the following in an action method of your existing view controllers:

Create the view controller that you want to present. Create your custom transitioning delegate object and assign it to the view controller’s transitioningDelegate property. The methods of your transitioning delegate should create and return your custom animator objects when asked. Call the presentViewController:animated:completion: method to present the view controller. When you call the presentViewController:animated:completion: method, UIKit initiates the presentation process. Presentations start during the next run loop iteration and continue until your custom animator calls the completeTransition: method. Interactive transitions allow you to process touch events while the transition is ongoing, but noninteractive transitions run for the duration specified by the animator object.

编辑:

您的替代选择是创建一个具有自定义大小的容器,并为添加为 UIViewController 的子视图的 UIViewController 设置动画:

[self addChildViewController:content];
content.view.frame = [self frameForContentController];
[self.view addSubview:self.currentClientView];
[content didMoveToParentViewController:self];

我不使用故事板,所以我都写出来了。您可以将其复制粘贴到一个全新的项目中,然后 运行 看看它是否正常工作。

您的 PresentingController 需要符合两点。第一个协议是:UIViewControllerTransitioningDelegate 它允许控制器提供一个自定义的演示者(在我们下面的例子中就是它自己)。无论您 return 这里(无论是自身还是其他对象)都需要符合 UIViewControllerAnimatedTransitioning 并提供自定义动画。对于这个 self-contained 示例,我选择当前 viewController 作为演示者和动画师。

接下来,它需要遵守协议:UIViewControllerAnimatedTransitioning 为任何显示或关闭控制器提供自定义动画。

换句话说,当我们呈现或关闭 viewController 时,将调用 UIViewControllerAnimatedTransitioning 协议中的 animateTransition 来确定子控制器应如何设置动画进入视角或从视角关闭view-port.

示例(带过渡动画):

//
//  ViewController.m
//  SO
//
//  Created by Brandon T on 2017-01-23.
//  Copyright © 2017 XIO. All rights reserved.
//

#import "ViewController.h"


//Some view controller that will be presented modally.
//I have coloured it red.
@interface ModalController : UIViewController
@end

@implementation ModalController
- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor redColor];
}
@end



//The view controller that will present or dismiss some other view controller modally.
//I have coloured it white.
@interface ViewController () <UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning>
@property (nonatomic, assign) bool presentingModalController;
@property (nonnull, nonatomic, strong) ModalController *modalController;
@property (nonnull, nonatomic, strong) UIButton *button;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor whiteColor];

    //For this example, I add a button to present and dismiss the redViewController.
    self.button = [[UIButton alloc] initWithFrame:CGRectMake(15, self.view.center.y - 100, self.view.frame.size.width - 30, 45)];
    [self.button setTitle:@"Present" forState:UIControlStateNormal];
    [self.button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [self.button setBackgroundColor:[UIColor lightGrayColor]];
    [self.button.layer setCornerRadius:5.0];
    [self.button addTarget:self action:@selector(onButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:self.button];


    //Create the redViewController and set its transitioning delegate to self (this controller will be providing the animation and presenter).
    //We also set the style to OverFullScreen because we don't want this controller to disappear.
    //When a view controller is presented, the one that presented it usually disappears or gets removed from the hierarchy until the child is dismissed. In the case of alerts, or controllers that need to display OVER the current one, we need to set the modalPresentationStyle.
    self.modalController = [[ModalController alloc] init];
    self.modalController.transitioningDelegate = self;
    self.modalController.modalPresentationStyle = UIModalPresentationOverFullScreen;
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

- (void)onButtonClicked:(UIButton *)button {
    if (self.modalController.view.window == nil) {
        [self presentViewController:self.modalController animated:YES completion:nil];
        [self.button setTitle:@"Dismiss" forState:UIControlStateNormal];

        //not a good idea but meh.. I need to keep this example short.
        [self.view.window addSubview:self.button];
    }
    else {
        [self.modalController dismissViewControllerAnimated:YES completion:nil];
        [self.button setTitle:@"Present" forState:UIControlStateNormal];
        [self.view addSubview:self.button];
    }
}


//Custom Animations and Presenters.
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
    self.presentingModalController = true; //We are presenting the controller.
    return self; //Who is animating it? We are.
}

- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
    self.presentingModalController = false; //We are dismissing the view controller.
    return self; //Who animated it? We did.
}

//How fast should it present? I chose 0.5 seconds.
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext {
    return 0.5;
}

//The actual animation code.
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
    if (self.presentingModalController) { 
        //If we are presenting, we need to add the new controller's view as a sub-view.

        UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

        //We need a starting frame for the animation.
        CGRect startingFrame = transitionContext.containerView.bounds;
        startingFrame.origin.y = startingFrame.size.height; //Starts from the bottom of the parent.
        startingFrame.size.height = 100; //Has a height of 100.

        //We need an end frame for the animation.
        CGRect finalFrame = transitionContext.containerView.bounds;
        finalFrame.origin.y = finalFrame.size.height - 100; //100 from the bottom of the parent.
        finalFrame.size.height = 100; //Present with a size of 100 height.

        //Add the controller's view as a subview of the context.
        [transitionContext.containerView addSubview:toViewController.view];
        [toViewController.view setFrame:startingFrame];

        //Start animating from "startFrame" --> "endFrame" with 0.5 seconds duration and no delay. I chose easeIn style.
        [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
            [toViewController.view setFrame:finalFrame];
        } completion:^(BOOL finished) {
            //We are finished animating, complete the transition!
            [transitionContext completeTransition:YES];
        }];
    }
    else {
        //If we are dismissing the view controller, we need to animate it down the screen and then remove its view from the context.

        UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

        //We only need one frame. This is the first frame. We are animating from "endFrame" --> "startingFrame" (backwards/reverse animation).
        CGRect startingFrame = transitionContext.containerView.bounds;
        startingFrame.origin.y = startingFrame.size.height; //Starts from the bottom of the parent.
        startingFrame.size.height = 100; //Has a height of 100.


        //Start the animation with 0.5 seconds duration and I chose easeOut style.
        [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
            [fromViewController.view setFrame:startingFrame];
        } completion:^(BOOL finished) {

            //Remove the view controller's view from the context and complete the transition!
            [fromViewController.view removeFromSuperview];
            [transitionContext completeTransition:YES];
        }];
    }
}
@end

示例(没有过渡动画):

//
//  ViewController.m
//  SO2
//
//  Created by Brandon Thomas on 2017-01-23.
//  Copyright © 2017 XIO. All rights reserved.
//

#import "ViewController.h"


@interface ModalController : UIViewController
@end

@implementation ModalController
- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor redColor];
}
@end



@interface ViewController ()
@property (nonatomic, assign) bool presentingModalController;
@property (nonnull, nonatomic, strong) ModalController *modalController;
@property (nonnull, nonatomic, strong) UIButton *button;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor whiteColor];

    self.button = [[UIButton alloc] initWithFrame:CGRectMake(15, self.view.center.y - 100, self.view.frame.size.width - 30, 45)];
    [self.button setTitle:@"Present" forState:UIControlStateNormal];
    [self.button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [self.button setBackgroundColor:[UIColor lightGrayColor]];
    [self.button.layer setCornerRadius:5.0];
    [self.button addTarget:self action:@selector(onButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:self.button];


    self.modalController = [[ModalController alloc] init];
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

- (void)onButtonClicked:(UIButton *)button {
    if (self.modalController.view.window == nil) {
        //Present
        CGRect startingFrame = self.view.bounds;
        startingFrame.origin.y = startingFrame.size.height; //Starts from the bottom of the parent.
        startingFrame.size.height = 100; //Has a height of 100.

        CGRect finalFrame = self.view.bounds;
        finalFrame.origin.y = finalFrame.size.height - 100; //100 from the bottom of the parent.
        finalFrame.size.height = 100; //Present with a size of 100 height.

        [self.modalController.view setFrame:startingFrame];

        [self.modalController willMoveToParentViewController:self];
        [self addChildViewController:self.modalController];
        [self.view addSubview:self.modalController.view];
        [self.modalController didMoveToParentViewController:self];

        [UIView animateWithDuration:0.5 animations:^{
            [self.modalController.view setFrame:finalFrame];
        } completion:^(BOOL finished) {

        }];
    }
    else {
        //Dismiss
        CGRect startingFrame = self.view.bounds;
        startingFrame.origin.y = startingFrame.size.height; //Starts from the bottom of the parent.
        startingFrame.size.height = 100; //Has a height of 100.

        [UIView animateWithDuration:0.5 animations:^{
            [self.modalController.view setFrame:startingFrame];
        } completion:^(BOOL finished) {
            [self.modalController.view removeFromSuperview];
            [self.modalController willMoveToParentViewController:nil];
            [self.modalController removeFromParentViewController];
            [self.modalController didMoveToParentViewController:nil];
        }];
    }
}
@end