【问题标题】:Replicating the style of the iOS Mail App's Compose Function复制 iOS Mail App 的 Compose Function 的风格
【发布时间】:2015-03-12 18:19:25
【问题描述】:

我正在 iOS 8 上构建应用程序,并希望在创建新电子邮件/消息时复制 iOS 邮件应用程序的功能。如下图所示:compose 视图控制器呈现在收件箱视图控制器之上,但是 compose vc 并没有占据整个屏幕。有没有比修改视图控制器的框架更简单的方法呢?谢谢!

【问题讨论】:

    标签: ios objective-c email


    【解决方案1】:

    这个效果可以通过UIPresentationController 实现,在 iOS 8 中可用。Apple 有一个关于这个主题的 WWDC '14 视频以及在这篇文章底部找到的一些有用的示例代码(我在这里发布的原始链接不再有效)。

    *该演示名为“LookInside:Presentation Controllers Adaptivity and Custom Animator Objects”。 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;
    }
    

    关键是您希望presentingViewController 的框架从屏幕顶部偏移,这样您就可以实现一个视图控制器与另一个重叠的外观(无需让模态完全覆盖presentingViewController)。

    接下来,在AAPLOverlayTransitioner.m中找到animateTransition:方法,将代码替换为下面的代码。您可能想根据自己的代码调整一些东西,但总的来说,这似乎是解决方案:

    - (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 之前的操作系统版本的解决方案,但如果您想出一个答案,请随时添加。谢谢。

    更新(03/2016):

    上面的链接似乎不再有效。可以在此处找到相同的项目:https://developer.apple.com/library/ios/samplecode/LookInside/LookInsidePresentationControllersAdaptivityandCustomAnimatorObjects.zip

    更新(12/2019):

    当在 iOS 13 上以模态方式呈现视图控制器时,这种转换样式现在似乎是默认行为。我对以前版本的操作系统不持肯定态度,但如果您想在自己的应用程序中复制此功能/转换无需编写大量代码,您既可以按原样在 iOS 13 上呈现视图控制器,也可以将该视图控制器的 modalPresentationStyle 设置为 .pageSheet,然后呈现它。

    【讨论】:

    • 这篇文章很棒!感谢分享。但是,我似乎找不到isPresentation 的派生位置。至少在 Swift 中,self.isPresentation 没有超类方法调用。我真正要确定的是他们是否将其提供给您,或者它是否是您在其他地方确定的自定义值?
    • 谢谢,伙计。这是一个很好的问题。让我检查一下何时返回源代码并更新此评论并为您提供一些帮助。
    • 我找到了设置的地方。它不是本机属性,而是包含在您上面提到的示例应用程序中:“LookInside ...”。它设置在 AAPLOverlayTransitioningDelegate 文件的第 46 行。再次感谢您的帮助,布赖恩。
    • 是的,就是这样。刚刚检查了我的代码。不客气。
    • @NicolasMiari 您可能需要一个容器视图控制器来容纳撰写 VC 以及带有滑动手势或类似东西的背景视图控制器,以促进移动/交互性。正如您所指出的,我的解决方案纯粹是视觉上的。
    【解决方案2】:

    更新 - 2018 年 6 月:

    @ChristopherSwasey 更新了 repo 以与 Swift 4 兼容。感谢 Christopher!


    对于未来的旅行者来说,Brian 的帖子非常棒,但是我强烈建议您查看有关 UIPresentationController(有助于制作此动画)的大量重要信息。我创建了一个包含 iOS Mail 应用程序撰写动画的工作 Swift 1.2 版本的存储库。我还在自述文件中放入了大量相关资源。请在此处查看:
    https://github.com/kbpontius/iOSComposeAnimation

    【讨论】:

    • 当设备在模式关闭之前旋转到横向时,此解决方案也会出错。
    • @trapper 老实说,这个解决方案旨在提供用于构建模态的基本前提。诚然,如果它得到维护,那将是我要修复的一个错误。我包含的链接上有很多很棒的参考资料,它们可以帮助您指明正确的方向。
    • 这是旋转后无法正确撤消的缩放。已经到处寻找解决方案。
    • 这是否也像 Apple Mail 和 Music 应用一样缩小后面的窗口?
    • 我已经更新了 Swift 4 的 repo。我已经打开了一个 PR 但与此同时:github.com/endash/iOSComposeAnimation
    【解决方案3】:

    我无法评论 MariSa 的回答,但我更改了他们的代码以使其真正起作用(可以进行一些清理,但它对我有用)

    (斯威夫特 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
    }()
    

    按照 MariSa 的指示更新 frameOfPresentedViewInContainerView:

    override var frameOfPresentedViewInContainerView : CGRect {
    
        // We don't want the presented view to fill the whole container view, so inset it's 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's 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's 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)
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-14
        • 2018-12-23
        • 1970-01-01
        • 1970-01-01
        • 2016-11-08
        • 1970-01-01
        相关资源
        最近更新 更多