In the book of Matt Neuburg i was reading about interactive custom transition animation and in the example he wrote i stumbled upon this line:
weak var context : UIViewControllerContextTransitioning?
I was thinking why this property was marked weak. full code of animator:
class Animator : NSObject {
var anim : UIViewImplicitlyAnimating?
unowned var tbc : UITabBarController
weak var context : UIViewControllerContextTransitioning?
var interacting = false
init(tabBarController tbc: UITabBarController) {
self.tbc = tbc
super.init()
let sep = UIScreenEdgePanGestureRecognizer(target:self, action:#selector(pan))
sep.edges = UIRectEdge.right
tbc.view.addGestureRecognizer(sep)
sep.delegate = self
let sep2 = UIScreenEdgePanGestureRecognizer(target:self, action:#selector(pan))
sep2.edges = UIRectEdge.left
tbc.view.addGestureRecognizer(sep2)
sep2.delegate = self
}
}
extension Animator: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
print("animation controller")
return self
}
func tabBarController(_ tabBarController: UITabBarController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
print("interaction controller")
return self.interacting ? self : nil
}
}
extension Animator : UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ g: UIGestureRecognizer) -> Bool {
let ix = self.tbc.selectedIndex
let a = (g as! UIScreenEdgePanGestureRecognizer).edges == .right ?
ix < self.tbc.viewControllers!.count - 1 : ix > 0
return a
}
@objc func pan(_ g:UIScreenEdgePanGestureRecognizer) {
switch g.state {
case .began:
self.interacting = true
if g.edges == .right {
self.tbc.selectedIndex = self.tbc.selectedIndex + 1
} else {
self.tbc.selectedIndex = self.tbc.selectedIndex - 1
}
case .changed:
let v = g.view!
let delta = g.translation(in:v)
let percent = abs(delta.x/v.bounds.size.width)
self.anim?.fractionComplete = percent
self.context?.updateInteractiveTransition(percent)
case .ended:
// this is the money shot!
// with a property animator, the whole notion of "hurry home" is easy -
// including "hurry back to start"
let anim = self.anim as! UIViewPropertyAnimator
anim.pauseAnimation()
if anim.fractionComplete < 0.5 {
anim.isReversed = false
}
anim.continueAnimation(
withTimingParameters:
UICubicTimingParameters(animationCurve:.linear),
durationFactor: 0.2)
case .cancelled:
print("cancelled")
self.anim?.pauseAnimation()
self.anim?.stopAnimation(false)
self.anim?.finishAnimation(at: .start)
default: break
}
}
}
extension Animator : UIViewControllerInteractiveTransitioning {
// called if we are interactive
// (because we now have no percent driver)
func startInteractiveTransition(_ ctx: UIViewControllerContextTransitioning){
print("startInteractiveTransition")
// store the animator so the gesture recognizer can get at it
_ = self.interruptibleAnimator(using: ctx)
// store transition context so the gesture recognizer can get at it
self.context = ctx
// I don't like having to store them both
// I could make this look neater with a "helper object"
// but really, they ought to give me nicer way
}
}
extension Animator : UIViewControllerAnimatedTransitioning {
func interruptibleAnimator(using ctx: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
print("interruptibleAnimator")
if self.anim != nil {
return self.anim!
}
let vc1 = ctx.viewController(forKey:.from)!
let vc2 = ctx.viewController(forKey:.to)!
let con = ctx.containerView
let r1start = ctx.initialFrame(for:vc1)
let r2end = ctx.finalFrame(for:vc2)
// new in iOS 8, use these instead of assuming that the views are the views of the vcs
let v1 = ctx.view(forKey:.from)!
let v2 = ctx.view(forKey:.to)!
// which way we are going depends on which vc is which
// the most general way to express this is in terms of index number
let ix1 = self.tbc.viewControllers!.firstIndex(of:vc1)!
let ix2 = self.tbc.viewControllers!.firstIndex(of:vc2)!
let dir : CGFloat = ix1 < ix2 ? 1 : -1
var r1end = r1start
r1end.origin.x -= r1end.size.width * dir
var r2start = r2end
r2start.origin.x += r2start.size.width * dir
v2.frame = r2start
con.addSubview(v2)
let anim = UIViewPropertyAnimator(duration: 0.4, curve: .linear) {
v1.frame = r1end
v2.frame = r2end
}
anim.addCompletion { finish in
if finish == .end {
ctx.finishInteractiveTransition()
ctx.completeTransition(true)
} else {
ctx.cancelInteractiveTransition()
ctx.completeTransition(false)
}
}
self.anim = anim
print("creating animator")
return anim
}
func transitionDuration(using ctx: UIViewControllerContextTransitioning?) -> TimeInterval {
print("transitionDuration")
return 0.4
}
// called if we are not interactive
func animateTransition(using ctx: UIViewControllerContextTransitioning) {
print("animateTransition")
let anim = self.interruptibleAnimator(using: ctx)
anim.startAnimation()
}
func animationEnded(_ transitionCompleted: Bool) {
print("animation ended")
// reset everything
self.interacting = false
self.anim = nil
delay(1) {
// prove the context goes out of existence in good order
print(self.context as Any)
}
}
}
At glance, there was no apparent reference (for me) to Animator from UIViewControllerContextTransitioning, so i tried to look at Variables View.
And indeed context was referencing to animator. Then i tried to find this fact with the help of Debug Memory Graph, but it did not show me (or i failed to find this reference to animator).
How can i find it in the Debug Memory Graph please help me.
The reference to context
is weak merely because it belongs to the runtime. It is handed to us in our role as the UIViewControllerInteractiveTransitioning object:
extension Animator : UIViewControllerInteractiveTransitioning {
func startInteractiveTransition(_ ctx: UIViewControllerContextTransitioning) {
// ...
self.context = ctx
}
}
We need a more global reference to it so that the gesture recognizer methods can talk to it. So we store a reference in an instance property. But it is not ours to retain so we don't retain it.