Репликация стиля функции IOS Mail App Compose

Я создаю приложение на iOS 8 и собираюсь реплицировать функциональность почтового приложения iOS при создании нового сообщения электронной почты. Он показан ниже: контроллер представления компоновки представлен сверху контроллера просмотра входящих сообщений, но compose vc не занимает весь экран. Есть ли более простой способ сделать это, чем взламывать рамками контроллеров? Спасибо!

enter image description here

Ответ 1

Этот эффект может быть достигнут с помощью UIPresentationController, доступного в iOS 8. У Apple есть видео WWDC '14 по этой теме, а также некоторый полезный образец кода, найденный в нижней части этого сообщения (исходная ссылка, которую я опубликовал здесь больше не работает).

* Демонстрация называется "LookInside: контроллеры презентаций Adaptivity и пользовательские объекты аниматора". В коде Apple есть пара ошибок, которые соответствуют устаревшему использованию API, которое можно решить, изменив имя сломанного метода (в нескольких местах) на следующее:

initWithPresentedViewController:presentingViewController:

Здесь вы можете сделать репликацию анимации в почтовом приложении iOS 8. Чтобы достичь желаемого эффекта, загрузите проект, о котором я говорил выше, а затем все, что вам нужно сделать, это изменить пару вещей.

Сначала перейдите в AAPLOverlayPresentationController.m и убедитесь, что вы реализовали метод frameOfPresentedViewInContainerView. Моя выглядит примерно так:

- (CGRect)frameOfPresentedViewInContainerView
{
    CGRect containerBounds = [[self containerView] bounds];
    CGRect presentedViewFrame = CGRectZero;
    presentedViewFrame.size = CGSizeMake(containerBounds.size.width, containerBounds.size.height-40.0f);
    presentedViewFrame.origin = CGPointMake(0.0f, 40.0f);
    return presentedViewFrame;
}

Ключ в том, что вы хотите, чтобы рамка представленного элемента управления ViewController была смещена от верхней части экрана, чтобы вы могли видеть внешний вид одного контроллера вида, перекрывающего другое (без полного охвата модального объекта presentingViewController).

Затем найдите метод animateTransition: в AAPLOverlayTransitioner.m и замените код кодом ниже. Вы можете настроить несколько вещей на основе вашего собственного кода, но в целом это, похоже, решение:

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIView *fromView = [fromVC view];
    UIViewController *toVC   = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIView *toView = [toVC view];

    UIView *containerView = [transitionContext containerView];

    BOOL isPresentation = [self isPresentation];

    if(isPresentation)
    {
        [containerView addSubview:toView];
    }

    UIViewController *bottomVC = isPresentation? fromVC : toVC;
    UIView *bottomPresentingView = [bottomVC view];

    UIViewController *topVC = isPresentation? toVC : fromVC;
    UIView *topPresentedView = [topVC view];
    CGRect topPresentedFrame = [transitionContext finalFrameForViewController:topVC];
    CGRect topDismissedFrame = topPresentedFrame;
    topDismissedFrame.origin.y += topDismissedFrame.size.height;
    CGRect topInitialFrame = isPresentation ? topDismissedFrame : topPresentedFrame;
    CGRect topFinalFrame = isPresentation ? topPresentedFrame : topDismissedFrame;
    [topPresentedView setFrame:topInitialFrame];

    [UIView animateWithDuration:[self transitionDuration:transitionContext]
                          delay:0
         usingSpringWithDamping:300.0
          initialSpringVelocity:5.0
                        options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState
                     animations:^{
                         [topPresentedView setFrame:topFinalFrame];
                         CGFloat scalingFactor = [self isPresentation] ? 0.92f : 1.0f;
                         //this is the magic right here
                         bottomPresentingView.transform = CGAffineTransformScale(CGAffineTransformIdentity, scalingFactor, scalingFactor);

                    }
                     completion:^(BOOL finished){
                         if(![self isPresentation])
                         {
                             [fromView removeFromSuperview];
                         }
                        [transitionContext completeTransition:YES];
                    }];
}

В настоящее время у меня нет решения для версий ОС до iOS 8, но, пожалуйста, не стесняйтесь добавлять ответ, если вы придумаете его. Спасибо.

UPDATE:

Похоже, что ссылка выше больше не работает. Тот же проект можно найти здесь: https://developer.apple.com/library/ios/samplecode/LookInside/LookInsidePresentationControllersAdaptivityandCustomAnimatorObjects.zip

Ответ 2

Для будущих путешественников сообщение Брайана превосходно, но там довольно много информации о UIPresentationController (что облегчает эту анимацию), я бы настоятельно рекомендовал изучить. Я создал репо с рабочей версией Swift 1.2 приложения iOS Mail для создания анимации. Есть тонна связанных ресурсов, которые я также добавил в ReadMe. Пожалуйста, проверьте здесь:
https://github.com/kbpontius/iOSComposeAnimation

Ответ 3

Я не могу комментировать ответ MariSa, но я изменил свой код, чтобы он действительно работал (можно использовать некоторые очистки, но он работает для меня)

(Swift 3)

Вот ссылка снова: http://dativestudios.com/blog/2014/06/29/presentation-controllers/

В CustomPresentationController.swift:

Обновить dimmingView (чтобы он был черным и не красным, как в примере)

lazy var dimmingView :UIView = {
    let view = UIView(frame: self.containerView!.bounds)
    view.backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.5)
    view.alpha = 0.0
    return view
}()

Обновить frameOfPresentedViewInContainerView в соответствии с инструкциями MariSa:

