Sunday, 19 June 2011

UIImageView with border and shadow

In iPhone or iPad applications we sometimes need to display images or photos with borders and shadows around them. There are few ways to achieve this but generally I tend to use UIImageView. There are few reasons why I prefer this solution. First according to iOS documentation, UIImageView is optimized to display images. Second it is less lines of code. I will discuss how we can convert image as shown in figure 1 to image with border and shadow in figure 2. Once you understand the basic concept of drawing borders and shadows I would invite you to read my next post: UIImageView curl shadow effect using shadowPath. This post discusses how to render shadows efficiently.

Figure1: image without border.
Figure 2: image with white border and a shadow.

First we extend UIImageView interface with some functions. We create an objective-c class using XCode File new wizard. We name our files UIImageViewBorder.h and UIImageViewBorder.m. In UIImageViewBorder.h file we define our interface as shown below:
#import <Foundation/Foundation.h >

@interface UIImageView (ImageViewBorder)
-(void)setImage:(UIImage*)image withBorderWidth:(CGFloat)borderWidth;
@end
In this interface we define one function which receives original image such as image in figure 1 with some border width. Now we define the implementation of this class in UIImageViewBorder.m file. 
#import "UIImageViewBorder.h"
#import "QuartzCore/QuartzCore.h"

@implementation UIImageView (ImageViewBorder)

@end
First we will define a function to create a border around an image view. This step is simple as shown in code below:
-(void)configureImageViewBorder:(CGFloat)borderWidth{
    CALayer* layer = [self layer];
    [layer setBorderWidth:borderWidth];
    [self setContentMode:UIViewContentModeCenter];
    [layer setBorderColor:[UIColor whiteColor].CGColor];
    [layer setShadowOffset:CGSizeMake(-3.0, 3.0)];
    [layer setShadowRadius:3.0];
    [layer setShadowOpacity:1.0];
}
In this function we get the layer object contained by UIImageView. We then set the border width and set the content mode to center. We define the border color to white and set the shadow offset to (-3.0, 3.0). The shadow radius is 3.0 and opacity is 1.0. Please note that these values can be changed as needed. Now with this function we can set the border around UIImageView. However we will find a slight problem with this. For example if UIImageView size is (100, 100) and image size is also (100,100). If we draw a border of width say 10.0 around UIImageView then some part of the image will be hidden by the border as shown in figure 3.
Figure 3: Part of image hidden by border with width 10.0
To solve this problem we scale down the original image according to the area which is available after drawing the border. We define a function rescaleImage which scales the image according to available image area.
-(UIImage*)rescaleImage:(UIImage*)image{
    UIImage* scaledImage = image;
    
    CALayer* layer = self.layer;
    CGFloat borderWidth = layer.borderWidth;
    
    //if border is defined 
    if (borderWidth > 0)
    {
        //rectangle in which we want to draw the image.
        CGRect imageRect = CGRectMake(0.0, 0.0, self.bounds.size.width - 2 * borderWidth,self.bounds.size.height - 2 * borderWidth);
        //Only draw image if its size is bigger than the image rect size.
        if (image.size.width > imageRect.size.width || image.size.height > imageRect.size.height)
        {
            UIGraphicsBeginImageContext(imageRect.size);
            [image drawInRect:imageRect];
            scaledImage = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();
        }        
    }
    return scaledImage;
}
In this function we first get the border width of UIImageView. We only want to scale down the image if border width is defined for UIImageView. We calculate the available image area inside UIImageView. If image is already scaled down or less than the size of available image area then we do not perform this operation as image can fit in the area without processing. If image is bigger than the available image then we define an image graphics context with the size of available image area. We then draw original image into this area.
We define public function setImage which calls configureImageViewBorder and rescaleImage to draw border and image scaling.
-(void)setImage:(UIImage*)image withBorderWidth:(CGFloat)borderWidth
{
    [self configureImageViewBorder:borderWidth];
    UIImage* scaledImage = [self rescaleImage:image];
    self.image = scaledImage;
}

configureImageViewBorder and rescaleImage functions are internal functions so we make them private by defining interface in UIImageViewBorder.m file as follows:
@interface UIImageView (private)
-(UIImage*)rescaleImage:(UIImage*)image;
-(void)configureImageViewBorder:(CGFloat)borderWidth;
@end

We are now ready to use this function. We can use this on UIImageView as follows:
UIImage* image = [UIImage imageNamed:@"dog"];
[imageView setImage:image withBorderWidth:10.0];
In the above code we are assuming that the UIImageView is already created.

Note:
I have noticed that the border of UIImageView does not update properly if autoresizingMask property of UIImageView is set to either UIViewAutoresizingFlexibleWidth or UIViewAutoresizingFlexibleHeight or both. In above examples I am using default value which is UIViewAutoresizingNone. Please feel free to contribute if you know how to fix this problem. Thanks.

4 comments:

  1. Congratulations on a well-designed website and blog, and on superb presentations.

    ReplyDelete
  2. I'm trying this on iOS 6 and while I can get the border to draw the image does not display. I get no error. I used storyboard to add the UIImageView and command-click to create an IBOutlet. I've even gone back and literally copy pasted every line you have above incase I typed something in wrong.

    Could it be something with iOS 6?

    UIImageViwBorder.h: http://pastebin.com/QH84wRyu
    UIImageViwBorder.m: http://pastebin.com/qHJJS1Ju

    ViewController.h: http://pastebin.com/Fu4heWDh
    ViewController.m: http://pastebin.com/Fu4heWDh

    Screenshot: http://imagr.eu/up/509829e327b482_iOS_Simulator_Screen_shot_Nov_5,_2012_4.04.23_PM.png

    Any help would be greatly appreciated.

    ReplyDelete
  3. Great tutorial, exactly what i was looking for. Thanks!

    ReplyDelete
  4. Still working on 2017 versions (IOS10+,code8+), This is great, thank you, really helpful.

    ReplyDelete