Drawing anti-aliased line - ios

I'm drawing a line on the screen which includes diagonal lines which in turn are drawing like this:
Notice how it doesn't look smooth at all. I've read a few articles on this and it would seem this looks the one closer to the issue I'm having, but the solution is not working for me.
Here's how I setup the layer:
- (void)viewDidLoad
{
[super viewDidLoad];
// Instantiate the navigation line view
CGRect navLineFrame = CGRectMake(0.0f, 120.0f, self.view.frame.size.width, 15.0f);
self.navigationLineView = [[HyNavigationLineView alloc] initWithFrame:navLineFrame];
// Make it's background transparent
self.navigationLineView.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.0f];
self.navigationLineView.opaque = NO;
HyNavigationLineLayer * layer = [[HyNavigationLineLayer alloc] init];
layer.shouldRasterize = YES;
layer.rasterizationScale = [UIScreen mainScreen].scale;
layer.contentsScale = [[UIScreen mainScreen] scale];
layer.needsDisplayOnBoundsChange = YES;
[[self.navigationLineView layer] addSublayer:layer];
[self.view addSubview:self.navigationLineView];
[self.navigationLineView.layer setNeedsDisplay];
}
Here's how I'm drawing in the layer:
- (void)drawInContext:(CGContextRef)ctx
{
CGFloat bottomInset = 1.0f / [[UIScreen mainScreen] scale] * 2.0f;
CGContextSetInterpolationQuality(ctx, kCGInterpolationHigh);
CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor);
GLfloat padding = kHyNavigationLineViewPadding * 4.0f;
GLfloat searchSpace = self.frame.size.width - padding - kHyNavigationLineViewPointerSize * 2.0f;
GLfloat x = kHyNavigationLineViewPadding * 2.0f + searchSpace * self.offset;
CGContextSetLineWidth(ctx, 2.0f);
CGContextMoveToPoint(ctx, kHyNavigationLineViewPadding, 0.0f);
CGContextAddLineToPoint(ctx, x, bottomInset);
CGContextAddLineToPoint(ctx, x + kHyNavigationLineViewPointerSize, self.frame.size.height);
CGContextAddLineToPoint(ctx, x + kHyNavigationLineViewPointerSize * 2.0f, bottomInset);
CGContextAddLineToPoint(ctx, self.frame.size.width - kHyNavigationLineViewPadding, 0.0f);
// Draw
CGContextStrokePath(ctx);
}
Any hints on how to solve this?
Edit: forgot to mention the reason I was drawing in the layer in the first place: I need to animate a property which causes this line to animate was well, so drawing in drawRect doesn't work.

Instead of using quartz to render the code, you can use UIBezierPath to render instead:
UIBezierPath* path = [UIBezierPath bezierPath];
[[UIColor whiteColor] setStroke];
GLfloat padding = kHyNavigationLineViewPadding * 4.0f;
GLfloat searchSpace = self.frame.size.width - padding - kHyNavigationLineViewPointerSize * 2.0f;
GLfloat x = kHyNavigationLineViewPadding * 2.0f + searchSpace * self.offset;
path.lineWidth = 2.0;
[path moveToPoint:CGPointMake(kHyNavigationLineViewPadding, 0)];
[path addLineToPoint:CGPointMake(x, bottomInset)];
[path addLineToPoint:CGPointMake(x + kHyNavigationLineViewPointerSize, self.frame.size.height)];
[path addLineToPoint:CGPointMake(x + kHyNavigationLineViewPointerSize * 2.0f, bottomInset)];
[path addLineToPoint:CGPointMake(self.frame.size.width - kHyNavigationLineViewPadding, 0.0f)];
[path stroke];

Related

Cutting an image to a CALayers content during an animation

