Figure1: (a) original photo (b) photo with border and curl shadow |
Implementation
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 borderWidth:(CGFloat)borderWidth shadowDepth:(CGFloat)shadowHeight controlPointXOffset:(CGFloat)controlPointXOffset controlPointYOffset:(CGFloat)controlPointYOffset; @endIn this interface we define one function which receives original image, border width, depth of shadow and control point offsets. We will discuss these parameters in detail later on.
In UIImageViewBorder.m we first define configureImageViewBorder function. This function draws border and shadow around original image. Please note that I have given complete implementation of UIImageViewBorder.m at the end of this post.
-(void)configureImageViewBorder:(CGFloat)borderWidth shadowDepth:(CGFloat)shadowDepth controlPointXOffset:(CGFloat)controlPointXOffset controlPointYOffset:(CGFloat)controlPointYOffset{ CALayer* layer = [self layer]; [layer setBorderWidth:borderWidth]; [layer setBorderColor:[UIColor whiteColor].CGColor]; [self setContentMode:UIViewContentModeCenter]; [layer setShadowColor:[UIColor blackColor].CGColor]; [layer setShadowOffset:CGSizeMake(0.0, 4.0)]; [layer setShadowRadius:3.0]; [layer setShadowOpacity:0.4]; layer.shadowPath = [self curlShadowPathWithShadowDepth:shadowDepth controlPointXOffset:controlPointXOffset controlPointYOffset:controlPointYOffset]; }In this function we first retrieve CALayer of UIImageView. We then set the border width and border color of UIImageView layer. We also set the content mode to center. This property sets the image in center if image size is smaller than the UIImageView. You can change it to other values in order to automatically scale the image to fit UIImageView size. We now set shadow properties of UIImageView layer. First we define shadow color as black. This can be set to any other color if required. We then set shadow offset value to (0.0, 4.0). This value draws shadow at the bottom of the image with no shadow on horizontal axis. The shadow radius defines the blur radius. In this case we have set it to 3.0. We only want to display slight shadow underneath the image. Therefore we have set the shadow opacity value to 0.4. Next we call curlShadowPathWithShadowDepth function to create curl shadow.
Polygon with cubic bezier curve
Before delving into the code we shall discuss how curl shadow is created using Bezier path. Bezier path can be used to create different shapes such as lines, polygons and curves etc. In this scenario we create shadow path which is polygon with cubic curve at the bottom of the image as shown in figure 2.
Figure 2: Curl shadow behind image. |
Figure 3: Bezier path using Gimp |
- Move to point (10,10). It is labelled as PolygonTopLeft.
- Add a line starting from point (10,10) to point (90,10). It is labelled as PolygonTopRight.
- Add a line starting from point (90,10) to point (90,90). The end point is labelled as PolygonBottomRight.
- Add a line starting from point (90,90) to point (10,90). The end point is labelled as PolygonBottomLeft.
- Close path from point (10,90) to starting point (10,10).
- Click on the bottom line between points PolygonBottomLeft and PolygonBottomRight.
- Change the location of control points CPoint1 to (70,60) and CPoint2 to (30,60). Cubic curve is created as we change the location of control points.
-(CGPathRef)curlShadowPathWithShadowDepth:(CGFloat)shadowDepth controlPointXOffset:(CGFloat)controlPointXOffset controlPointYOffset:(CGFloat)controlPointYOffset { CGSize viewSize = [self bounds].size; CGPoint polyTopLeft = CGPointMake(0.0, controlPointYOffset); CGPoint polyTopRight = CGPointMake(viewSize.width, controlPointYOffset); CGPoint polyBottomLeft = CGPointMake(0.0, viewSize.height + shadowDepth); CGPoint polyBottomRight = CGPointMake(viewSize.width, viewSize.height + shadowDepth); CGPoint controlPointLeft = CGPointMake(controlPointXOffset , controlPointYOffset); CGPoint controlPointRight = CGPointMake(viewSize.width - controlPointXOffset, controlPointYOffset); UIBezierPath* path = [UIBezierPath bezierPath]; [path moveToPoint:polyTopLeft]; [path addLineToPoint:polyTopRight]; [path addLineToPoint:polyBottomRight]; [path addCurveToPoint:polyBottomLeft controlPoint1:controlPointRight controlPoint2:controlPointLeft]; [path closePath]; return path.CGPath; }This function gets three parameters: shadowDepth, controlPointXOffset and controlPointYOffset. ShadowDepth is used to define the shadow area outside of the image which we want to display at the bottom of the image. controlPointXOffset and controlPointYOffset parameters are used to define two control points. After receiving input parameters we first get the bounds of the UIImageView. We then set the four vertices of the polygon which we will use as shadow path. polyTopLeft is set to (0.0, controlPointYOffset). Please note that we are using controlPointYOffset as the Y coordinate of topLeft point. The reason is that in this scenario we place the control point at same height as the polygon. Similarly topRight point is set to (viewSize.width, controlPointYOffset). BottomLeft point is set to (0.0, viewSize.height + shadowDepth). If you notice we have added shadowDepth in Y coordinate of BottomLeft point. The reason is that the shadow path covers some area at the bottom of the image. Similarly we have set the bottomRight point to (viewSize.width, viewSize.height + shadowDepth). Now we want to define two control points. First control point is set to (controlPointXOffset, controlPointYOffset) and second control point is set to (viewSize.width - controlPointXOffset, controlPointYOffset). We now use the four vertices and the two control points to define the shadow path. We use UIBezierPath to define polygon and cubic Bezier curve. The process of drawing Bezier path is similar to the process which we have discussed in Figure 3. We first move to topLeft point. We draw a line from topLeft to topRight. We then draw a line from topRight to bottomRight. We draw a curve from bottomRight to bottomLeft using two control points. We then use [path closePath] function to draw line from bottomLeft to topLeft. This process is shown in figure 4.
Figure 4: shadow path drawing |
-(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; }
All of the above functions are called from setImage function which we define in UIImageViewBorder.h interface file.
-(void)setImage:(UIImage*)image borderWidth:(CGFloat)borderWidth shadowDepth:(CGFloat)shadowDepth controlPointXOffset:(CGFloat)controlPointXOffset controlPointYOffset:(CGFloat)controlPointYOffset { self.backgroundColor = [UIColor lightGrayColor]; [self configureImageViewBorder:borderWidth shadowDepth:shadowDepth controlPointXOffset:controlPointXOffset controlPointYOffset:controlPointYOffset]; UIImage* scaledImage = [self rescaleImage:image]; self.image = scaledImage; }This function sets the background color of the UIImageView to gray. This color can be changed to any other color as required. It then calls configureImageViewBorder function to draw border and shadow. It then scales the image if required and sets it to the image property of UIImageView.
How to Use
We are now ready to use UIImageViewBorder.h and UIImageViewBorder.m. Create an application with UIViewController. Add UIImageView control on UIViewController. Create UIImageViewBorder.h and UIImageViewBorder.m files. Copy implementation of these files as given at the end of this post. Now in viewDidLoad you can call this function.
-(void)viewDidLoad{ UIImage* image = [UIImage imageNamed:@"dog"]; [imageView setImage:image borderWidth:3.0 shadowDepth:10.0 controlPointXOffset:30.0 controlPointYOffset:70.0]; }In this particular example I have used UIImageView of size(100,100). I have given shadowDepth to 10.0, controlPointXOffset to 30.0 and controlPointYOffset to 70.0. You might think how can we find suitable control point values. In my experience one way to find is to create an empty image in Gimp or any other image editing tool. Create a polygon as I have shown in Figure 3 and adjust the control point positions to get your desired result.
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 example I am using default value which is UIViewAutoresizingNone. I have set this value using interface builder. Please feel free to contribute if you know how to fix this problem.
Here is the complete implementation of UIImageViewBorder.h and UIImageViewBorder.m.
// // UIImageViewBorder.h // CustomTableView // #import <Foundation/Foundation.h> @interface UIImageView (ImageViewBorder) -(void)setImage:(UIImage*)image borderWidth:(CGFloat)borderWidth shadowDepth:(CGFloat)shadowHeight controlPointXOffset:(CGFloat)controlPointXOffset controlPointYOffset:(CGFloat)controlPointYOffset; @end
// // UIImageViewBorder.m // CustomTableView // #import "UIImageViewBorder.h" #import "QuartzCore/QuartzCore.h" @interface UIImageView (private) -(UIImage*)rescaleImage:(UIImage*)image; -(void)setImage:(UIImage*)image withBorderWidth:(CGFloat)borderWidth shadowDepth:(CGFloat)shadowDepth controlPointXOffset:(CGFloat)controlPointXOffset controlPointYOffset:(CGFloat)controlPointYOffset; -(CGPathRef)curlShadowPathWithShadowDepth:(CGFloat)shadowDepth controlPointXOffset:(CGFloat)controlPointXOffset controlPointYOffset:(CGFloat)controlPointYOffset; @end @implementation UIImageView (ImageViewBorder) -(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; } -(void)configureImageViewBorder:(CGFloat)borderWidth shadowDepth:(CGFloat)shadowDepth controlPointXOffset:(CGFloat)controlPointXOffset controlPointYOffset:(CGFloat)controlPointYOffset{ CALayer* layer = [self layer]; [layer setBorderWidth:borderWidth]; [self setContentMode:UIViewContentModeCenter]; [layer setBorderColor:[UIColor whiteColor].CGColor]; [layer setShadowColor:[UIColor blackColor].CGColor]; [layer setShadowOffset:CGSizeMake(0.0, 4.0)]; [layer setShadowRadius:3.0]; [layer setShadowOpacity:0.4]; layer.shadowPath = [self curlShadowPathWithShadowDepth:shadowDepth controlPointXOffset:controlPointXOffset controlPointYOffset:controlPointYOffset]; } -(CGPathRef)curlShadowPathWithShadowDepth:(CGFloat)shadowDepth controlPointXOffset:(CGFloat)controlPointXOffset controlPointYOffset:(CGFloat)controlPointYOffset { CGSize viewSize = [self bounds].size; CGPoint polyTopLeft = CGPointMake(0.0, controlPointYOffset); CGPoint polyTopRight = CGPointMake(viewSize.width, controlPointYOffset); CGPoint polyBottomLeft = CGPointMake(0.0, viewSize.height + shadowDepth); CGPoint polyBottomRight = CGPointMake(viewSize.width, viewSize.height + shadowDepth); CGPoint controlPointLeft = CGPointMake(controlPointXOffset , controlPointYOffset); CGPoint controlPointRight = CGPointMake(viewSize.width - controlPointXOffset, controlPointYOffset); UIBezierPath* path = [UIBezierPath bezierPath]; [path moveToPoint:polyTopLeft]; [path addLineToPoint:polyTopRight]; [path addLineToPoint:polyBottomRight]; [path addCurveToPoint:polyBottomLeft controlPoint1:controlPointRight controlPoint2:controlPointLeft]; [path closePath]; return path.CGPath; } -(void)setImage:(UIImage*)image borderWidth:(CGFloat)borderWidth shadowDepth:(CGFloat)shadowDepth controlPointXOffset:(CGFloat)controlPointXOffset controlPointYOffset:(CGFloat)controlPointYOffset { self.backgroundColor = [UIColor lightGrayColor]; [self configureImageViewBorder:borderWidth shadowDepth:shadowDepth controlPointXOffset:controlPointXOffset controlPointYOffset:controlPointYOffset]; UIImage* scaledImage = [self rescaleImage:image]; self.image = scaledImage; } @end
Thanks for sharing. It works well for me.
ReplyDeleteexcellent tutorial. thx.
ReplyDeletedoes it work with rectangular image?
ReplyDeleteHi awesome article!
ReplyDeleteIt worked really well on my emulator, but when I deployed to the device 4S (iOS 5). It crashed on the line where you specify the shadowPath and pass the CGFloat values...i couldn't figure out why :(
[SOLVED] UIImageView curl shadow effect using shadowPath shadowPath crash.
ReplyDeleteDownload the source code here
http://iphonesdkpro.com/%5BSOLVED%5D+UIImageView+curl+shadow+effect+using+shadowPath+shadowPath+crash
Fixed shadowPath crash
ReplyDeleteAnother try at the download url
http://bit.ly/L729FS
GREAT!
ReplyDelete