Custom Charts for iPad Application - objective-c

I have to draw charts as displayed in following image for an iPad app. is there any free native chart api available for it?
Or any help to draw it...
Any help would be appreciable.

I would like to go with Quartz 2D.
I dont think so any library will do your deal. You need to create your own. Here are my ideas.
For one kind of charts, you can take idea how speedometer works. Eeww, I didnt searched links for that, hope idea will do. You can calculate angle for your percentage and draw arc accordingly. This wont be too hard.
For droplets, oops that will be bit busy. But here is some logic for that:
I guess you will have percentage for each segment in your droplet like chart.
You can calculate radius for them ! Hope image below will better explain your idea.
I am just trying to share logic and not code, since SO is not about give me code kinda stuff :)
Hope this helps

I have a working project on GitHub.
#import "PlectrumView.h"
#implementation PlectrumView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
-(void) drawPlectrumWithPercentage:(CGFloat) percentage color:(UIColor *)color
{
CGFloat factor = self.frame.size.width ;
percentage*=factor;
UIBezierPath* plectrum = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(0, factor - percentage, percentage,percentage)
byRoundingCorners: UIRectCornerTopLeft | UIRectCornerTopRight | UIRectCornerBottomRight
cornerRadii: CGSizeMake(percentage/2, percentage/2)];
[color setFill];
[plectrum fill];
}
- (void)drawRect:(CGRect)rect
{
[self drawPlectrumWithPercentage:1.0 color:[UIColor colorWithWhite:.9 alpha:1]];
[self drawPlectrumWithPercentage:.75 color:[UIColor colorWithRed:245.0/255.0 green:134.0/255.0 blue:122.0/255.0 alpha:1]];
[self drawPlectrumWithPercentage:.61 color:[UIColor colorWithRed:171.0/255.0 green:212.0/255.0 blue:105.0/255.0 alpha:1]];
}
#end
Results in
I change the plectrum code in a way that it's area will represent the percentage instead of it's width. It feels more natural.
-(void) drawPlectrumWithPercentage:(CGFloat) percentage color:(UIColor *)color
{
static CGFloat pi = 3.141592653589793;
static CGFloat radius100percent = 50.0;
static CGFloat area100percent;
static CGFloat threeFouthPiPlusOne;
area100percent= radius100percent * radius100percent * pi *3/4 + radius100percent*radius100percent;
threeFouthPiPlusOne = (1 + (pi*(3.0/4.0)));
CGFloat area = area100percent * percentage;
CGFloat newRadius = sqrt(area / threeFouthPiPlusOne);
percentage = newRadius/ 50.0;
CGFloat factor = self.frame.size.width ;
percentage*=factor;
UIBezierPath* plectrum = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(0, factor - percentage, percentage,percentage)
byRoundingCorners: UIRectCornerTopLeft | UIRectCornerTopRight | UIRectCornerBottomRight
cornerRadii: CGSizeMake(percentage/2, percentage/2)];
[color setFill];
[plectrum fill];
}

Your example pictures are actually quite easy to draw using Quartz 2D. If you are not concerned with exactly how your charts look, I would suggest looking into what is already out there. If you need the charts to look exactly as you show (for example because they should look similar to a web app), I suggest you draw them yourself. It is not that hard, to draw the kind of simple charts you show:
Use UILabels for the text
Create layers with a CGPath for the droplet shape.
Move and scale the layers with the value for each chart. They can even be animated smoothly with just a few lines of code.
Let me know if you want to try and need any more details.

There are a lot of "chart" libraries:
Check out on Cocoa Controls
or this one:
MeterView