I have created a subclass of CALayer to draw a slice of a pie chart with a custom color (color), a startAngle (actualStartAngle), an endAngle (actualEndAngle) and an image (image)[This image should always be in the middle of the slice] as animating this is not possible with a normal CAShapeLayer. The layer is currently drawn like this:
- (void)drawInContext:(CGContextRef)ctx{
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, self.center.x, self.center.y);
CGContextAddArc(ctx, self.center.x, self.center.y, self.radius, self.actualEndAngle, self.actualStartAngle, YES);
CGContextClosePath(ctx);
CGContextSetFillColorWithColor(ctx, self.color);
CGContextSetLineWidth(ctx, 0);
CGContextDrawPath(ctx, kCGPathFillStroke);
if (self.image) {
CGContextDrawImage(ctx, [self getFrameForImgLayerWithStartAngle:self.actualStartAngle andEndAngle:self.actualEndAngle], self.image);
}
}
These individual slices are animated by:
[CATransaction begin];
CABasicAnimation *colorAnimation = [CABasicAnimation animationWithKeyPath:#"color"];
colorAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
colorAnimation.duration = duration;
colorAnimation.fromValue = (id)self.color;
colorAnimation.toValue = (id)colorTU.CGColor;
CABasicAnimation *startAngleAnimation = [CABasicAnimation animationWithKeyPath:#"actualStartAngle"];
startAngleAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
startAngleAnimation.duration = duration;
startAngleAnimation.fromValue = [self valueForKey:#"actualStartAngle"];
startAngleAnimation.toValue = [[NSNumber alloc] initWithFloat:newStartAngle];
CABasicAnimation *endAngleAnimation = [CABasicAnimation animationWithKeyPath:#"actualEndAngle"];
endAngleAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
endAngleAnimation.duration = duration;
endAngleAnimation.fromValue = [self valueForKey:#"actualEndAngle"];
endAngleAnimation.toValue = [[NSNumber alloc] initWithFloat:newEndAngle];
if (completionBlock) {
[CATransaction setCompletionBlock:completionBlock];
}
if (colorTU && colorTU.CGColor != self.color) {
[self addAnimation:colorAnimation forKey:#"color"];
}
if (newStartAngle != self.actualStartAngle) {
[self addAnimation:startAngleAnimation forKey:#"actualStartAngle"];
}
if (newEndAngle != self.actualEndAngle) {
[self addAnimation:endAngleAnimation forKey:#"actualEndAngle"];
}
[CATransaction commit];
The problem is that I would like to have the image only take up the space that the individual layer takes up (resp. the shape in it). This is solved in general by this method:
- (BOOL)imageFitsIntoSliceWithStartAngle:(float)sA andEndAngle:(float)eA{
BOOL fits = YES;
CGRect rectToUse = [self getFrameForImgLayerWithStartAngle:sA andEndAngle:eA];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:self.center];
[path addArcWithCenter:self.center radius:self.deg startAngle:sA endAngle:eA clockwise:YES];
for (int w = 0; w <= 1; w++) {
if (fits) {
for (int h = 0; h <= 1; h++) {
if (![path containsPoint:CGPointMake(rectToUse.origin.x + rectToUse.size.width * w, rectToUse.origin.y + rectToUse.size.height * h)]) {
fits = NO;
break;
}
}
}
}
return fits;
}
The problem now is that it may fit in the end and not at the beginning of the animation, so I would have to cut the image to be as big as the slice when this happens.
I have tried the following:
set a mask for the slice instead of drawing it directly -> This is firstly bad coding and I have found it to be very energy inefficient, also it created an error (expecting model layer not copy) for me.
Any ideas on how to solve this issue? (not what I tried but this problem in general, I don't think you should do something like what I tried above)
Thanks
Don't know if it'll help, but here the drawing code for a pie chart part :
- (void)drawChartWithFrame:(CGRect)frame startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle fillColor:(UIColor *)fillColor strokeColor:(UIColor *)strokeColor
{
//// Oval Drawing
CGRect ovalRect = CGRectMake(CGRectGetMinX(frame),CGRectGetMinY(frame), CGRectGetWidth(frame), CGRectGetHeight(frame));
UIBezierPath* ovalPath = UIBezierPath.bezierPath;
[ovalPath addArcWithCenter: CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect)) radius: CGRectGetWidth(ovalRect) / 2 startAngle: -(startAngle - 25) * M_PI/180 endAngle: -(endAngle - 93) * M_PI/180 clockwise: YES];
[ovalPath addLineToPoint: CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect))];
[ovalPath closePath];ansform ovalTransform = CGAffineTransformMakeTranslation(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect));
ovalTransform = CGAffineTransformScale(ovalTransform, 1, CGRectGetHeight(ovalRect) / CGRectGetWidth(ovalRect));
[ovalPath applyTransform: ovalTransform];
[fillColor setFill];
[ovalPath fill];
[strokeColor setStroke];
ovalPath.lineWidth = 1;
[ovalPath stroke];
}
It's much lighter to animate that a real image.

Draw Triangle iOS

The code below is drawing me a circle, how can I modify the existing code to draw a triangle instead?
_colorDotLayer = [CALayer layer];
CGFloat width = self.bounds.size.width-6;
_colorDotLayer.bounds = CGRectMake(0, 0, width, width);
_colorDotLayer.allowsGroupOpacity = YES;
_colorDotLayer.backgroundColor = self.annotationColor.CGColor;
_colorDotLayer.cornerRadius = width/2;
_colorDotLayer.position = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2);
While there are shown several Core Graphics solution, I want to add a Core Animation based solution.
- (void)viewDidLoad
{
[super viewDidLoad];
UIBezierPath* trianglePath = [UIBezierPath bezierPath];
[trianglePath moveToPoint:CGPointMake(0, view3.frame.size.height-100)];
[trianglePath addLineToPoint:CGPointMake(view3.frame.size.width/2,100)];
[trianglePath addLineToPoint:CGPointMake(view3.frame.size.width, view2.frame.size.height)];
[trianglePath closePath];
CAShapeLayer *triangleMaskLayer = [CAShapeLayer layer];
[triangleMaskLayer setPath:trianglePath.CGPath];
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0,0, size.width, size.height)];
view.backgroundColor = [UIColor colorWithWhite:.75 alpha:1];
view.layer.mask = triangleMaskLayer;
[self.view addSubview:view];
}
code based on my blog post.
#implementation TriangleView {
CAShapeLayer *_colorDotLayer;
}
- (void)layoutSubviews {
[super layoutSubviews];
if (_colorDotLayer == nil) {
_colorDotLayer = [CAShapeLayer layer];
_colorDotLayer.fillColor = self.annotationColor.CGColor;
[self.layer addSublayer:_colorDotLayer];
}
CGRect bounds = self.bounds;
CGFloat radius = (bounds.size.width - 6) / 2;
CGFloat a = radius * sqrt((CGFloat)3.0) / 2;
CGFloat b = radius / 2;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, -radius)];
[path addLineToPoint:CGPointMake(a, b)];
[path addLineToPoint:CGPointMake(-a, b)];
[path closePath];
[path applyTransform:CGAffineTransformMakeTranslation(CGRectGetMidX(bounds), CGRectGetMidY(bounds))];
_colorDotLayer.path = path.CGPath;
}
- (void)awakeFromNib {
[super awakeFromNib];
self.annotationColor = [UIColor redColor];
}
#end
Result:
I think is more easy than this solutions:
UIBezierPath *path = [UIBezierPath new];
[path moveToPoint:(CGPoint){20, 0}];
[path addLineToPoint:(CGPoint){40, 40}];
[path addLineToPoint:(CGPoint){0, 40}];
[path addLineToPoint:(CGPoint){20, 0}];
// Create a CAShapeLayer with this triangular path
// Same size as the original imageView
CAShapeLayer *mask = [CAShapeLayer new];
mask.frame = self.viewTriangleCallout.bounds;
mask.path = path.CGPath;
This is my white triangle:
You can change points or rotate if you want:
UIBezierPath *path = [UIBezierPath new];
[path moveToPoint:(CGPoint){0, 0}];
[path addLineToPoint:(CGPoint){40, 0}];
[path addLineToPoint:(CGPoint){20, 20}];
[path addLineToPoint:(CGPoint){0, 0}];
// Create a CAShapeLayer with this triangular path
// Same size as the original imageView
CAShapeLayer *mask = [CAShapeLayer new];
mask.frame = self.viewTriangleCallout.bounds;
mask.path = path.CGPath;
Example code, it is based on this SO Answer which draws stars:
#implementation TriangleView
-(void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
int sides = 3;
double size = 100.0;
CGPoint center = CGPointMake(160.0, 100.0);
double radius = size / 2.0;
double theta = 2.0 * M_PI / sides;
CGContextMoveToPoint(context, center.x, center.y-radius);
for (NSUInteger k=1; k<sides; k++) {
float x = radius * sin(k * theta);
float y = radius * cos(k * theta);
CGContextAddLineToPoint(context, center.x+x, center.y-y);
}
CGContextClosePath(context);
CGContextFillPath(context); // Choose for a filled triangle
// CGContextSetLineWidth(context, 2); // Choose for a unfilled triangle
// CGContextStrokePath(context); // Choose for a unfilled triangle
}
#end
Use UIBezeierPaths
CGFloat radius = 20;
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, (center.x + bottomLeft.x) / 2, (center.y + bottomLeft.y) / 2);
CGPathAddArcToPoint(path, NULL, bottomLeft.x, bottomLeft.y, bottomRight.x, bottomRight.y, radius);
CGPathAddArcToPoint(path, NULL, bottomRight.x, bottomRight.y, center.x, center.y, radius);
CGPathAddArcToPoint(path, NULL, center.x, center.y, bottomLeft.x, bottomLeft.y, radius);
CGPathCloseSubpath(path);
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithCGPath:path];
CGPathRelease(path);

