【问题标题】:Display 2 view controllers at the same time with animation用动画同时显示 2 个视图控制器
【发布时间】:2016-10-10 07:18:24
【问题描述】:

我正在关注这个很棒的video 为我的项目创建自定义过渡,因为我正在为 iPad 开发,所以我不想全屏显示目标视图控制器,而是希望它占据屏幕的一半像这样:

我的自定义转场类的代码是:

class CircularTransition: NSObject {

var circle = UIView()
var startingPoint = CGPoint.zero {
    didSet {
        circle.center = startingPoint
    }
}
var circleColor = UIColor.white
var duration = 0.4

enum circularTransitionMode: Int {
    case present, dismiss
}
var transitionMode = circularTransitionMode.present    
}

extension CircularTransition: UIViewControllerAnimatedTransitioning {

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    return duration
}

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    let containerView = transitionContext.containerView
    if transitionMode == .present {
        if let presentedView = transitionContext.view(forKey: UITransitionContextViewKey.to) {

            var viewCenter = presentedView.center
            var viewSize = presentedView.frame.size


            if UIDevice.current.userInterfaceIdiom == .pad {
                viewCenter = CGPoint(x: viewCenter.x, y: viewSize.height)
                viewSize = CGSize(width: viewSize.width, height: viewSize.height)
            }

            circle = UIView()
            circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
            circle.layer.cornerRadius = circle.frame.size.width / 2
            circle.center = startingPoint
            circle.backgroundColor = circleColor
            circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
            containerView.addSubview(circle)

            presentedView.center = startingPoint
            presentedView.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
            presentedView.alpha = 0
            containerView.addSubview(presentedView)

            UIView.animate(withDuration: duration, animations: {
                self.circle.transform = CGAffineTransform.identity
                presentedView.transform = CGAffineTransform.identity
                presentedView.alpha = 1
                presentedView.center = viewCenter
                }, completion: {(sucess: Bool) in transitionContext.completeTransition(sucess)})
        }
    } else {
        if let returningView = transitionContext.view(forKey: UITransitionContextViewKey.from) {
            let viewCenter = returningView.center
            let viewSize = returningView.frame.size

            circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
            circle.layer.cornerRadius = circle.frame.size.width / 2
            circle.center = startingPoint

            UIView.animate(withDuration: duration + 0.1, animations: {
                self.circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
                returningView.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
                returningView.center = self.startingPoint
                returningView.alpha = 0
                }, completion: {(success: Bool) in
                    returningView.center = viewCenter
                    returningView.removeFromSuperview()
                    self.circle.removeFromSuperview()
                    transitionContext.completeTransition(success)
            })
        }
    }
}

func frameForCircle(withViewCenter viewCenter: CGPoint, size viewSize: CGSize, startPoint: CGPoint) -> CGRect {

    let xLength = fmax(startingPoint.x, viewSize.width - startingPoint.x)
    let yLength = fmax(startingPoint.y, viewSize.height - startingPoint.y)
    let offsetVector = sqrt(xLength * xLength + yLength * yLength) * 2
    let size = CGSize(width: offsetVector, height: offsetVector)

    return CGRect(origin: CGPoint.zero, size: size)

}
}

以及我的视图控制器中的代码部分:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    let secondVC = segue.destination as! ResultViewController
    secondVC.transitioningDelegate = self
    secondVC.modalPresentationStyle = .custom
}

// MARK: - Animation

func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {

    transtion.transitionMode = .dismiss
    transtion.startingPoint = calculateButton.center
    transtion.circleColor = calculateButton.backgroundColor!
    return transtion
}

func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {

    transtion.transitionMode = .present
    transtion.startingPoint = calculateButton.center
    transtion.circleColor = calculateButton.backgroundColor!
    return transtion
}

但是控制器全屏显示。

【问题讨论】:

  • 这里有什么问题?
  • @gurmandeep 现在还是全屏
  • 好的,您希望第二个控制器与图像相似。
  • @gurmandeep 是的 :)
  • 您是否尝试在父视图控制器和子视图控制器中使用容器控制器。这将达到你的目的。

标签: ios swift animation


【解决方案1】:

您可以尝试两种不同的 Container View 的顶部和底部的一半。 然后给它动画...

