Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Swift3, KVO the alpha of a UIView?

It's "easy" to KVO a value in Swift3, using the technique in this great post...

https://stackoverflow.com/a/25219216/294884

How do I KVO the alpha of a UIView?

Goal: imagine a screen with a small view V. Screen has a large table view. Each cell has a small view CV.

I want each CV to follow1 the alpha of V, actually during an animation of the alpha of V.

(The only real alternate I can think of is to CADisplayLink the cells and poll the alpha of V, which kind of sucks.)


1Reason for that? It's the only way to sync alpha changes across cells.


Note Just as Rob explains below, in fact the alpha of a layer does not animate. (It just sets "instantly" to the new value at the beginning of an animation.) In fact you have to follow the presentation layer. Here's an example of the converse process, pulling it from where you want every draw

let d = CADisplayLink(target: self, selector: #selector(ThisClassName.updateAlpha))
d.add(to: RunLoop.current, forMode: RunLoopMode.commonModes)

func updateAlpha() {
  let a = leader.layer.presentation()?.value(forKey: "opacity") as! CGFloat
  follower.alpha = a
  }
like image 831
Fattie Avatar asked Dec 23 '22 22:12

Fattie


1 Answers

This is not an ideal use-case of KVO. You'd probably prefer to go to the code that is changing the alpha and have it do the necessary updates of the other views (or build some interface to do that more generically; something other than KVO).

Also note that if you're dealing with animations, the KVO of alpha may only identify the initiation of the animation, not observing the changes mid-flight during the animation. (Those sorts of changes are generally captured with the presentation/presentationLayer in a CADisplayLink or the like.)

Having said that, yes, you can observe alpha property. So, in Swift 3, just define context:

private var observerContext = 0  

And then add the observer:

observedView.addObserver(self, forKeyPath: "alpha", options: .new, context: &observerContext)

and then implement your observer:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    guard context == &observerContext else {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        return
    }

    // do what you want here
}

Note, remember to remove the observer. For example, you might do that in deinit:

deinit {
    observedView.removeObserver(self, forKeyPath: "alpha", context: &observerContext)
}

Note, in Swift 4, this process is simplified a bit. Define a token property to keep track of your observer:

private var token: NSKeyValueObservation?

And then observe the alpha property:

token = observedView.observe(\.alpha) { [weak self] object, change in
    // do something
}

Note the use of [weak self] to ensure the closure doesn't cause strong reference cycle. Obviously, if you're not referencing self in that closure, that is not needed.

But the virtue of the block based pattern is that (a) when the the token falls out of scope, the observer is automatically removed, so no special deinit routine is needed; (b) the observable keys for observedView are now strongly typed, avoiding simple typographical errors in the keys; and (c) because the observer is linked to this closure, we no longer have to do that check of the context of the observer and call self if this wasn't our context.

like image 178
Rob Avatar answered Dec 31 '22 14:12

Rob