我正在关注这个真棒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
}
但是控制器会全屏显示。
答案 0 :(得分:5)
您可以尝试两个不同的容器视图,用于顶部和底部的一半。 然后给它动画......
答案 1 :(得分:2)
感谢大家的建议,我尝试使用容器视图,这是我如何做到的:
首先我在containerView
课程中添加了CircularTransition
媒体资源:
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
就会加载它,但是,{ {1}}需要来自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
}
中以这种方式创建了CircularTransition
课程:
mainViewController
基本上我所做的一切,我都可以展示华丽的双vc视图 然而,在iPad上,回归过渡并不起作用,我还是 我们还没弄明白是什么造成的。
答案 2 :(得分: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将使用转发委托在发送UIButtons中心的圆心显示您的ResultViewController。
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参数(应该是你的按钮中心我猜测),一个可选的半径参数(如果这是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。我更改了上面的代码以反映更改。它不太理想,但我稍后会再看看是否有更好的解决方法。编辑:这就像是对我开放的
答案 3 :(得分:1)
您好我在您的animateTransition方法中做了一些更改尝试此操作。您可能需要使用动画的withRelativeStartTime以及框架和中心来完善动画。但我想这应该让你开始。
Uncaught (in promise): Error: Cannot match any routes. URL Segment: 'admin'
希望这有帮助。