A Safer KVC macro

January 08 2013

If you’re not familiar with the term, this just means instances where you’re using strings and a certain convention to identify methods that can be called on a given object. A particularly common instance of this is key-value observing such as -[NSObject addObserver:forKeyPath:options:context:], where you want to pass a key that you know is going to be valid and not return a runtime error.

A post got some attention recently promoting a way to statically-check key-value-coding methods. I’ve been using something along those lines for a while, but it has some important differences.

The disadvantage to the NSStringFromSelector() approach is that it doesn’t work in cases like hidden where the getter is called isHidden, since there is no valid selector called hidden. It also does not specify on which object the selector must be defined, so you could get away with mentioning a selector that is bogus for that object, but happens to be defined somewhere else (triggering a runtime error at some unknown time in the future).

My macro (which does support this usage) is as follows:

//o is an object type, k is the key on which to check validity
//returns a NSString constant of the relevant key

#define KVC(o, k) (0 && sizeof(o.k) ? @#k : @#k)

//Usage:
ObjectWithMethodCalledKeyName *object = ...;
[object addObserver:self forKeyPath:KVC(object, keyName) options:0 context:NULL];

Let’s dissect briefly when this will work and when it doesn’t. It uses the fact that you can do object.foo to call whatever the getter is for foo on object or just the method named foo if there is no property called foo. All great so far, but we don’t actually want to call this method, just find out statically whether it would be valid to call the method on an object of the type of object.

So, I use sizeof() as a way to invoke the compiler to consider the expression statically. Then, since I want it to return the key regardless of the actual result of sizeof(), avoid it being considered by a short-circuiting boolean operation and a ternary operator. The # stringifies k and the @ makes it an NSString.

Also available in gist!

Parallax Scrolling

January 07 2013

What is parallax scrolling?

Parallax scrolling is a technique typically found in side-scroller video games where as the character moves horizontally, the background moves at a slower pace, giving a sense of 3d-depth. This was an important technique to use before the advent of 3d games and graphics acceleration, but it’s also an aesthetic choice in such games as Cannabalt .

So, how can we use this in a user interface? One way, used in Twittelator Neue (made by my friends Ollie and Andrew Stone), is where an image inside a box gains an illusion of depth when you scroll vertically.

Notice the slightly different crop on the map below?

How might we implement this? Well, what we want is to change the crop of the image depending on the vertical scroll position as so:

Let’s just assume we’re working with a square image to simplify the calculations. A real implementation would have to deal with images of different aspect ratios.

We’re going to use the contentsRect property of the layer displaying the image to crop it efficiently. We could have put a larger image inside a parent layer with clipsToBounds on, but this approach is more efficient for CoreAnimation to render on the GPU, as it only needs to adjust texture coordinates instead of doing something like changing the glViewPort() to only render into the correct box.

So, since contentsRect is measured in an abstract coordinates, we want to generate a CGRect whose origin and size lie within the unit box (CGRect){0,0,1,1}, representing some crop within the square image of unknown dimensions.

If we take k to be the parallax ratio, ie the ratio of apparent movement in the image vs amount of scrolling, we can arrive at a calculation for the correct contentsRect.

float offset = <y position of cell in frame of scroll view>
float totalHeight = tableView.frame.size.height;
float yPos = 2.0 * (offset / totalHeight - 0.5); //normalized y-position (-1 to 1)
float k = 0.7f; //parallax ratio
float w = self.bounds.size.width;
float h = self.bounds.size.height;

// We want the center of the crop to be at 0.5 when yPos = 0,
// and seem to move vertically by (k - 1.0) * contentSize.height pixels at each extreme.
// w in pixels equates to 1.0 vertically in contentRect because the image is square

float center = 0.5 - (k - 1.0) * 0.5 * totalHeight/w * yPos;

layer.contentsRect = (CGRect){
	.origin.x = 0.0,
	.origin.y = center - 0.5 * h/w,
	.size.width = w/w,
	.size.height = h/w,
};

One thing to note is that because we are in a UIScrollView, instead of moving the image up at k * offset and ourselves at 1.0 * offset, we should move the image down inside our box at (k - 1.0) * offset to compensate for the motion of the scrollview contents.

Final result (kinda have to try it to see the effect):

You can fork the code on GitHub if you want to add support for non-square images, etc.

Announcing Take

December 19 2012

I’m pleased to finally talk about my latest project: Take (App Store link). It’s a video filter app that is really simple.

So simple it just shoots video with the filter applied and saves it to your camera roll.

You can read more about it over on the official site.

Take has been my project / labor / frustration / obsession for the last year or so. When I started working on it, I never expected quite the complexity I encountered when trying to make a video filter app that I would be happy with. Now that it’s starting to accumulate real users, I’ll be taking feedback and looking for ways to improve and extend it.

In any case, look for more filters and some bug fixes in the near future.

Thanks everyone!

P.S. Especially those beta testers who send feedback and comments.

Older posts