Search code examples
iosuiviewuiimageviewicarousel

iCarousel viewForItemAtIndex using custom UIView resuses first images loaded


I have no trouble using iCarousel with UIImageViews, everything works great.

However, I wanted to add some extra drawing with the image, so I did a quick subclass of UIView that included an image and had a custom draw routine. Simple enough. However, inside of the viewForItemAtIndex, when I set the image property, it doesn't update visually. Instead it repeats the n images that represented the visible images and thus the originally loaded objects.

Example, I have 26 images, a - z. 5 visible on startup, a - e. Without the 'setNeedsReload' i get images a - e repeatedly, instead of a - z, when I scroll through the view.

I have found that if I add 'setNeedsDisplay' inside the setImage property, it will work correctly. However, there is a huge performance penalty.

My question is two fold:

  1. Am I missing something?
  2. Should I just extend UIImageView instead and do the custom drawing inside a 'processImage' style method like Nick did in FXImage?

Thanks,

/// GroupImageView.m

- (void)setImage:(UIImage *)newImage {
    image = newImage;
    [self setNeedsDisplay];
}

// override of the drawRect routine.
- (void)drawRect:(CGRect)rect {

    //  only draw if we have an image to draw
    if (image) {
        CGContextRef context = UIGraphicsGetCurrentContext();

        // blah blah, fancy drawing crap
        CGContextRotateCTM (context, radians((rand() % 5 + 2)));
        CGContextTranslateCTM(context, -0.5f * insetRect.size.width, -0.5f * insetRect.size.height);
        CGContextFillRect(context,insetRect);
        // etc

        [image drawInRect:insetRect blendMode:kCGBlendModeNormal alpha:[self alpha]];

        // etc
    } 
}

- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView *)view {

    if (view == nil) {
        view = [[GroupImageView alloc] initWithImage:[UIImage imageNamed:@"Loading.png"]];
    }
    CGImageRef ref = [group posterImage];
    UIImage* coverImage;
    if (ref) {
        coverImage = [UIImage imageWithCGImage:ref scale:1/scaleFactor orientation:UIImageOrientationUp];
    }
    else {
        coverImage = [UIImage imageNamed:@"Sunflower.png"];
    }

    [(GroupImageView*)view setImage:coverImage];
    return view;

}

Solution

  • UIImageView does not use drawRect for drawing the image because drawRect uses Core Graphics for drawing, and Core Graphics is not hardware accelerated.

    A UIView subclass that uses drawRect to draw its contents will be 100 times slower than a UIImageView, which is why you're getting performance problems.

    FXImageView gets around this by doing the drawing on a background thread and using an NSOperationQueue to make sure that images are updated in order as needed.

    Forking FXImageView and modifying the processImage method to do your drawing is not a bad idea - it will certainly be less work than recreating FXImageView's queuing and caching behaviour from scratch. Note that you'll need to modify the cache key logic as well because FXImageView's caching is based on the specific drawing attributes that it uses.

    I might consider updating FXImageView to include a custom drawing block property - it seems like something that might be useful.