Displaying a UIBeizerPath using a CAShapeLayer vs Quartz 2D doesn't look as nice

I'm writing an app with a circular progress bar, and have recently changed it to be drawn with a couple of CAShapeLayers (one for the white background, one for the purple progress) so I can animate the purple line, rather than making new UIBeizerPaths in the drawRect of a UIView.
Having made this change, I've had some issues with how it looks now. Below is a screenshot of each way, and a diff (using “Diff” an image using ImageMagick)
My main issue with it is that it looks slightly blurry when using the CAShapeLayers - and I can't for the life of me figure out how to make it look sharper. The other issue is some of the white background is showing through the purple, but I can get around that by changing the width of the white line to be slightly less wide.
The drawRect code as it was originally written is as follows:
- (void)drawRect:(CGRect)rect {
UIBezierPath *backCircle = [UIBezierPath bezierPath];
[backCircle addArcWithCenter:CGPointMake(rect.size.width / 2, rect.size.height / 2)
radius:(rect.size.width/2.0f) - 4.0f
startAngle:(self.endAngle - self.startAngle) * _percent + self.startAngle
endAngle:self.endAngle
clockwise:YES];
backCircle.lineWidth = 5;
[[UIColor whiteColor] setStroke];
[backCircle stroke];
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath addArcWithCenter:CGPointMake(rect.size.width / 2, rect.size.height / 2)
radius:(rect.size.width/2.0f) - 4.0f
startAngle:self.startAngle
endAngle:(self.endAngle - self.startAngle) * _percent + self.startAngle
clockwise:YES];
bezierPath.lineWidth = 5;
[[UIColor purpleColor] setStroke];
[bezierPath stroke];
}
with the init code:
self.startAngle = M_PI * 1.5;
self.endAngle = self.startAngle + (M_PI * 2);
And then with the 2 layers:
- (void)setUpView
{
CGFloat startAngle = M_PI * 1.5;
CGFloat endAngle = startAngle + (M_PI * 2);
UIBezierPath *processPath = [UIBezierPath bezierPath];
[processPath addArcWithCenter:self.boundsCenter
radius:self.radius
startAngle:startAngle
endAngle:endAngle
clockwise:YES];
self.backgroundShapeLayer.path = [processPath CGPath];
self.progressShapeLayer.path = [processPath CGPath];
}
- (CGPoint)boundsCenter
{
return CGPointMake((self.bounds.size.width ) / 2.0, (self.bounds.size.height ) / 2.0);
}
- (CGFloat)radius
{
return (self.bounds.size.width / 2.0) - 4.0f;
}
- (CAShapeLayer *)progressShapeLayer
{
if (_progressShapeLayer == nil) {
_progressShapeLayer = [CAShapeLayer layer];
_progressShapeLayer.fillColor = [[UIColor clearColor] CGColor];
_progressShapeLayer.lineWidth = 5.0;
_progressShapeLayer.strokeStart = 0.0;
_progressShapeLayer.contentsScale = [[UIScreen mainScreen] scale];
[self.layer addSublayer:_progressShapeLayer];
}
return _progressShapeLayer;
}
- (CAShapeLayer *)backgroundShapeLayer
{
if (_backgroundShapeLayer == nil) {
_backgroundShapeLayer = [CAShapeLayer layer];
_backgroundShapeLayer.fillColor = [[UIColor clearColor] CGColor];
_backgroundShapeLayer.strokeColor = [[UIColor whiteColor] CGColor];
_backgroundShapeLayer.lineWidth = 5.0;
_backgroundShapeLayer.strokeStart = 0.0;
_backgroundShapeLayer.contentsScale = [[UIScreen mainScreen] scale];
[self.layer addSublayer:_backgroundShapeLayer];
}
return _backgroundShapeLayer;
}
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated
{
if(isnan(progress) || progress < 0) return;
_progress = progress;
if(!animated) {
[CATransaction setDisableActions:YES];
}
self.progressShapeLayer.strokeColor = [self.progressColour CGColor];
self.progressShapeLayer.strokeEnd = _progress;
}
I really feel like I'm going a bit crazy staring at the zoomed in screen, but it just doesn't look quite right...