If you use SVG, you can easily create the shapes with a function like:
/**
* #param {SVGPathElement} aShape a SVG path element
* #param {number} radius the radius of the plectrum's arc
* #param {string} fillColor color in "#rrggbb" format
*/
function drawPlectrum(aShape, radius, fillColor) {
// Creating a string which defines a path in SVG
// M = moveto, A = line to, etc. See http://www.w3.org/TR/SVG/paths.html
var ss="M "+parseInt(0)+" "+parseInt(0)+" "; // moveto 0, 0
ss+="L "+parseInt(0)+" "+parseInt(radius)+" "; // lineto 0, R
ss+="A "+parseInt(radius)+" "+parseInt(radius)+" 0 0 0 "+parseInt(radius)+" "+parseInt(2*radius)+" ";// elliptic curve to R, 2*R
ss+="A "+parseInt(radius)+" "+parseInt(radius)+" 0 0 0 "+parseInt(2*radius)+" "+parseInt(radius)+" ";// elliptic curve to 2*R, R
ss+="A "+parseInt(radius)+" "+parseInt(radius)+" 0 0 0 "+parseInt(radius)+" "+parseInt(0)+" "; // elliptic curve to R, 0
ss+="Z"; // closepath
// The d attribute of the path element holds the line information
// In XML the element will essentially be <path d="*the created string*">
aShape.setAttribute("d", ss);
// Give the color
aShape.setAttribute("fill", fillColor);
}
Successive drawings looks something like this:
// Note that 3 different path elements are used to overlap the plectrums
drawPlectrum(node, 100, "#aaaaaa");
drawPlectrum(node2, 75, "#33dd33");
drawPlectrum(node3, 50, "#3333dd");
Edit 1:
Added more comments in the code for better understandibility

Download this project and use this but they are using ARC for that.
Hope it will help you!

I think all of the above could probably be done using transparent images with an underlying shape that increases in size/diameter using mathematical code.
With the Arc Percentages you could have the dial (available area / 100) * Amount of Progress
and then have the image rotate by the amount to get the dial in place and an underlying coloured square fill by the same amount from left to right? this will save a lot on drawing code.

Related

Custom MKOverlayView/unmodified MKPolygonView is clipped at certain zoom levels

