During CocoaConf-2012-Raleigh, I discussed my PinchView from Laying out text with Core Text. It’s a text view that squeezes the glyphs towards your finger when you touch it. I built it to demonstrate per-glyph layout in Core Text. While demonstrating it, I was pretty unsatisfied with how it looked when you touched it or let go. When you drag your finger on the view, the glyphs move around like water. It’s quite pretty. But when you initially touch the screen, the glyphs suddenly jump to their new locations, and then they jump back when you release the screen. Well, that’s no good. So I wanted to add animations.
But here’s the thing: what do you animate? While you do want to animate the glyph positions, you’re not doing it directly. The location of each glyph is dependent on the location of the current touch. What you want to animate is how much the touch impacts the glyph positions. A quick look over CALayer’s list of animatable properties confirmed that there’s nothing like that. But no problem, I added a custom property called
touchPointScale and animated that. (I cover animating custom properties in the Layers chapter of iOS:PTL, and I still have to pull out that chapter every time to remind myself how to do it. Ole Begemann has a good, quick writeup on Stack Overflow.)
OK, so great. But one comment I got at CocoaConf was that it should handle multitouch. So I started playing with that, but now I had a problem. I could have lots of touches, so my single
touchPointScale doesn’t…er…scale. What I want to do is take a collection of
TouchPoint objects that the layer owns, animate each of their scales independently, and have the layer do its animation thing. But how do we animate based on changes in properties of things in a layer’s collection?
The sample code is available on github.
First, we have
TouchPoint objects. These are just trivial data objects. The
identifier here happens to be the address of the object, but it could be any unique string.
1 2 3 4 5 6 7 8
Then we have
PinchTextLayer, which has a collection of
The thing we want to animate is “the
scale of the touch point with a given identifier.” In order to animate something, it needs to be something you can call
setValue:forKeyPath: on. And that brings us to the power of KVC and dictionaries.
Say you have this code:
You can also write that this way:
And if you have this code:
You can write that this way:
And that means that things held in dictionaries can be animated pretty easily because they can be accessed via
setValue:forKeyPath:. First, you need to tell the layer that changes on your dictionary impact drawing:
1 2 3 4 5 6 7 8
This applies to all key paths that start with
touchPointForIdentifier. And because we’re not animating
touchPointForIdentifier itself, we don’t have to make it
@dynamic. We do need to copy it in
initWithLayer: of course:
1 2 3 4 5 6
And that’s just about it. We can now treat the key path “touchPointForIdentifier.<identifier>.scale” as an animatable property just like
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
Side note: Along the way, I also developed a technique for animating custom properties (without any storage behind them, implemented by custom methods) by overriding
setValue:forKeyPath:. If you think that might be useful, you can see it in github, but so far I haven’t thought of any cases where it’s better than using the dictionary.