override var frameOfPresentedViewInContainerView : CGRect {

    // We don't want the presented view to fill the whole container view, so inset it frame
    let frame = self.containerView!.bounds;
    var presentedViewFrame = CGRect.zero
    presentedViewFrame.size = CGSize(width: frame.size.width, height: frame.size.height - 40)
    presentedViewFrame.origin = CGPoint(x: 0, y: 40)

    return presentedViewFrame
}

В CustomPresentationAnimationController:

Обновить animateTransition (исходные/конечные кадры отличаются от ответа MariSa)

 func animateTransition(using transitionContext: UIViewControllerContextTransitioning)  {
    let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
    let fromView = fromVC?.view
    let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
    let toView = toVC?.view

    let containerView = transitionContext.containerView

    if isPresenting {
        containerView.addSubview(toView!)
    }

    let bottomVC = isPresenting ? fromVC : toVC
    let bottomPresentingView = bottomVC?.view

    let topVC = isPresenting ? toVC : fromVC
    let topPresentedView = topVC?.view
    var topPresentedFrame = transitionContext.finalFrame(for: topVC!)
    let topDismissedFrame = topPresentedFrame
    topPresentedFrame.origin.y -= topDismissedFrame.size.height
    let topInitialFrame = topDismissedFrame
    let topFinalFrame = isPresenting ? topPresentedFrame : topDismissedFrame
    topPresentedView?.frame = topInitialFrame

    UIView.animate(withDuration: self.transitionDuration(using: transitionContext),
                               delay: 0,
                               usingSpringWithDamping: 300.0,
                               initialSpringVelocity: 5.0,
                               options: [.allowUserInteraction, .beginFromCurrentState], //[.Alert, .Badge]
        animations: {
            topPresentedView?.frame = topFinalFrame
            let scalingFactor : CGFloat = self.isPresenting ? 0.92 : 1.0
            bottomPresentingView?.transform = CGAffineTransform.identity.scaledBy(x: scalingFactor, y: scalingFactor)

    }, completion: {
        (value: Bool) in
        if !self.isPresenting {
            fromView?.removeFromSuperview()
        }
    })


    if isPresenting {
        animatePresentationWithTransitionContext(transitionContext)
    }
    else {
        animateDismissalWithTransitionContext(transitionContext)
    }
}

Обновить animatePresentationWithTransitionContext (другая позиция кадра снова):

func animatePresentationWithTransitionContext(_ transitionContext: UIViewControllerContextTransitioning) {

    let containerView = transitionContext.containerView
    guard
        let presentedController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to),
        let presentedControllerView = transitionContext.view(forKey: UITransitionContextViewKey.to)
    else {
        return
    }

    // Position the presented view off the top of the container view
    presentedControllerView.frame = transitionContext.finalFrame(for: presentedController)
    presentedControllerView.center.y += containerView.bounds.size.height

    containerView.addSubview(presentedControllerView)

    // Animate the presented view to it final position
    UIView.animate(withDuration: self.duration, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 0.0, options: .allowUserInteraction, animations: {
        presentedControllerView.center.y -= containerView.bounds.size.height
    }, completion: {(completed: Bool) -> Void in
        transitionContext.completeTransition(completed)
    })
}

Ответ 4

Для Swift 2 вы можете следовать этому руководству: http://dativestudios.com/blog/2014/06/29/presentation-controllers/ и заменить:

override func frameOfPresentedViewInContainerView() -> CGRect {

    // We don't want the presented view to fill the whole container view, so inset it frame
    let frame = self.containerView!.bounds;
    var presentedViewFrame = CGRectZero
    presentedViewFrame.size = CGSizeMake(frame.size.width, frame.size.height - 40)
    presentedViewFrame.origin = CGPointMake(0, 40)

    return presentedViewFrame
}

и

func animateTransition(transitionContext: UIViewControllerContextTransitioning)  {
    let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)
    let fromView = fromVC?.view
    let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
    let toView = toVC?.view

    let containerView = transitionContext.containerView()

    if isPresenting {
        containerView?.addSubview(toView!)
    }

    let bottomVC = isPresenting ? fromVC : toVC
    let bottomPresentingView = bottomVC?.view

    let topVC = isPresenting ? toVC : fromVC
    let topPresentedView = topVC?.view
    var topPresentedFrame = transitionContext.finalFrameForViewController(topVC!)
    let topDismissedFrame = topPresentedFrame
    topPresentedFrame.origin.y += topDismissedFrame.size.height
    let topInitialFrame = isPresenting ? topDismissedFrame : topPresentedFrame
    let topFinalFrame = isPresenting ? topPresentedFrame : topDismissedFrame
    topPresentedView?.frame = topInitialFrame

    UIView.animateWithDuration(self.transitionDuration(transitionContext),
        delay: 0,
        usingSpringWithDamping: 300.0,
        initialSpringVelocity: 5.0,
        options: [.AllowUserInteraction, .BeginFromCurrentState], //[.Alert, .Badge]
        animations: {
            topPresentedView?.frame = topFinalFrame
            let scalingFactor : CGFloat = self.isPresenting ? 0.92 : 1.0
            bottomPresentingView?.transform = CGAffineTransformScale(CGAffineTransformIdentity, scalingFactor, scalingFactor)

        }, completion: {
            (value: Bool) in
            if !self.isPresenting {
                fromView?.removeFromSuperview()
            }
    })


    if isPresenting {
        animatePresentationWithTransitionContext(transitionContext)
    }
    else {
        animateDismissalWithTransitionContext(transitionContext)
    }
}