iOS自定义Push转场
Posted CaryaLiu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS自定义Push转场相关的知识,希望对你有一定的参考价值。
自定义Push转场
理论
首先,需要实现UINavigationController
的delegate
方法。
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?
该代理方法提供一个UIViewControllerAnimatedTransitioning
对象,该对象负责在自定义转场的源视图和目标视图的动画。
为了给view controller
之间的转场提供用户交互,我们必须实现另外一个代理方法。
func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
该方法也会用于处理view controller
的侧滑返回。
实践
首先,我们创建一个遵循UIViewControllerAnimatedTransitioning
协议的对象。
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
func animateTransition(using transitionContext: UIViewControllerContextTransitioning)
该协议包含如上两个required
方法。该协议继承NSObjectProtocol
协议,因此遵循该协议的对象可继承自NSObject
来满足。
另外,在遵循该协议的对象中,需要知道操作是Push
还是Pop
操作。因此,你可以在对象中声明一个标志位来区分当前操作,或者将Push
和Pop
操作的转场分离到两个对象中。
对于复杂的转场动画,你可以在对象中存储动画所需的任何值。
这里的示例是创建视图滑动动画,以下代码片段是用于Push
操作:
class SlidePushAnimator: NSObject, UIViewControllerAnimatedTransitioning
var direction = TransitionType.Direction.up
init(direction: TransitionType.Direction)
self.direction = direction
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
// TimeInterval(UINavigationController.hideShowBarDuration)
0.5
func animateTransition(using transitionContext: UIViewControllerContextTransitioning)
// 1.
guard let fromView = transitionContext.view(forKey: .from),
let toView = transitionContext.view(forKey: .to) else return
// 2.
transitionContext.containerView.addSubview(toView)
// 3.
let size = transitionContext.containerView.bounds.size
switch direction
case .up:
toView.frame = CGRect(x: 0, y: size.height, width: size.width, height: size.height)
case .down:
toView.frame = CGRect(x: 0, y: -size.height, width: size.width, height: size.height)
case .left:
toView.frame = CGRect(x: size.width, y: 0, width: size.width, height: size.height)
case .right:
toView.frame = CGRect(x: -size.width, y: 0, width: size.width, height: size.height)
UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, options: .curveEaseInOut)
toView.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
switch self.direction
case .up:
fromView.frame = CGRect(x: 0, y: -size.height, width: size.width, height: size.height)
case .down:
fromView.frame = CGRect(x: 0, y: size.height, width: size.width, height: size.height)
case .left:
fromView.frame = CGRect(x: -size.width, y: 0, width: size.width, height: size.height)
case .right:
fromView.frame = CGRect(x: size.width, y: 0, width: size.width, height: size.height)
completion: finished in
// 4.
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
滑动动画可能会是不同的方向,因此这里存储了关于方向的属性。
- 我们可通过
UIViewControllerContextTransitioning
的func view(forKey key: UITransitionContextViewKey) -> UIView?
方法获取动画所需的controller
视图 - 获取
containerView
,其作为转场中涉及视图的父视图。依赖于view controller
的是Push
还是Pop
,可能是将目标视图加载到containerView
(Push: containerView.addSubview(toView)
),或者是将目标视图添加到源视图fromView
底部(Pop: containerView.insertSubview(toView, belowSubview: fromView)
)。 - 根据转场的设定,绘制相应的动画。
- 动画完成后,需调用
UIViewControllerContextTransitioning
的方法func completeTransition(_ didComplete: Bool)
转场相关已经完成,现在就考虑如何将自定义转场附加到navigation controller
上。
这里定义TransitionCoordinator
来遵循UINavigationControllerDelegate
协议。
class TransitionCoordinator: NSObject, UINavigationControllerDelegate
// 1.
var interactionController: UIPercentDrivenInteractiveTransition?
// 2.
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?
let transitionType = navigationController.transition?[operation]
return transitionType?.transitionAnimator()
// 3.
func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
return interactionController
- 用于处理侧滑返回
- 根据当前是
Push
还是Pop
,返回对应的转场对象 - 返回
interactionController
以处理交互式转场。
最后,我们通过UINavigationController
的扩展将自定义转场附加到UINavigationController
上。
extension UINavigationController
enum TransitionKey
static var coordinator: Void?
static var transition: Void?
private static let disposeBag = DisposeBag()
var transitionCoordinatorHelper: TransitionCoordinator?
get
return objc_getAssociatedObject(self, &TransitionKey.coordinator) as? TransitionCoordinator
set
objc_setAssociatedObject(self, &TransitionKey.coordinator, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
var transition: [UINavigationController.Operation: TransitionType]?
get
return objc_getAssociatedObject(self, &TransitionKey.transition) as? [UINavigationController.Operation: TransitionType]
set
return objc_setAssociatedObject(self, &TransitionKey.transition, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
func addTransition(_ transitionType: TransitionType)
// 1.
transitionCoordinatorHelper = TransitionCoordinator()
delegate = transitionCoordinatorHelper
// 2.
let operation = transitionType.operation()
if var transition = transition
transition[operation] = transitionType
self.transition = transition
else
transition = [operation: transitionType]
// 3.
addEdgePan()
private func addEdgePan()
let swipeGestureRecognizer = UIScreenEdgePanGestureRecognizer()
swipeGestureRecognizer.edges = .left
swipeGestureRecognizer.rx.event.bind(onNext: [weak self] recognizer in
guard let self = self, let view = recognizer.view else
self?.transitionCoordinatorHelper?.interactionController = nil
return
let percent = recognizer.translation(in: view).x / view.bounds.width
if recognizer.state == .began
self.transitionCoordinatorHelper?.interactionController = UIPercentDrivenInteractiveTransition()
self.popViewController(animated: true)
else if recognizer.state == .changed
self.transitionCoordinatorHelper?.interactionController?.update(percent)
else if recognizer.state == .ended || recognizer.state == .cancelled
if percent > 0.5
self.transitionCoordinatorHelper?.interactionController?.finish()
else
self.transitionCoordinatorHelper?.interactionController?.cancel()
self.transitionCoordinatorHelper?.interactionController = nil
).disposed(by: UINavigationController.disposeBag)
view.addGestureRecognizer(swipeGestureRecognizer)
- 创建
TransitionCoordinator
实例,赋值给UINavigationController
的关联对象transitionCoordinatorHelper
,同时将UINavationController
的代理设置为TransitionCoordinator
实例 - 存储需要设置的转场类型
- 添加
UIScreenEdgePanGestureRecognizer
以处理页面侧滑返回
转场类型根据项目需要自己设定,如下:
enum TransitionType
enum Direction
case up, down, left, right
case slide(direction: Direction, operation: UINavigationController.Operation)
func operation() -> UINavigationController.Operation
switch self
case .slide(_, let operation):
return operation
extension TransitionType
func transitionAnimator() -> UIViewControllerAnimatedTransitioning?
TransitionAnimator.create(with: self)
class TransitionAnimator: NSObject
static func create(with transitionType: TransitionType) -> UIViewControllerAnimatedTransitioning?
switch transitionType
case .slide(let direction, let operation):
switch operation
case .push:
return SlidePushAnimator(direction: direction)
case .pop:
return SlidePopAnimator(direction: direction)
default:
return nil
在业务代码中,可如下使用:
override func viewDidAppear(_ animated: Bool)
super.viewDidAppear(animated)
// navigationController?.delegate = self
navigationController?.addTransition(.slide(direction: .down, operation: .pop))
代码仓库见:自定义Push转场
以上是关于iOS自定义Push转场的主要内容,如果未能解决你的问题,请参考以下文章