【讨论】:

    【解决方案2】:

    所以我已经完成了我的答案,它采用与其他答案不同的方法,所以请耐心等待。

    我认为最好的方法是创建一个 UIViewController 子类(我称之为 CircleDisplayViewController),而不是添加一个容器视图。那么你所有需要这个功能的 VC 都可以从它继承(而不是从 UIViewController)。

    这样,您呈现和关闭 ResultViewController 的所有逻辑都在一个地方处理,并且可以在您应用的任何地方使用。

    你的 VC 可以使用它的方式是这样的:

    class AnyViewController: CircleDisplayViewController { 
    
        /* Only inherit from CircleDisplayViewController, 
          otherwise you inherit from UIViewController twice */
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
        @IBAction func showCircle(_ sender: UIButton) {
    
            openCircle(withCenter: sender.center, radius: nil, resultDataSource: calculator!.iterateWPItems())
    
            //I'll get to this stuff in just a minute
    
            //Edit: from talking to Bright Future in chat I saw that resultViewController needs to be setup with calculator!.iterateWPItems()
    
        }
    
    }
    

    showCircle 将在其中使用转换委托呈现您的 ResultViewController,圆心位于发送 UIButtons 的中心。

    CircleDisplayViewController 子类是这样的:

    class CircleDisplayViewController: UIViewController, UIViewControllerTransitioningDelegate, ResultDelegate {
    
        private enum CircleState {
            case collapsed, visible
        }
    
        private var circleState: CircleState = .collapsed
    
        private var resultViewController: ResultViewController!
    
        private lazy var transition = CircularTransition()
    
        func openCircle(withCenter center: CGPoint, radius: CGFloat?, resultDataSource: ([Items], Int, String)) {
    
            let circleCollapsed = (circleState == .collapsed)
    
            DispatchQueue.main.async { () -> Void in
    
                if circleCollapsed {
    
                    self.addCircle(withCenter: center, radius: radius, resultDataSource: resultDataSource)
    
                }
    
            }
    
        }
    
        private func addCircle(withCenter circleCenter: CGPoint, radius: CGFloat?, resultDataSource: ([Items], Int, String])) {
    
            var circleRadius: CGFloat!
    
            if radius == nil {
                circleRadius = view.frame.size.height/2.0
            } else {
                circleRadius = radius
            }
    
            //instantiate resultViewController here, and setup delegate etc.
    
            resultViewController = UIStoryboard.resultViewController()
    
            resultViewController.transitioningDelegate = self
            resultViewController.delegate = self
            resultViewController.modalPresentationStyle = .custom
    
            //setup any values for resultViewController here
    
            resultViewController.dataSource = resultDataSource
    
            //then set the frame of resultViewController (while also setting endFrame)
    
            let resultOrigin = CGPoint(x: 0.0, y: circleCenter.y - circleRadius)
            let resultSize = CGSize(width: view.frame.size.width, height: (view.frame.size.height - circleCenter.y) + circleRadius)
    
            resultViewController.view.frame = CGRect(origin: resultOrigin, size: resultSize)
            resultViewController.endframe = CGRect(origin: resultOrigin, size: resultSize)
    
            transition.circle = UIView()
            transition.startingPoint = circleCenter
            transition.radius = circleRadius
    
            transition.circle.frame = circleFrame(radius: transition.radius, center: transition.startingPoint)
    
            present(resultViewController, animated: true, completion: nil)
    
        }
    
        func collapseCircle() { //THIS IS THE RESULT DELEGATE FUNCTIONS
    
            dismiss(animated: true) {
    
                self.resultViewController = nil
    
            }
    
        }
    
        func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    
            transition.transitionMode = .dismiss
            transition.circleColor = UIColor.red
            return transition
    
        }
    
        func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    
            transition.transitionMode = .present
            transition.circleColor = UIColor.red
            return transition
    
        }
    
        func circleFrame(radius: CGFloat, center: CGPoint) -> CGRect {
            let circleOrigin = CGPoint(x: center.x - radius, y: center.y - radius)
            let circleSize = CGSize(width: radius*2, height: radius*2)
            return CGRect(origin: circleOrigin, size: circleSize)
        }
    
    }
    
    public extension UIStoryboard {
        class func mainStoryboard() -> UIStoryboard { return UIStoryboard(name: "Main", bundle: Bundle.main) }
    }
    
    private extension UIStoryboard {
    
        class func resultViewController() -> ResultViewController {
            return mainStoryboard().instantiateViewController(withIdentifier: "/* Your ID for ResultViewController */") as! ResultViewController
        }
    
    }
    

    从 DisplayCircleViewController 继承的 VC 调用的唯一函数是 openCircle,openCircle 有一个 circleCenter 参数(我猜应该是你的按钮中心),一个可选的 radius 参数(如果这是 nil 则为默认值取视图高度的一半,然后设置 ResultViewController 所需的任何其他内容。

    在 addCircle 函数中有一些重要的东西:

    你设置 ResultViewController 但是你必须在展示之前(就像你准备 segue 一样),

    然后为它设置框架(我试图让它成为可见的圆圈区域,但这里很粗糙,可能值得一玩),

    然后这是我重置过渡圆的地方(而不是在过渡类中),这样我就可以在这里设置圆的起点、半径和框架。

    那只是一个普通的礼物。

    如果您没有为 ResultViewController 设置标识符,则需要这样做(请参阅 UIStoryboard 扩展)

    我还更改了 TransitioningDelegate 函数,因此您无需设置圆心,这是因为为了保持其通用性,我将这个责任交给了继承自此的 ViewController。 (见代码顶部)

    最后我改变了 CircularTransition 类

    我添加了一个变量:

    var radius: CGFloat = 0.0 //set in the addCircle function above
    

    并更改了 animateTransition:

    (删除了注释掉的行):

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    
        let containerView = transitionContext.containerView
    
        if transitionMode == .present {
            if let presentedView = transitionContext.view(forKey: UITransitionContextViewKey.to) {
    
               ...
    
               // circle = UIView()
               // circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
               circle.layer.cornerRadius = radius
    
               ...
    
            }
    
        } else {
    
            if let returningView = transitionContext.view(forKey: UITransitionContextViewKey.from) {
    
                ...
    
                // circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
    
                ...
    
            }
        }
    }
    

    最后我制定了一个协议,以便 ResultViewController 可以关闭圆圈

    protocol ResultDelegate: class {
    
        func collapseCircle()
    
    }
    
    class ResultViewController: UIViewController {
    
        weak var delegate: ResultDelegate!
    
        var endFrame: CGRect! 
    
        var dataSource: ([Items], Int, String)! // same as in Bright Future's case
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
    
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
        override func viewDidLayoutSubviews() {
            if endFrame != nil {
                view.frame = endFrame
            }
        }
    
        @IBAction func closeResult(_ sender: UIButton) {
    
            delegate.collapseCircle()
    
        }
    
    }
    

    这是一个相当大的答案,对此感到抱歉,我写的有点匆忙,所以如果有什么不清楚的地方就说吧。

    希望这会有所帮助!

    编辑:我发现了问题,iOS 10 改变了他们布局视图的方式,所以为了解决这个问题,我向 ResultViewController 添加了一个 endFrame 属性,并将它的视图框架设置为 viewDidLayoutSubviews 中的那个。我还在 addCircle 中同时设置了 frame 和 endFrame。我更改了上面的代码以反映更改。这并不理想,但我稍后会再看一下,看看是否有更好的解决方案。

    编辑:这就是对我开放的样子

    【讨论】:

    • 哇,你的回答太棒了!我需要一段时间来实现它,所以我现在就接受它,为让它通用的整个想法喝彩,它可以挽救生命:)
    • 我刚刚实现了你的代码,ResultViewController 似乎是全尺寸的,而不是屏幕的一半。
    • 而且我无法在addCircle 方法中覆盖它
    • 我会认真研究为什么会发生这种情况,因为你已经给了我分数。
    • 啊,我明白了,resultVC 的框架。我去看看,大约半小时后我得去辅导某人,但到一天结束前一切都会搞定的!
    【解决方案3】:

    感谢大家的建议,我尝试使用容器视图,我是这样做的:

    首先我在CircularTransition 类中添加了一个containerView 属性:

    class CircularTransition: NSObject {
        ...
    
        var containerView: UIView
        init(containerView: UIView) {
            self.containerView = containerView
        }
    
        ...    
    }
    

    然后在其扩展中注释掉这些代码:

    // let containerView = transitionContext.containerView
    
    // if UIDevice.current.userInterfaceIdiom == .pad {
    //          viewCenter = CGPoint(x: viewCenter.x, y: viewSize.height)
    //          viewSize = CGSize(width: viewSize.width, height: viewSize.height)
    //      }
    

    在我的mainViewController中,我添加了一个添加容器视图的方法:

    func addContainerView() {
    
        let containerView = UIView()
        containerView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(containerView)
        NSLayoutConstraint.activate([
            containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
            containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
            containerView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5),
            containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
            ])
        transtion.containerView = containerView
    }
    

    我不使用故事板的原因是,如果我将动画视图控制器 (ResultViewController) 放在容器视图中,它会在加载 mainViewController 时加载,但是,ResultViewController 需要来自prepareForSegue,因此它会崩溃。

    然后我在prepareForSegue改了一点:

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    
        transtion.containerView = view
        if UIDevice.current.userInterfaceIdiom == .pad {
            addContainerView()
        }
    
        let secondVC = segue.destination as! ResultViewController
        secondVC.transitioningDelegate = self
        secondVC.modalPresentationStyle = .custom
    
        secondVC.dataSource = calculator!.iterateWPItems().0
    }
    

    并以这种方式在mainViewController 中创建CircularTransition 类:

        let transtion = CircularTransition(containerView: UIView())
    

    这基本上就是我所做的,我可以显示华丽的双 vc 视图 在 iPad 上,但是,返回转换不起作用,我仍然 还没弄清楚是什么原因造成的。

    【讨论】:

      【解决方案4】:

      您好,我对您的 animateTransition 方法进行了一些更改,试试这个。您可能需要对动画的 withRelativeStartTime 以及 frame 和 center 进行一些操作以完善动画。但我想这应该让你开始。

      func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
          let containerView = transitionContext.containerView
          if transitionMode == .present {
              if let presentedView = transitionContext.view(forKey: UITransitionContextViewKey.to) {
      
                  var viewCenter = presentedView.center
                  var viewSize = presentedView.frame.size
      
      
                  if UIDevice.current.userInterfaceIdiom == .pad {
                      viewCenter = CGPoint(x: viewCenter.x, y: viewSize.height)
                      viewSize = CGSize(width: viewSize.width, height: viewSize.height)
                  }
      
                  circle = UIView()
                  circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
                  circle.layer.cornerRadius = circle.frame.size.width / 2
                  circle.center = startingPoint
                  circle.backgroundColor = circleColor
                  circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
                  circle.layer.masksToBounds = true
                  containerView.addSubview(circle)
      
                  presentedView.center = startingPoint
                  presentedView.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
                  presentedView.alpha = 0
                  containerView.addSubview(presentedView)
      
                  UIView.animateKeyframes(withDuration: duration, delay: 0, options: .calculationModeLinear, animations: { 
                      UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1, animations: { 
                          self.circle.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
                          presentedView.alpha = 1
                      })
                      UIView.addKeyframe(withRelativeStartTime: 0.19, relativeDuration: 1, animations: {
                          presentedView.transform = CGAffineTransform(scaleX: 1, y: 1)
                          presentedView.frame = CGRect(x: 0, y: (containerView.frame.size.height / 2)+10, width: containerView.frame.size.width, height: containerView.frame.size.height*0.5)
                      })
                      }, completion: { (sucess) in
                          transitionContext.completeTransition(sucess)
                  })
              }
          } else {
              if let returningView = transitionContext.view(forKey: UITransitionContextViewKey.from) {
                  let viewCenter = returningView.center
                  let viewSize = returningView.frame.size
      
                  circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
                  circle.layer.cornerRadius = circle.frame.size.width / 2
                  circle.center = startingPoint
      
                  UIView.animate(withDuration: duration + 0.1, animations: {
                      self.circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
                      returningView.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
                      returningView.center = self.startingPoint
                      returningView.alpha = 0
                      }, completion: {(success: Bool) in
                          returningView.center = viewCenter
                          returningView.removeFromSuperview()
                          self.circle.removeFromSuperview()
                          transitionContext.completeTransition(success)
                  })
              }
          }
      }
      

      希望这会有所帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多