RGB Values retrieved from Pixels incorrect

I've rendered a circular gradient and created a method that lets me sweep over it with my finger, using a Pan gesture recognizer.
I am retrieving the pixel at my current touch position and want to retrieve it's color.
This means, the color value should constantly update while moving over the gradient.
i'm using the following code :
- (IBAction)handlePan:(UIPanGestureRecognizer *)sender {
CGPoint translation = [sender translationInView:iv];
[sender setTranslation:CGPointZero inView:self.view];
CGPoint center = sender.view.center;
center.x += translation.x;
center.y += translation.y;
sender.view.center = center;
CGPoint colorPoint = [sender.view.superview convertPoint:center toView:iv];
[sender setTranslation:CGPointMake(0, 0) inView:self.view];
CGImageRef image = img.CGImage;
NSUInteger width = CGImageGetWidth(image);
NSUInteger height = CGImageGetHeight(image);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
char *rawData = malloc(height * width * 4);
int bytesPerPixel = 4;
int bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(
rawData, width, height, bitsPerComponent, bytesPerRow, colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big
);
CGContextSetBlendMode(context, kCGBlendModeCopy);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), image);
CGContextRelease(context);
int byteIndex = (bytesPerRow * colorPoint.y) + colorPoint.x * bytesPerPixel;
unsigned char red = rawData[byteIndex];
unsigned char green = rawData[byteIndex+1];
unsigned char blue = rawData[byteIndex+2];
UIColor *hauptfarbe = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
ch.backgroundColor = hauptfarbe;
NSLog(#"Color value - R : %i G : %i : B %i",red, green, blue);
}
this doesn't work as intended, giving me wrong colors and not showing some colors (like red) at all
EDIT : I cannot add a picture yet due to low rep. i will now add the code for rendering the gradient
Code :
- (void)viewDidLoad
{
[super viewDidLoad];
CGSize size = CGSizeMake(self.view.bounds.size.width, self.view.bounds.size.height);
UIGraphicsBeginImageContextWithOptions(CGSizeMake(size.width, size.height), YES, 0.0);
[[UIColor whiteColor] setFill];
UIRectFill(CGRectMake(0, 0, size.width, size.height));
int sectors = 180;
float radius = MIN(size.width, size.height)/2;
float angle = 2 * M_PI/sectors;
UIBezierPath *bezierPath;
for ( int i = 0; i < sectors; i++)
{
CGPoint center = CGPointMake((size.width/2), (size.height/2));
bezierPath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:i * angle endAngle:(i + 1) * angle clockwise:YES];
[bezierPath addLineToPoint:center];
[bezierPath closePath];
UIColor *color = [UIColor colorWithHue:((float)i)/sectors saturation:1. brightness:1. alpha:1];
[color setFill];
[color setStroke];
[bezierPath fill];
[bezierPath stroke];
}
img = UIGraphicsGetImageFromCurrentImageContext();
iv = [[UIImageView alloc] initWithImage:img];
[self.view addSubview:iv];
[self.view addSubview:ch];
}
The first problem here is the way you're calculating colorPoint. They way it is now, colorPoint will always be the center point of the view. This handlePan: method should get you the point of the last touch in the view:
- (IBAction)handlePan:(UIPanGestureRecognizer *)sender
{
if (sender.numberOfTouches)
{
CGPoint lastPoint = [sender locationOfTouch: sender.numberOfTouches - 1 inView: sender.view];
NSLog(#"lastPoint: %#", NSStringFromCGPoint(lastPoint));
}
}
From there, I would probably recommend that instead of blitting the image into a bitmap context and then attempting to read back from it at that point, that you just calculate the color for the point using the same mathematical process you used to create the image in the first place. The way you're doing it now is going to be much more CPU + memory intensive.
EDIT: Here's what I came up with. It works for me. Starting from the Single View Application template in Xcode, and using the code you posted, I have the following code in ViewController.m:
#implementation ViewController
{
UIImage* img;
UIImageView* iv;
}
- (void)viewDidLoad
{
[super viewDidLoad];
CGSize size = CGSizeMake(self.view.bounds.size.width, self.view.bounds.size.height);
UIGraphicsBeginImageContextWithOptions(CGSizeMake(size.width, size.height), YES, 0.0);
[[UIColor whiteColor] setFill];
UIRectFill(CGRectMake(0, 0, size.width, size.height));
int sectors = 180;
float radius = MIN(size.width, size.height)/2;
float angle = 2 * M_PI/sectors;
UIBezierPath *bezierPath;
for ( int i = 0; i < sectors; i++)
{
CGPoint center = CGPointMake((size.width/2), (size.height/2));
bezierPath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:i * angle endAngle:(i + 1) * angle clockwise:YES];
[bezierPath addLineToPoint:center];
[bezierPath closePath];
UIColor *color = [UIColor colorWithHue:((float)i)/sectors saturation:1. brightness:1. alpha:1];
[color setFill];
[color setStroke];
[bezierPath fill];
[bezierPath stroke];
}
img = UIGraphicsGetImageFromCurrentImageContext();
iv = [[UIImageView alloc] initWithImage:img];
[self.view addSubview:iv];
colorView = [[UIView alloc] init];
colorView.frame = CGRectMake(CGRectGetMaxX(bounds) - 25, CGRectGetMaxY(bounds) - 25, 20, 20);
[self.view addSubview:colorView];
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:#selector(handlePan:)];
[self.view addGestureRecognizer: panGesture];
}
- (IBAction)handlePan:(UIPanGestureRecognizer *)sender
{
if (sender.numberOfTouches)
{
CGPoint lastPoint = [sender locationOfTouch: sender.numberOfTouches - 1 inView: sender.view];
CGRect bounds = self.view.bounds;
CGPoint center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
CGPoint delta = CGPointMake(lastPoint.x - center.x, lastPoint.y - center.y);
CGFloat angle = (delta.y == 0 ? delta.x >= 0 ? 0 : M_PI : atan2(delta.y, delta.x));
angle = fmod(angle, M_PI * 2.0);
angle += angle >= 0 ? 0 : M_PI * 2.0;
UIColor *color = [UIColor colorWithHue: angle / (M_PI * 2.0) saturation:1. brightness:1. alpha:1];
colorView.backgroundColor = color;
CGFloat r,g,b,a;
if ([color getRed: &r green: &g blue:&b alpha: &a])
{
NSLog(#"Color value - R : %g G : %g : B %g", r, g, b);
}
}
}
#end
What I see is that as I drag my finger over the gradient, I get a steady stream of messages to console with RGB values that correspond to the location of my finger. I've also added code to show the last color in a small view in the lower right. It also doesn't use bitmap contexts. Hope this helps.

Draw line between two moveable circles in objective-c (iPad)

I'm new to objective-c and am trying to draw a line between moveable circles in Objective-c. I already have code that generates circles. Here is an image that I'd like to create in my app.
http://images.sciencedaily.com/2004/04/040407083832.jpg
Here's my code.
CircleViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
for (int i=0; i<5; i++) {
CGRect circleFrame = CGRectMake(arc4random() % 500, arc4random() % 500, (arc4random() % 200)+50 , (arc4random() % 200)+50);
CircleView *cirleView = [[CircleView alloc] initWithFrame: circleFrame];
cirleView.backgroundColor = [UIColor clearColor];
CGFloat hue = ( arc4random() % 256 / 256.0 ); // 0.0 to 1.0
CGFloat saturation = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from white
CGFloat brightness = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from black
UIColor *color = [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:1];
cirleView.circleColor = color;
[self.view addSubview:cirleView];
}
}
CircleView.m
-(void) drawCircle:(CGPoint)p withRadius:(CGFloat)radius inContext:(CGContextRef)contex
{
UIGraphicsPushContext(contex);
CGContextBeginPath(contex);
CGContextAddArc(contex, p.x, p.y, radius, 0, 2*M_PI, YES);
CGContextSetLineWidth(contex, 2.0);
CGContextAddLineToPoint(contex, p.x, p.y);
CGContextDrawPath(contex, kCGPathFillStroke);
UIGraphicsPopContext();
}
- (void)drawRect:(CGRect)rect
{
CGFloat size = self.bounds.size.width/2;
if(self.bounds.size.height < self.bounds.size.width) size = self.bounds.size.height / 2;
size *= 0.90;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 5.0);
[_circleColor setStroke];
[_circleColor setFill];
CGPoint point1;
point1.x = self.bounds.origin.x + self.bounds.size.width/2;
point1.y = self.bounds.origin.y + self.bounds.size.height/2;
[self drawCircle:point1 withRadius:size inContext:context];
UITapGestureRecognizer *singleFingerTap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(handleSingleTap:)];
[self addGestureRecognizer:singleFingerTap];
}
Thank you for your help.
Drawing a line:
-(void) drawLine (CGRect circleViewRect1, CGRect circleViewRect2)
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
//line width
CGContextSetLineWidth(context, 1.0);
CGContextMoveToPoint(context, circleViewRect1.center.x + circleViewRect1.bounds.width/2,circleViewRect1.center.y + circleViewRect1.bounds.height/2); //start from first circle radius
CGContextAddLineToPoint(context, circleViewRect2.center.x + circleViewRect2.bounds.width/2,circleViewRect2.center.y + circleViewRect2.bounds.height/2); //draw to this point
// and now draw the Path!
CGContextStrokePath(context);
}
Note:This is only useful if two circle's centers fall in horizontal line. Also, assume that circleViewRect1 is at the left of circleViewRect2. You must figure out the values for other use cases ie if they are at positioned at different angles etc.

Resources