iOS Swift: 自定义交互过渡
iOS Swift: Custom interactive transition
我写了一个自定义滑动过渡,在模态演示中效果很好。但是在推送演示中,"to" 视图位置没有动画。
我已经尝试过使用 alpha 切换翻译的相同代码并且它有效。
from 视图完美运行,只是 to 视图在动画期间保持固定。
func transitionAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
let duration = transitionDuration(using: transitionContext)
let container = transitionContext.containerView
let toController = transitionContext.viewController(forKey: .to)
toController?.beginAppearanceTransition(true, animated: true)
guard
let to = transitionContext.view(forKey: .to),
let from = transitionContext.view(forKey: .from)
else {
print("To or from view are nil!")
fatalError()
}
container.addSubview(to)
let animator = UIViewPropertyAnimator(duration: duration, curve: .linear)
var toStartingPoint: CGPoint
var fromEndingPoint: CGPoint
switch self.from {
case .down:
toStartingPoint = CGPoint(x: 0, y: -from.bounds.height)
fromEndingPoint = CGPoint(x: 0, y: from.bounds.height)
case .top:
toStartingPoint = CGPoint(x: 0, y: from.bounds.height)
fromEndingPoint = CGPoint(x: 0, y: -from.bounds.height)
case .right:
toStartingPoint = CGPoint(x: from.bounds.width, y: 0)
fromEndingPoint = CGPoint(x: -from.bounds.width, y: 0)
case .left:
toStartingPoint = CGPoint(x: -from.bounds.width, y: 0)
fromEndingPoint = CGPoint(x: from.bounds.width, y: 0)
}
to.transform = CGAffineTransform(translationX: toStartingPoint.x, y: toStartingPoint.y)
animator.addAnimations({
from.transform = CGAffineTransform(translationX: fromEndingPoint.x, y: fromEndingPoint.y)
}, delayFactor: 0.0)
animator.addAnimations({
to.transform = .identity
}, delayFactor: 0.0)
animator.addCompletion { [weak self] position in
switch position {
case .start:
self?.auxCancelCompletion?()
transitionContext.completeTransition(false)
self?.auxAnimationsCancel?()
case .end:
self?.auxEndCompletion?()
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
from.transform = .identity
to.transform = .identity
default:
transitionContext.completeTransition(false)
self?.auxAnimationsCancel?()
}
}
if let auxAnimations = auxAnimations {
animator.addAnimations(auxAnimations)
}
self.animator = animator
self.context = transitionContext
animator.addCompletion { [unowned self] _ in
self.animator = nil
self.context = nil
}
animator.isUserInteractionEnabled = true
return animator
}
我认为这是委托的问题,但 navigationDelgate 设置正确,否则我想我看不到任何动画..
代表设置:
override func viewDidLoad() {
super.viewDidLoad()
transitionHelper = SwipeInteractiveTransitionHelper(withDelegate: self)
}
extension TodayViewController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return transitionHelper?.swipeTransition
}
func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return transitionHelper?.swipeTransition
}
}
这里是自定义推送协调器,其中 viewController 是下一个视图控制器,也是我附加委托的地方。
case .pushCustom:
guard let navigationController = currentViewController.navigationController else {
fatalError("Can't push a view controller without a current navigation controller")
}
guard let current = currentViewController as? UINavigationControllerDelegate else {
fatalError("Can't push a view controller without a current navigation delegate")
}
navigationController.delegate = current
navigationController.pushViewController(viewController, animated: true) { [weak self] in
self?.currentViewController = SceneCoordinator.actualViewController(for: viewController)
completion?()
}
我就是这样做的。希望对你有所帮助。
VC:UIViewController
{
@IBAction func test(_ sender: Any){
navigationController?.delegate = self
let destine = storyboard?.instantiateViewController(withIdentifier: "target")
navigationController?.pushViewController(destine!, animated: true)
}
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?{
return Traistion()
}
}
class Traistion: NSObject, UIViewControllerAnimatedTransitioning{
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let animator = transitionAnimator(using: transitionContext)
animator.startAnimation()
}
var animator: UIViewPropertyAnimator!
var context: UIViewControllerContextTransitioning!
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval{
return 1.0
}
enum Direction {
case down
case top
case left
case right
}
var from : Direction = .left
var auxCancelCompletion :(()->())? = {
return nil
}()
var auxAnimationsCancel :(()->())? = {
return nil
}()
var auxEndCompletion :(()->())? = {
return nil
}()
var auxAnimations : (()->())?
func transitionAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
let duration = transitionDuration(using: transitionContext)
let container = transitionContext.containerView
let toController = transitionContext.viewController(forKey: .to)
toController?.beginAppearanceTransition(true, animated: true)
guard
let to = transitionContext.view(forKey: .to),
let from = transitionContext.view(forKey: .from)
else {
print("To or from view are nil!")
fatalError()
}
container.addSubview(to)
let animator = UIViewPropertyAnimator(duration: duration, curve: .linear)
var toStartingPoint: CGPoint
var fromEndingPoint: CGPoint
switch self.from {
case .down:
toStartingPoint = CGPoint(x: 0, y: -from.bounds.height)
fromEndingPoint = CGPoint(x: 0, y: from.bounds.height)
case .top:
toStartingPoint = CGPoint(x: 0, y: from.bounds.height)
fromEndingPoint = CGPoint(x: 0, y: -from.bounds.height)
case .right:
toStartingPoint = CGPoint(x: from.bounds.width, y: 0)
fromEndingPoint = CGPoint(x: -from.bounds.width, y: 0)
case .left:
toStartingPoint = CGPoint(x: -from.bounds.width, y: 0)
fromEndingPoint = CGPoint(x: from.bounds.width, y: 0)
}
to.transform = CGAffineTransform(translationX: toStartingPoint.x, y: toStartingPoint.y)
animator.addAnimations({
from.transform = CGAffineTransform(translationX: fromEndingPoint.x, y: fromEndingPoint.y)
}, delayFactor: 0.0)
animator.addAnimations({
to.transform = .identity
}, delayFactor: 0.0)
animator.addCompletion { [weak self] position in
switch position {
case .start:
self?.auxCancelCompletion?()
transitionContext.completeTransition(false)
self?.auxAnimationsCancel?()
case .end:
self?.auxEndCompletion?()
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
from.transform = .identity
to.transform = .identity
default:
transitionContext.completeTransition(false)
self?.auxAnimationsCancel?()
}
}
if let auxAnimations = auxAnimations {
animator.addAnimations(auxAnimations)
}
self.animator = animator
self.context = transitionContext
animator.addCompletion { [unowned self] _ in
//self.animator = nil
// self.context = nil
}
animator.isUserInteractionEnabled = true
return animator
}
}
使用时interruptibleAnimator
。它会至少调用这个函数两次,你应该提供相同但不同的动画师。所以你必须这样称呼它:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
animator = transitionAnimator(using: transitionContext) as! UIViewPropertyAnimator
animator.startAnimation()
}
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
return animator
}
var animator: UIViewPropertyAnimator!
var context: UIViewControllerContextTransitioning!
或像这样更简单:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
}
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
if animator == nil {animator = transitionAnimator(using: transitionContext) as! UIViewPropertyAnimator
animator.startAnimation()}
return animator
}
这里animator
会被调用两次,而且是同一个。如果
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
return transitionAnimator(using: transitionContext) }
这不是正确的使用方法,因为动画师会有所不同。
查看文档的最后一行。
解决了对目标视图的快照进行动画处理,而不是直接对目标视图进行动画处理。
let to = transitionContext.view(forKey: .to)
让 toViewSnapshot = to.snapshotView(afterScreenUpdates: true)
只需将 toViewSnapshot 用于动画
我写了一个自定义滑动过渡,在模态演示中效果很好。但是在推送演示中,"to" 视图位置没有动画。
我已经尝试过使用 alpha 切换翻译的相同代码并且它有效。
from 视图完美运行,只是 to 视图在动画期间保持固定。
func transitionAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
let duration = transitionDuration(using: transitionContext)
let container = transitionContext.containerView
let toController = transitionContext.viewController(forKey: .to)
toController?.beginAppearanceTransition(true, animated: true)
guard
let to = transitionContext.view(forKey: .to),
let from = transitionContext.view(forKey: .from)
else {
print("To or from view are nil!")
fatalError()
}
container.addSubview(to)
let animator = UIViewPropertyAnimator(duration: duration, curve: .linear)
var toStartingPoint: CGPoint
var fromEndingPoint: CGPoint
switch self.from {
case .down:
toStartingPoint = CGPoint(x: 0, y: -from.bounds.height)
fromEndingPoint = CGPoint(x: 0, y: from.bounds.height)
case .top:
toStartingPoint = CGPoint(x: 0, y: from.bounds.height)
fromEndingPoint = CGPoint(x: 0, y: -from.bounds.height)
case .right:
toStartingPoint = CGPoint(x: from.bounds.width, y: 0)
fromEndingPoint = CGPoint(x: -from.bounds.width, y: 0)
case .left:
toStartingPoint = CGPoint(x: -from.bounds.width, y: 0)
fromEndingPoint = CGPoint(x: from.bounds.width, y: 0)
}
to.transform = CGAffineTransform(translationX: toStartingPoint.x, y: toStartingPoint.y)
animator.addAnimations({
from.transform = CGAffineTransform(translationX: fromEndingPoint.x, y: fromEndingPoint.y)
}, delayFactor: 0.0)
animator.addAnimations({
to.transform = .identity
}, delayFactor: 0.0)
animator.addCompletion { [weak self] position in
switch position {
case .start:
self?.auxCancelCompletion?()
transitionContext.completeTransition(false)
self?.auxAnimationsCancel?()
case .end:
self?.auxEndCompletion?()
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
from.transform = .identity
to.transform = .identity
default:
transitionContext.completeTransition(false)
self?.auxAnimationsCancel?()
}
}
if let auxAnimations = auxAnimations {
animator.addAnimations(auxAnimations)
}
self.animator = animator
self.context = transitionContext
animator.addCompletion { [unowned self] _ in
self.animator = nil
self.context = nil
}
animator.isUserInteractionEnabled = true
return animator
}
我认为这是委托的问题,但 navigationDelgate 设置正确,否则我想我看不到任何动画..
代表设置:
override func viewDidLoad() {
super.viewDidLoad()
transitionHelper = SwipeInteractiveTransitionHelper(withDelegate: self)
}
extension TodayViewController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return transitionHelper?.swipeTransition
}
func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return transitionHelper?.swipeTransition
}
}
这里是自定义推送协调器,其中 viewController 是下一个视图控制器,也是我附加委托的地方。
case .pushCustom:
guard let navigationController = currentViewController.navigationController else {
fatalError("Can't push a view controller without a current navigation controller")
}
guard let current = currentViewController as? UINavigationControllerDelegate else {
fatalError("Can't push a view controller without a current navigation delegate")
}
navigationController.delegate = current
navigationController.pushViewController(viewController, animated: true) { [weak self] in
self?.currentViewController = SceneCoordinator.actualViewController(for: viewController)
completion?()
}
我就是这样做的。希望对你有所帮助。
VC:UIViewController
{
@IBAction func test(_ sender: Any){
navigationController?.delegate = self
let destine = storyboard?.instantiateViewController(withIdentifier: "target")
navigationController?.pushViewController(destine!, animated: true)
}
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?{
return Traistion()
}
}
class Traistion: NSObject, UIViewControllerAnimatedTransitioning{
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let animator = transitionAnimator(using: transitionContext)
animator.startAnimation()
}
var animator: UIViewPropertyAnimator!
var context: UIViewControllerContextTransitioning!
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval{
return 1.0
}
enum Direction {
case down
case top
case left
case right
}
var from : Direction = .left
var auxCancelCompletion :(()->())? = {
return nil
}()
var auxAnimationsCancel :(()->())? = {
return nil
}()
var auxEndCompletion :(()->())? = {
return nil
}()
var auxAnimations : (()->())?
func transitionAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
let duration = transitionDuration(using: transitionContext)
let container = transitionContext.containerView
let toController = transitionContext.viewController(forKey: .to)
toController?.beginAppearanceTransition(true, animated: true)
guard
let to = transitionContext.view(forKey: .to),
let from = transitionContext.view(forKey: .from)
else {
print("To or from view are nil!")
fatalError()
}
container.addSubview(to)
let animator = UIViewPropertyAnimator(duration: duration, curve: .linear)
var toStartingPoint: CGPoint
var fromEndingPoint: CGPoint
switch self.from {
case .down:
toStartingPoint = CGPoint(x: 0, y: -from.bounds.height)
fromEndingPoint = CGPoint(x: 0, y: from.bounds.height)
case .top:
toStartingPoint = CGPoint(x: 0, y: from.bounds.height)
fromEndingPoint = CGPoint(x: 0, y: -from.bounds.height)
case .right:
toStartingPoint = CGPoint(x: from.bounds.width, y: 0)
fromEndingPoint = CGPoint(x: -from.bounds.width, y: 0)
case .left:
toStartingPoint = CGPoint(x: -from.bounds.width, y: 0)
fromEndingPoint = CGPoint(x: from.bounds.width, y: 0)
}
to.transform = CGAffineTransform(translationX: toStartingPoint.x, y: toStartingPoint.y)
animator.addAnimations({
from.transform = CGAffineTransform(translationX: fromEndingPoint.x, y: fromEndingPoint.y)
}, delayFactor: 0.0)
animator.addAnimations({
to.transform = .identity
}, delayFactor: 0.0)
animator.addCompletion { [weak self] position in
switch position {
case .start:
self?.auxCancelCompletion?()
transitionContext.completeTransition(false)
self?.auxAnimationsCancel?()
case .end:
self?.auxEndCompletion?()
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
from.transform = .identity
to.transform = .identity
default:
transitionContext.completeTransition(false)
self?.auxAnimationsCancel?()
}
}
if let auxAnimations = auxAnimations {
animator.addAnimations(auxAnimations)
}
self.animator = animator
self.context = transitionContext
animator.addCompletion { [unowned self] _ in
//self.animator = nil
// self.context = nil
}
animator.isUserInteractionEnabled = true
return animator
}
}
使用时interruptibleAnimator
。它会至少调用这个函数两次,你应该提供相同但不同的动画师。所以你必须这样称呼它:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
animator = transitionAnimator(using: transitionContext) as! UIViewPropertyAnimator
animator.startAnimation()
}
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
return animator
}
var animator: UIViewPropertyAnimator!
var context: UIViewControllerContextTransitioning!
或像这样更简单:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
}
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
if animator == nil {animator = transitionAnimator(using: transitionContext) as! UIViewPropertyAnimator
animator.startAnimation()}
return animator
}
这里animator
会被调用两次,而且是同一个。如果
func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
return transitionAnimator(using: transitionContext) }
这不是正确的使用方法,因为动画师会有所不同。
查看文档的最后一行。
解决了对目标视图的快照进行动画处理,而不是直接对目标视图进行动画处理。
let to = transitionContext.view(forKey: .to) 让 toViewSnapshot = to.snapshotView(afterScreenUpdates: true)
只需将 toViewSnapshot 用于动画