I'm having an issue with both a custom MKOverlayView and standard MKPolygonView being clipped at certain zoom levels when there are multiple overlays added to a map.
The overlay of Algeria at two double tap zoom level.
The overlay of Algeria at three double tap zoom level. Note the clipping.
A few observations:
This occurs regardless of whether or not I use a custom MKOverlayView or return an MKPolygonView with the same polygons.
If I only draw one overlay, this problem does not occur.
This does not occur for all overlays - only some.
As far as code goes: this adds the overlay to an NSMutableArray (borderOverlays), which is then accessed elsewhere to load the overlay for a specific country ID. minX/minY/maxX/maxY are latitude/longitude values; polygon is a path constructed from an ESRI shapefile.
CLLocationCoordinate2D mbrMin = CLLocationCoordinate2DMake(minY, minX);
CLLocationCoordinate2D mbrMax = CLLocationCoordinate2DMake(maxY, maxX);
MKMapPoint minPoint = MKMapPointForCoordinate(mbrMin);
MKMapPoint maxPoint = MKMapPointForCoordinate(mbrMax);
MKMapSize size = MKMapSizeMake(maxPoint.x - minPoint.x, maxPoint.y - minPoint.y);
MKMapRect rect = MKMapRectMake(minPoint.x, minPoint.y, size.width, size.height);
if ( spans180 ) {
rect = MKMapRectMake(minPoint.x, minPoint.y, MKMapSizeWorld.width * 2, size.height);
}
CustomMKOverlay* overlay = [[CustomMKOverlay alloc] initWithPolygon:polygon withBoundingMapRect:rect];
[borderOverlays addObject:overlay];
The overlay is added to the map via:
[mapView addOverlay:overlay];
viewForOverlay:
- (MKOverlayView *)mapView:(MKMapView*)aMapView viewForOverlay:(id<MKOverlay>)overlay
{
if ( [overlay isKindOfClass:[CustomMKOverlay class]] ) {
/* Note: the behaviour if this chunk is not commented is the exact same as below.
CustomMKOverlayView* overlayView = [[[CustomMKOverlayView alloc] initWithOverlay:overlay withMapView:aMapView] autorelease];
[borderViews addObject:overlayView];
return overlayView; */
MKPolygonView* view = [[[MKPolygonView alloc] initWithPolygon:((CustomMKOverlay*)overlay).polygon] autorelease];
view.fillColor = [((CustomMKOverlay*)overlay).colour colorWithAlphaComponent:0.5f];
view.lineWidth = 5.0f;
view.strokeColor = [UIColor blackColor];
[borderViews addObject:view];
return view;
}
}
When MKPolygonView is used, there is no drawing code (the example shown). For completion's sake, though, here's my custom drawing code, and the same issue occurs. The outlines normally draw - this is actually debugging drawing, which draws a rect around the boundingMapRect of the overlay and fills it without mucking around with the outlines.
- (void)drawMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale inContext:(CGContextRef)context
{
CustomMKOverlay* overlay = (CustomMKOverlay*)self.overlay;
CGRect clipRect = [self rectForMapRect:overlay.boundingMapRect];
CGContextAddRect(context, clipRect);
CGContextClip(context);
UIColor* colour = [UIColor redColor];
colour = [colour colorWithAlphaComponent:0.5f];
CGContextSetFillColorWithColor(context, [colour CGColor]);
CGRect fillRect = [self rectForMapRect:overlay.boundingMapRect];
CGContextFillRect(context, fillRect);
}
Suffice to say, I'm a bit stumped at this point - it's almost as if the zoomed tiled that's being loaded draws over the overlay. I've poured over various examples regarding TileMap and HazardMap, but as I am not loading my own map tiles, they're not very helpful.
I'm probably missing something painfully obvious. Any help would be appreciated. I'm happy to provide more code/context if necessary.
It would appear that the culprit is:
CLLocationCoordinate2D mbrMin = CLLocationCoordinate2DMake(minY, minX);
CLLocationCoordinate2D mbrMax = CLLocationCoordinate2DMake(maxY, maxX);
Bounding rectangles for MKOverlays apparently need to be based on the northwest/southeast coordinates of the bounding region, and not southwest/northeast (which is the format the ESRI shapefile stores its bounding coordinates in). Changing the offending code to:
CLLocationCoordinate2D mbrMin = CLLocationCoordinate2DMake(maxY, minX);
CLLocationCoordinate2D mbrMax = CLLocationCoordinate2DMake(minY, maxX);
Appears to resolve all issues with zooming and strange outline anomalies. I hope this helps anyone who comes across this problem in the future (and I'd like to hear about it if it doesn't, since this solution works a treat for me).
Also: if anyone can point to any documentation that states this, I'd like to see it.

How do I avoid interpolation artifacts when drawing NSImage into a different size rect?

My end goal is to fill an arbitrarily sized rectangle with an NSImage. I want to:
Fill the entire rectangle
Preserve the aspect ratio of the image
Show as much as possible of the image while maintaining 1) and 2)
When not all the image can be shown, crop toward the center.
This demonstrates what I'm trying to do. The original image of the boat at the top is drawn into various sized rectangles below.
Okay, so far so good. I added a category to NSImage to do this.
#implementation NSImage (Fill)
/**
* Crops source to best fit the destination
*
* destRect is the rect in which we want to draw the image
* sourceRect is the rect of the image
*/
-(NSRect)scaleAspectFillRect:(NSRect)destRect fromRect:(NSRect)sourceRect
{
NSSize sourceSize = sourceRect.size;
NSSize destSize = destRect.size;
CGFloat sourceAspect = sourceSize.width / sourceSize.height;
CGFloat destAspect = destSize.width / destSize.height;
NSRect cropRect = NSZeroRect;
if (sourceAspect > destAspect) { // source is proportionally wider than dest
cropRect.size.height = sourceSize.height;
cropRect.size.width = cropRect.size.height * destAspect;
cropRect.origin.x = (sourceSize.width - cropRect.size.width) / 2;
} else { // dest is proportionally wider than source (or they are equal)
cropRect.size.width = sourceSize.width;
cropRect.size.height = cropRect.size.width / destAspect;
cropRect.origin.y = (sourceSize.height - cropRect.size.height) / 2;
}
return cropRect;
}
- (void)drawScaledAspectFilledInRect:(NSRect)rect
{
NSRect imageRect = NSMakeRect(0, 0, [self size].width, [self size].height);
NSRect sourceRect = [self scaleAspectFillRect:rect fromRect:imageRect];
[[NSGraphicsContext currentContext]
setImageInterpolation:NSImageInterpolationHigh];
[self drawInRect:rect
fromRect:sourceRect
operation:NSCompositeSourceOver
fraction:1.0 respectFlipped:YES hints:nil];
}
#end
When I want to draw the image into a certain rectangle I call:
[myImage drawScaledAspectFilledInRect:onScreenRect];
Works really well except for one problem. At certain sizes the image looks quite blurry:
My first thought was that I need to draw on integral pixels, so I used NSIntegralRect() before drawing. No luck.
As I thought about it I figured that it's probably a result of the interpolation. To draw from the larger image to the smaller draw rect NSImage has to interpolate. The blurry images are likely just a case where the values don't map very well and we end up with some undesirable artifacts that can't be avoided.
So, the question is this: How do I choose an optimal rect that avoids those artifacts? I can adjust either the draw rect or the crop rect before drawing to avoid this, but I don't know how or when to adjust them.

