Bri*_*ure 10 animation ios swift
我正在关注这个非常棒的视频,为我的项目创建一个自定义过渡,因为我正在为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
}
但控制器显示全屏.
所以我已经完成了我的答案的创建,它采用了与其他答案不同的方法,所以请耐心等待。
我认为最好的方法是创建一个 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。我更改了上面的代码以反映更改。这并不理想,但我稍后会再看看是否有更好的解决方案。
编辑:这就是对我来说开放的样子
| 归档时间: | 
 | 
| 查看次数: | 462 次 | 
| 最近记录: |