
Stars at Various Scales and filled at 25, 50, 75, 100 %
Earlier we posted about implementing a star ratings slider using two images. Unfortunately this method does not work for iPhone OS 3.0 as Apple decided to stretch the two images instead of using a mask like they had done in OS 2.2.1. The new implementation uses the Quartz 2D system to draw the stars and fill them in. In the following article we will demonstrate how to do this using Quartz 2D and will provide code examples to get you going with your own custom implementations. In part one we will talk about creating a custom star that can be filled to any arbitrary percentage. Part 2 will explain how to use the custom star to create the slider.
Quartz 2D
The underlying drawing API on the iPhone (and on OS X Leopard) is Quartz. Quartz allows you to draw complex 2D images using paths to draw arbitrary shapes (including Bézier curves) and stroke and fill them. Quartz also provides you with powerful Affine Transform methods that allow you to scale, rotate, and translate your drawings. Quartz uses a painters model, so each subsequent draw operation will paint over top of your previous image. When you are building a complex image, the order in which you issue the draw commands matters. We will take advantage of this when drawing our custom fill-able star.
Drawing and Filling a Star
The first thing we want to do in order to create a star slider is to be able draw and fill a star. For this purpose we created the BAFillableStar class. This class will allow you to draw and fill a star to any arbitrary percentage (0-100%) starting from the left side.
UIColor * fillColor;
UIColor * backgroundColor;
UIColor * strokeColor;
CGFloat lineWidth;
float fillPercent;
}
@property (nonatomic, retain) UIColor * fillColor, * backgroundColor, * strokeColor;
@property (nonatomic) CGFloat lineWidth;
@property (nonatomic) float fillPercent;
@end
Before we get to the drawing, lets talk about the coordinates of the star. We want to have normalized coordinates for the 10 points that make up the star in order to be able to draw the star to any scale. In the init method of the class we want to load these into a CGPoint array. CGPoint array is used because it makes it easier to draw the path for the star.
points[0] = CGPointMake(0.5,0.025);
points[1] = CGPointMake(0.654,0.338);
points[2] = CGPointMake(1,0.388);
points[3] = CGPointMake(0.75,0.631);
points[4] = CGPointMake(0.809,0.975);
points[5] = CGPointMake(0.5,0.813);
points[6] = CGPointMake(0.191,0.975);
points[7] = CGPointMake(0.25,0.631);
points[8] = CGPointMake(0,0.388);
points[9] = CGPointMake(0.346,0.338);
}
lineWidth = 2.0; //default line width
self.strokeColor = [UIColor blackColor];
//default colors
self.fillColor = [UIColor yellowColor];
self.backgroundColor = [UIColor whiteColor];
//scale our normalized points to the dimensions of the rectangle
for (int i=0; i<10; i++) {
points[i].x = points[i].x * frame.size.width;
points[i].y = points[i].y * frame.size.height;
}
return self;
}
Remember that Quartz uses the painters method so the first thing we want to draw is the background. This is simple as all we want to do is fill the rectangle that defines the size of the view with the background colour.
{
CGContextFillRect(context, rect);
}
Then we want to draw the filled star. Remember that we are going to fill it up from the left side to an arbitrary percentage. The simplest way to do this is to define a path that draws the star and restrict drawing to the area defined by this path. This is done by using the CGContextClip method. Before we set the clipping area we want to save the graphic context as we don’t want any other subsequent drawing commands to get effected by our clipped area. At the end we restore the context to its original state.
{
CGContextBeginPath(context);
CGContextAddLines(context, points, 10);
CGContextClosePath(context);
CGContextClip(context); //clip drawing to the area defined by this path
rect.size.width = rect.size.width * fillPercent; //we want make the width of the rect
CGContextSetFillColorWithColor(context, [fillColor CGColor]);
CGContextFillRect(context, rect);
CGContextRestoreGState(context);
}
Finally we want to draw the outline of the star and stroke it with our stroke colour.
{
CGContextAddLines(context, points, 10); //create the path
CGContextClosePath(context);
//set the properties for the line
CGContextSetLineWidth(context, lineWidth);
CGContextSetStrokeColorWithColor(context, [strokeColor CGColor]);//stroke the path
CGContextStrokePath(context);
}
Putting it all together, we want to override the drawRect method of the UIView class so that we can draw our star. First we have to grab the graphic context, then we want to create a layer from this context because drawing to a layer is faster than drawing to the graphic context directly. The next step is to scale our normalized points to their correct locations using the width and height of the rectangle. We then call our draw and fill method defined above in the right order using the layers context. Finally we draw the layer to the actual graphic context and we are done.
CGContextSetShouldAntialias(context, true);CGLayerRef layer = CGLayerCreateWithContext(context, rect.size, NULL);
CGContextRef layerContext = CGLayerGetContext(layer);
[self fillBackgroundOfContext:layerContext withRect:rect];
[self fillStarInContext:layerContext withRect:rect];
[self drawStarOutlineInContext:layerContext withRect:rect];
CGContextDrawLayerInRect(context, rect, layer); //draw the layer to the actual drawing context
CGLayerRelease(layer); //release the layer
}
And that is it for Part 1, look for part 2 coming out soon.
Tags: drawing, fillable star, iPhone OS 3.0, Quartz 2D, rating slider, UIView
Comments
[...] wrote a how-to post on creating a custom fill-able star class for the iPhone using Quartz 2D at the BeefyApps Blog. I have to say I had a lot of fun writing this class. I’ll be using it in a custom [...]
[...] Part 1 we talked about creating a custom UIView class that draws fillable stars. We’ve edited the [...]
Hi! I’m new in this platform and I would like to know How can I implement this in a xcode project. I’ve created a window based project and I guess that an object of this class must be created in the viewController file but I do not understand the way it should be initialized. Could you help me with this?
Thank’s
You can download the source code for the two classes that make up the slider here:BAUIStarSlider Package
After that, either in your
loadViewmethod orviewDidLoadmethod of your view controller you can add the star slider as follows:BAUIStarSlider * slider = [[BAUIStarSlider alloc] initWithFrame:CGRectMake(0, 0, 250, 50) andStars:5];
slider.approxMode = ApproximationModeNone;
[self.view addSubview:slider];
[slider release];
This will add a star slider at position 0, 0 that is 250 wide by 50 high.
If you are adding the slider to a UIView subclass, then you can do it in its
initmethod, the only difference would be you would change[self.view addSubview:slider];to[self addSubview:slider];. Hope that helps you out.Foremost, great post! It solves a common task in iPhone development. One question though, how can one modify the above code (what change would be needed) to increase the spacing between the drawn stars?
The easiest way to do it would be to go into the init method in the BAUIStarSlider class and make the stars width and height smaller.
So
[[BAFillableStar alloc] initWithFrame:CGRectMake(0+(i*width), 0, width, height)];would become[[BAFillableStar alloc] initWithFrame:CGRectMake(0+(i*width), 0, width-spacing, height-spacing)];This will put spaces in between the stars.The next step would be in the touchesBegan method, you would need to adjust the way the value is calculated to account for the extra space.
So change the following line
value = touchPoint.x / (self.frame.size.width/numStars);such that the extra spaces you put in between the stars don’t count towards the value that is calculated based on the touch point. You will probably need to create a custom function to do this.Great post! Very informative. Is there a way to draw the stars with a transparent background? I want to put them in a UITableViewCell but when the cell is highlighted blue the stars all have white boxes around them.
Hi, thanks for sharing this. It really helped me.
I am a beginner…
Its really great app. for learners
can you provided source code application of part 1 BAFillableStar.
Greate post. but i have a simple question… how to get the values when the user do the rating??
how would i know which star the user taped !
thank you
Hi,
After you setup a slider like this:
BAUIStarSlider * slider = [[BAUIStarSlider alloc] initWithFrame:CGRectMake(0, 0, 250, 50) andStars:5];
slider.approxMode = ApproximationModeNone;
[self.view addSubview:slider];
[slider release];
You can get the value by calling
slider.value
This will get you a float value between 0 and the maximum number of stars (in the above example 5).
Hi you can find the source code for both parts on github:https://github.com/sanjeevp/BAUIStarSlider
thank you sooo much sanjeev
Post a Comment