UIView coordinate systems - offsetting and translating

I'm pretty sure this is more a of a math question, but I'll phrase it in the context of UIView and iPad-related objective C
I am importing raw data from a mapping file I have created from some public domain material downloaded elsewhere, then split out to isolate various regions within the map. Each region has a number of sub-regions, much like, for example, the continental US and then the various states which appear within the US, and then each sub-region is broken down again, into, let's say, counties.
Each state, and each county has a bounding box which tells me the origin, the width, and height each is.
In my initial setup, I created a separate view for each state, and then another view for each county. The polygon representing the area of the state/county was rendered (obviously with the county on top of the state so it would be visible) relative to a view I created through interface builder, called mainContainerView. This initial setup worked correctly.
Now I am trying to change things a bit, by adding the counties to the UIView holding the polygon for the state, so I will be able to overlay the state as a clipping mask on the counties. The problem is that no matter what I try, I cannot seem to get the county to translate to the right place within the state's view.
It seems like it should be straightforward addition or subtraction as the scaling for each item is exactly the same, and I'm not trying to do any major transformations, so I do not believe the CFAffineTransformation family is needed.
I can post code if necessary, but I'm not trying to get someone to write my program for me; I just want someone to point me in the right direction here, by giving me a suggestion on how to set the county relative to the state within the state's view.
As per a request, here's the relevant code that I am working on right now. This code does not work, but it gives you the idea as to what I'm doing. Posting sample data is a little more difficult, as it involves arrays of points and data extracted from a .SHP file designed to produce a map (and subdivisions). I'll include some comments in the code with some real point values as I step through the program to show you what's happening to them.
MASK_MAX_EASTING, MASK_MAX_NORTHING, MASK_MIN_EASTING, and MASK_MIN_NORTHING are constants which define the bounding box for the entire map of the country when made up of states.
DIST_MAX_EASTING, DIST_MAX_NORTHING, DIST_MIN_EASTING, and DIST_MIN_NORTHING are constants which define the bounding box for a map of the country when made up of the counties. The scales of the two maps are slightly different, so, by using the different bounding boxes, I've been able to scale the two maps to the same size.
-(void)didLoadMap:(NSNotification *)notification {
id region = [notification object];
ShapePolyline *polygon = [region polygon];
if ([notification name] == #"MapsLoadingForState") {
// m_nBoundingBox is an array which contains the RAW northing and easting values for each subdivision. [0] - west limit, [1] - south limit, [2] - east limit, [3] - north limit.
// The code below, combined with the drawrect method in DrawMap.m (below) puts all the states on the map in precisely the right places, so for the state maps, it works just fine.
CGFloat originX = ((polygon->m_nBoundingBox[0]-MASK_MIN_EASTING)*stateScaleMultiplier)+([mainContainerView frame].size.width/2);
CGFloat originY = ((MASK_MAX_NORTHING-(polygon->m_nBoundingBox[3]))*stateScaleMultiplier)+[mainContainerView frame].origin.y;
CGFloat width = polygon->m_nBoundingBox[2] - polygon->m_nBoundingBox[0];
CGFloat height = polygon->m_nBoundingBox[3] - polygon->m_nBoundingBox[1];
CGFloat scaledWidth = width*stateScaleMultiplier;
CGFloat scaledHeight = height*stateScaleMultiplier;
UIColor *subViewColor = [UIColor colorWithRed:0.0 green:1.0 blue:1.0 alpha:0.0];
stateMapView = [[DrawMap alloc] initWithFrame:CGRectMake(originX, originY, scaledWidth, scaledHeight)];
[stateMapView setBackgroundColor:subViewColor];
[stateMapView setStateScale:stateScaleMultiplier];
[stateMapView setCountyScale:countyScaleMultiplier]; // Not actually needed.
[stateMapView setClippingMask:polygon];
UIColor *colorMask = [UIColor colorWithWhite:1.0 alpha:1.0];
[stateMapView setForeground:colorMask];
[states addObject:stateMapView]; // Add the state map view to an array (for future use)
[mapView addSubview:stateMapView]; // MapView is a UIView of equivalent size and shape as mainContainerView.
} else {
// This is where the problems occur.
CGFloat originX = (polygon->m_nBoundingBox[0]-DIST_MIN_EASTING); // 4431590 (raw data)
originX *= countyScaleMultiplier; // 303.929108
originX += ([mainContainerView frame].size.width/2); // 815.929077
CGFloat originY = (DIST_MAX_NORTHING-polygon->m_nBoundingBox[3]); 4328997
originY *= countyScaleMultiplier; // 296.893036
originY -= [mainContainerView frame].origin.y; // 340.893036
CGRect frame = [stateMapView frame]; // Dummy variable created for watches in the debugger. x=856.237183, y=332.169922 width=34.3800087, height=28.7534008
// When I was invoking DrawMap.h and the included drawrect method, the county map would easily be displayed in the right place, as you can see by the values above.
// This is where I think the problem is. The X value is WAY off as far as I can tell.
originX -= frame.origin.x; // -40.3081055
originY -= frame.origin.y; // 8.72311401
CGPoint countyOrigin = CGPointMake(originX,originY);
// Translate the county's origin so it is relative to the origin of stateMapView, not MainContainerView (doesn't work)
[stateMapView addCountyMap:[region polygon] withColor:winner translatedBy:countyOrigin];
[stateMapView setNeedsDisplay];
}
I am aware that there are several issues with this code and some stuff outside the scope of this question may make a few of you raise an eyebrow (or two) but this is definitely a work in progress...
Here's the relevant code from DrawMap.m; I've cut a bunch of stuff out because it is extraneous.
- (void)drawRect:(CGRect)rect {
// Set up
for (int i=0;i<[countyMaps count];i++) {
// Draw the polygon.
[[countyColors objectAtIndex:i] setFill];
[self drawPolygon:[countyMaps objectAtIndex:i]
usingScale:stateScale
translatedBy:CGPointMake([[countyTranslations objectAtIndex:2*i] floatValue],
[[countyTranslations objectAtIndex:2*i+1] floatValue])];
}
// Set the blend mode to multiply
CGContextSetBlendMode(context, kCGBlendModeMultiply);
// Draw a path with clippingMask
[[UIColor colorWithWhite:1.0 alpha:1.0] setFill];
// CGPoint translate = CGPointMake(0,0);
[self drawPolygon:clippingMask usingScale:stateScale translatedBy:CGPointMake(0,0)];
}
-(void)drawPolygon:(ShapePolyline *)aPolygon usingScale:(float)mapScale translatedBy:(CGPoint)trans {
for (int j=0;j<[aPolygon numParts];j++) {
UIBezierPath *path = [UIBezierPath bezierPath];
[path setLineJoinStyle:kCGLineJoinRound];
int startIndex = [[[aPolygon m_Parts] objectAtIndex:j] intValue];
int endIndex = [aPolygon numPoints];
CGPoint startPoint;
[[[aPolygon m_Points] objectAtIndex:startIndex] getValue:&startPoint];
startPoint.x *=mapScale;
startPoint.y *=mapScale;
startPoint.x -= trans.x;
startPoint.y -= trans.y;
[path moveToPoint:startPoint];
if (j+1 != [aPolygon numParts]){
endIndex = [[[aPolygon m_Parts] objectAtIndex:j+1] intValue];
}
for (int k=startIndex+1; k<endIndex; k++)
{
CGPoint nextPoint;
[[[aPolygon m_Points] objectAtIndex:k] getValue:&nextPoint];
nextPoint.x *= mapScale;
nextPoint.y *= mapScale;
nextPoint.x -= trans.x;
nextPoint.y -= trans.y;
[path addLineToPoint:nextPoint];
}
[path closePath];
// [path stroke];
[path fill];
}
}
This tome is really may be too much information, or it may not be enough. Either way, hopefully by adding code, I've given you some information to go on...
-SOLVED-
And it was so simple. I'm surprised it took me this long to figure it out, as I was right in my initial question - it was simple addition and subtraction:
All translations are now done inside the methods which render the polygons. For each point in the polygon, I needed to add the origin of the state's view, and subtract the origin of the county's bounding box, then subtract 44 from the Y-value (the height of the control bar).
This, I think, is an example of over-thinking a problem, getting frustrated, over-thinking more, only to find out three days later that the answer is staring you in the face, waving a red flag, and shouting, "I'M OVER HERE!!!!"

Simple way of using irregular shaped buttons

I've finally got my main app release (Tap Play MMO - check it out ;-) ) and I'm now working on expanding it.
To do this I need to have a circle that has four seperate buttons in it, these buttons will essentially be quarters. I've come to the conclusion that the circlular image will need to be constructed of four images, one for each quarter, but due to the necessity of rectangular image shapes I'm going to end up with some overlap, although the overlap will be transparent.
What's the best way of getting this to work? I need something really simple really, I've looked at this
http://iphonedevelopment.blogspot.com/2010/03/irregularly-shaped-uibuttons.html
Before but not yet succeeded in getting it to work. Anyone able to offer some advice?
In case it makes any difference I'll be deploying to a iOS 3.X framework (will be 4.2 down the line when 4.2 comes out for iPad)
Skip the buttons and simply respond to touches in your view that contains the circle.
Create a CGPath for each area that you want to capture touches, when your UIview receives a touch, check for membership inside the paths.
[Edited answer to show skeleton implementation details -- TomH]
Here's how I would approach the problem: (I haven't tested this code and the syntax may not be quite right, but this is the general idea)
1) Using PS or your favorite image creation application, create one png of the quarter circles. Add it to your XCode project.
2) Add a UIView to the UI. Set the UIView's layer's contents to the png.
self.myView = [[UIView alloc] initWithRect:CGRectMake(10.0, 10.0, 100.0, 100,0)];
[myView.layer setContents:(id)[UIImage loadImageNamed:#"my.png"]];
3) Create CGPaths that describe the region in the UIView that you are interested in.
self.quadrantOnePath = CGPathCreateMutable();
CGPathMoveToPoint(self.quadrantOnePath, NULL, 50.0, 50.0);
CGPathAddLineToPoint(self.quadrantOnePath, NULL, 100.0, 50.0);
CGPathAddArc(self.quadrantOnePath, NULL, 50.0, 50.0, 50.0, 0.0, M_PI2, 1);
CGPathCloseSubpath(self.quadrantOnePath);
// create paths for the other 3 circle quadrants too!
4) Add a UIGestureRecognizer and listen/observe for taps in the view
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
[tapRecognizer setNumberOfTapsRequired:2]; // default is 1
5) When tapRecognizer invokes its target selector
- (void)handleGesture:(UIGestureRecognizer *) recognizer {
CGPoint touchPoint = [recognizer locationOfTouch:0 inView:self.myView];
bool processTouch = CGPathContainsPoint(self.quadrantOnePath, NULL, touchPoint, true);
if(processTouch) {
// call your method to process the touch
}
}
Don't forget to release everything when appropriate -- use CGPathRelease to release paths.
Another thought: If the graphic that you are using to represent your circle quadrants is simply a filled color (i.e. no fancy graphics, layer effects, etc.), you could also use the paths you created in the UIView's drawRect method to draw the quadrants too. This would address one of the failings of the approach above: there isn't a tight integration between the graphic and the paths used to check for the touches. That is, if you swap out the graphic for something different, change the size of the graphic, etc., your paths used to check for touches will be out of sync. Potentially a high maintenance piece of code.
I can't see, why overlapping is needed.
Just create 4 buttons and give each one a slice of your image.
edit after comment
see this great project. One example is exactly what you want to do.
It works by incorporating the alpha-value of a pixel in the overwritten
pointInside:withEvent: and a category on UIImage, that adds this method
- (UIColor *)colorAtPixel:(CGPoint)point {
// Cancel if point is outside image coordinates
if (!CGRectContainsPoint(CGRectMake(0.0f, 0.0f, self.size.width, self.size.height), point)) {
return nil;
}
// Create a 1x1 pixel byte array and bitmap context to draw the pixel into.
// Reference: http://stackoverflow.com/questions/1042830/retrieving-a-pixel-alpha-value-for-a-uiimage
NSInteger pointX = trunc(point.x);
NSInteger pointY = trunc(point.y);
CGImageRef cgImage = self.CGImage;
NSUInteger width = self.size.width;
NSUInteger height = self.size.height;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
int bytesPerPixel = 4;
int bytesPerRow = bytesPerPixel * 1;
NSUInteger bitsPerComponent = 8;
unsigned char pixelData[4] = { 0, 0, 0, 0 };
CGContextRef context = CGBitmapContextCreate(pixelData,
1,
1,
bitsPerComponent,
bytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextSetBlendMode(context, kCGBlendModeCopy);
// Draw the pixel we are interested in onto the bitmap context
CGContextTranslateCTM(context, -pointX, pointY-(CGFloat)height);
CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, (CGFloat)width, (CGFloat)height), cgImage);
CGContextRelease(context);
// Convert color values [0..255] to floats [0.0..1.0]
CGFloat red = (CGFloat)pixelData[0] / 255.0f;
CGFloat green = (CGFloat)pixelData[1] / 255.0f;
CGFloat blue = (CGFloat)pixelData[2] / 255.0f;
CGFloat alpha = (CGFloat)pixelData[3] / 255.0f;
return [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
}
Here's an awesome project that solves the problem of irregular shaped buttons so easily:
http://christinemorris.com/2011/06/ios-irregular-shaped-buttons/

