Objective-c – Fade in/out UIScrollView’s content like Mobile Safari does in its tab


NOTE: I added my new solution at the UPDATE answer below.

I try to recreate the effects we saw in Mobile Safari's tab on iPhone/iPod touch.

Basically, it's a UIScrollView that holds 4 reusable UIView (acting like ring buffer), and scrolls horizontally. When scrolling, the UIView's opacity will fade in/out seamlessly with offset.

Currently, I do all the dirty job in - (void)scrollViewDidScroll:(UIScrollView *)scrollView. Such as getting the contentOffset from UIScrollView, determine the pageingation, calculate the delta between contentOffset and each UIView positions, and decide/set the alpha value of each UIView at the moment when scrollViewDidScroll: was called.

And it works, performance is okay, everything runs smoothly, but the only problem is too much calculating.

I tried UIView's beginAnimations: and commitAnimations:, but it's useless in scrollViewDidScroll:, since 1) the new alpha value still have to be calculate by myself, and 2) scrollViewDidScroll: kept called when scrolling, it's meaningless to do the animation here. Is there any way to rewrite this part with Core Animation? So I don't have to do the math myself on alpha value of each UIView, instead, I can left whole works to Core Animation.

Best Solution


I came up with another way to update alpha value of each page. When creating content view, I use KVO to addObserver with UIScrollView's contentOffset:

[self.scrollView addObserver:[self.contentViews objectAtIndex:i] 

So that each of my contentViews will get the message when contentOffset changed:

- (void)observeValueForKeyPath:(NSString *)keyPath 
                        change:(NSDictionary *)change 
                       context:(void *)context {
    [self performSelector:(SEL)context withObject:change];

And my contentViews can do the calculation and animate by themselves when scrolling:

- (void)updateAlpha:(NSDictionary *)change {
    CGFloat offset = [[change objectForKey:NSKeyValueChangeNewKey] CGPointValue].x;
    CGFloat origin = [self frame].origin.x;
    CGFloat delta = fabs(origin - offset);

    [UIView beginAnimations:@"Fading" context:nil];
    if (delta < [self frame].size.width) {
        self.alpha = 1 - delta/self.frame.size.width*0.7;
    } else {
        self.alpha = 0.3;
    [UIView commitAnimations];

Of course, you will need to remove the observer when you remove the content view from superview, and add them again when new content view created.

Hope this help to those who want to do the same.