用动画同时显示 2 个视图控制器
Posted
技术标签:
【中文标题】用动画同时显示 2 个视图控制器【英文标题】:Display 2 view controllers at the same time with animation 【发布时间】: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 是的 :) 您是否尝试在父视图控制器和子视图控制器中使用容器控制器。这将达到你的目的。 【参考方案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)
)
希望这会有所帮助。
【讨论】:
以上是关于用动画同时显示 2 个视图控制器的主要内容,如果未能解决你的问题,请参考以下文章
如何在 tableview 中为图像设置动画以同时扩展和打开另一个视图控制器?