Core Animation and Transformation like zoomRectToVisible

I'm trying to get an effect like the zoomRectToVisible-method of UIScrollview.
But my method should be able to center the particular rect in the layer while zooming and it should be able to re-adjust after the device orientation changed.
I'm trying to write a software like the marvel-comic app and need a view that presents each panel in a page.
For my implementation I'm using CALayer and Core Animation to get the desired effect with CATransform3D-transformations. My problem is, I'm not able to get the zoomed rect/panel centered.
the structure of my implementation looks like this: I have a subclass of UIScrollview with a UIView added as subview. The UIView contains the image/page in it's CALayer.contents and I use core animations to get the zooming and centering effect. The zoom effect on each panel works correcty but the centering is off. I'm not able to compute the correct translate-transformation for centering.
My code for the implementation of the effect is like this:
- (void) zoomToRect:(CGRect)rect animated:(BOOL)animated {
CGSize scrollViewSize = self.bounds.size;
// get the current panel boundingbox
CGRect panelboundingBox = CGPathGetBoundingBox([comicPage panelAtIndex:currentPanel]);
// compute zoomfactor depending on the longer dimension of the panelboundingBox size
CGFloat zoomFactor = (panelboundingBox.size.height > panelboundingBox.size.width) ? scrollViewSize.height/panelboundingBox.size.height : scrollViewSize.width/panelboundingBox.size.width;
CGFloat translateX = scrollViewSize.width/2 - (panelboundingBox.origin.x/2 + panelboundingBox.size.width/2);
CGFloat translateY = scrollViewSize.height/2 - (panelboundingBox.size.height/2 - panelboundingBox.origin.y);
// move anchorPoint to panelboundingBox center
CGPoint anchor = CGPointMake(1/contentViewLayer.bounds.size.width * (panelboundingBox.origin.x + panelboundingBox.size.width/2), 1/contentViewLayer.bounds.size.height * (contentViewLayer.bounds.size.height - (panelboundingBox.origin.y + panelboundingBox.size.height/2)));
// create the nessesary transformations
CATransform3D translateMatrix = CATransform3DMakeTranslation(translateX, -translateY, 1);
CATransform3D scaleMatrix = CATransform3DMakeScale(zoomFactor, zoomFactor, 1);
// create respective core animation for transformation
CABasicAnimation *zoomAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
zoomAnimation.fromValue = (id) [NSValue valueWithCATransform3D:contentViewLayer.transform];
zoomAnimation.toValue = (id) [NSValue valueWithCATransform3D:CATransform3DConcat(scaleMatrix, translateMatrix)];
zoomAnimation.removedOnCompletion = YES;
zoomAnimation.duration = duration;
// create respective core animation for anchorpoint movement
CABasicAnimation *anchorAnimatione = [CABasicAnimation animationWithKeyPath:#"anchorPoint"];
anchorAnimatione.fromValue = (id)[NSValue valueWithCGPoint:contentViewLayer.anchorPoint];
anchorAnimatione.toValue = (id) [NSValue valueWithCGPoint:anchor];
anchorAnimatione.removedOnCompletion = YES;
anchorAnimatione.duration = duration;
// put them into an animation group
CAAnimationGroup *group = [CAAnimationGroup animation];
group.animations = [NSArray arrayWithObjects:zoomAnimation, anchorAnimatione, nil] ;
/////////////
NSLog(#"scrollViewBounds (w = %f, h = %f)", self.frame.size.width, self.frame.size.height);
NSLog(#"panelBounds (x = %f, y = %f, w = %f, h = %f)", panelboundingBox.origin.x, panelboundingBox.origin.y, panelboundingBox.size.width, panelboundingBox.size.height);
NSLog(#"zoomfactor: %f", zoomFactor);
NSLog(#"translateX: %f, translateY: %f", translateX, translateY);
NSLog(#"anchorPoint (x = %f, y = %f)", anchor.x, anchor.y);
/////////////
// add animation group to layer
[contentViewLayer addAnimation:group forKey:#"zoomAnimation"];
// trigger respective animations
contentViewLayer.anchorPoint = anchor;
contentViewLayer.transform = CATransform3DConcat(scaleMatrix, translateMatrix);
}
So the view requires the following points:
it should be able to zoom and center a rect/panel of the layer/view depending on the current device orientation. (zoomRectToVisible of UIScrollview does not center the rect)
if nessesary (either device orientation changed or panel requires rotation) the zoomed panel/rect should be able to rotate
the duration of the animation is depending on user preference. (I don't know whether I can change the default animation duration of zoomRectToVisible of UIScrollView ?)
Those points are the reason why I overwrite the zoomRectToVisible-method of UIScrollView.
So I have to know how I can correctly compute the translation parameters for the transformation.
I hope someone can guide me to get the correct parameters.
Just skimmed over your code and this line is probably not being calculated as you think:
CGPoint anchor = CGPointMake(1/contentViewLayer.bounds.size.width * (panelboundingBox.origin.x + panelboundingBox.size.width/2), 1/contentViewLayer.bounds.size.height * (contentViewLayer.bounds.size.height - (panelboundingBox.origin.y + panelboundingBox.size.height/2)));
You're likely to get 0 because of the 1/ at the start. C will do your multiplication before this division, resulting in values <1 - probably not what you're after. See this
You might find it more useful to breakdown your calculation so you know it's working in the right order (just use some temporary variables) - believe me it will help enormously in making your code easier to read (and debug) later. Or you could just use more brackets...
Hope this helps.