Detecting Touch On UIView - objective-c

Hello All I drawn Pie Chart on a UIView using CGContext of 10 Arcs (pieces or slice).
But When I touch on the 1st arc and last arc of this piechart i dont get touch point from them...rest of all arcs of this piechart supports touch event....
why this is happening m stuck of that... please help me...to get out this...
here is my code
-(void) drawRect:(CGRect)rect
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(ctx, 1.0, 1.0, 1.0, 1.0);
CGContextSetLineWidth(ctx, 2.0);
float mult = 360/total;
// Draw each pie slice
float curValue = 0.0f;
for(int i=0; i< 10; i++)
{
int startAngle = (int)(curValue * mult);
int arcAngle = (int) ([[sorted objectAtIndex:i] floatValue]* mult);
// Ensure that rounding errors do not leave a gap between the first and last slice
if ( count == [sorted count] ) { arcAngle = 360 - startAngle; }
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, x, y );
CGContextAddArc(ctx, x, y, r, startAngle * M_PI/180.0, (startAngle * M_PI/180.0) + (arcAngle * M_PI/180), 0);
CGContextClosePath(ctx);
CGContextSetStrokeColorWithColor(ctx, [[UIColor lightGrayColor] CGColor]);
CGContextDrawPath(ctx, kCGPathFillStroke ); curValue += [[sorted objectAtIndex:i] floatValue];
}
}
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
CABasicAnimation *rotation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
double startRotationValue = [[[self.layer presentationLayer] valueForKeyPath:#"transform.rotation.z"] doubleValue];
rotation.fromValue = [NSNumber numberWithDouble:startRotationValue];
rotation.toValue = [NSNumber numberWithDouble:startRotationValue+5];
rotation.duration = 2.0;
[self.layer addAnimation:rotation forKey:#"rotating"];
}
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesBegan:touches withEvent:event];
}
Thanks in Advance.......

Related

iOS Quartz Changing stroke color

I am new to iOS and am doing this as a learning experience. I got most of this code from a tutorial and its working but I wanted to add more stroke colors. My code begins with a default stroke in UIColor greenColor, but I have two buttons labeled "Red" and "Blue" that should change the stroke color to Red or Blue when pressed. I want to press Red, then start drawing and have that stroke come out red, etc. like in MS Paint. I've confirmed my UIButtons are linked up correctly. Any help is appreciated, thanks!
DrawView.h:
#import <UIKit/UIKit.h>
#interface DrawView : UIImageView
- (void)changeOption:(NSString *)withOption;
#end
DrawView.m:
#import "DrawView.h"
#interface DrawView()
#property (nonatomic) BOOL fingerMoved;
#property (nonatomic) CGPoint lastPoint;
#end
#implementation DrawView
CGFloat red = 0.0, green = 0.0, blue = 0.0, width = 5.0;
#synthesize fingerMoved = _fingerMoved;
#synthesize lastPoint = _lastPoint;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void)changeOption:(NSString *)withOption
{
if ([withOption isEqualToString:#"Black"]) {
red = 0.0; green = 0.0; blue = 0.0; width = 5.0;
}else if ([withOption isEqualToString:#"Red"]) {
red = 1.0; green = 0.0; blue = 0.0; width = 5.0;
}else if ([withOption isEqualToString:#"Blue"]) {
red = 0.0; green = 0.0; blue = 1.0; width = 5.0;
}else if ([withOption isEqualToString:#"Green"]) {
red = 0.0; green = 1.0; blue = 0.0; width = 5.0;
}else if ([withOption isEqualToString:#"Brown"]) {
red = 0.4; green = 0.0; blue = 0.0; width = 5.0;
}else if ([withOption isEqualToString:#"Orange"]) {
red = 1.0; green = 0.6; blue = 0.0; width = 5.0;
}else if ([withOption isEqualToString:#"Yellow"]) {
red = 1.0; green = 1.0; blue = 0.0; width = 5.0;
}else if ([withOption isEqualToString:#"Purple"]) {
red = 0.5; green = 0.0; blue = 1.0; width = 5.0;
}else if ([withOption isEqualToString:#"Eraser"]) {
red = 1.0; green = 1.0; blue = 1.0; width = 13.0;
}else if ([withOption isEqualToString:#"Clear"]) {
self.image = nil;
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
self.fingerMoved = NO;
UITouch *touch = [touches anyObject];
if ([touch tapCount] == 2) {
self.image = nil;
return;
}
self.lastPoint = [touch locationInView:self];
self.lastPoint = CGPointMake(self.lastPoint.x, self.lastPoint.y - 20);
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
self.fingerMoved = YES;
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self];
currentPoint.y -= 20;
UIGraphicsBeginImageContext(self.frame.size);
[self.image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), width);
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), red, green, blue, 1.0);
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), self.lastPoint.x, self.lastPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());
self.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.lastPoint = currentPoint;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if ([touch tapCount] == 2) {
self.image = nil;
return;
}
if (!self.fingerMoved) {
UIGraphicsBeginImageContext(self.frame.size);
[self.image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), width);
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), red, green, blue, 1.0);
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), self.lastPoint.x, self.lastPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), self.lastPoint.x, self.lastPoint.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());
self.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#end
Silly mistake in some other code. Fixed now.

Paint app for ipad [opengl-es] line strokes not proper

I have a strange problem with openGL. I am working on paint app for both iphone and ipad. I am using opengl-es for my app. In my app I am filling colors in outline images, drawings a line onscreen based on where the user touches. I just use the "touchesMoved" function to draw a line between two points. Since I would like lines to stay on screen, in my renderLineFromPoint function, but for some reason some of the points of the line just drop out, and it appears completely random. However ipad simulator and iphone device/simulator gives desired output. Line stroke appears as shown in figure.
I am creating buffer using following code:
- (BOOL)createFramebuffer{
// Generate IDs for a framebuffer object and a color renderbuffer
glGenFramebuffersOES(1, &viewFramebuffer);
glGenRenderbuffersOES(1, &viewRenderbuffer);
glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
// This call associates the storage for the current render buffer with the EAGLDrawable (our CAEAGLLayer)
// allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view).
[context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self.layer];
glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
NSLog(#"Backing Width:%i and Height: %i", backingWidth, backingHeight);
// For this sample, we also need a depth buffer, so we'll create and attach one via another renderbuffer.
glGenRenderbuffersOES(1, &depthRenderbuffer);
glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES)
{
NSLog(#"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
return NO;
}
return YES;
}
I am using following code snippet for renderLineFromPoint
- (void) renderLineFromPoint:(CGPoint)start toPoint:(CGPoint)end{
static GLfloat* vertexBuffer = NULL;
static NSUInteger vertexMax = 64;
NSUInteger vertexCount = 0,
count,
i;
[EAGLContext setCurrentContext:context];
glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
// Convert locations from Points to Pixels
//CGFloat scale = self.contentScaleFactor;
CGFloat scale;
if ([self respondsToSelector: #selector(contentScaleFactor)])
{
scale=self.contentScaleFactor;
}
else{
//scale = 1.000000;
if ([[UIScreen mainScreen] respondsToSelector:#selector(scale)] == YES && [[UIScreen mainScreen] scale] == 2.00) {
// RETINA DISPLAY
scale = 2.000000;
}
else {
scale = 1.000000;
}
}
NSLog(#"start point %#", NSStringFromCGPoint(start));
NSLog(#"End Point %#", NSStringFromCGPoint(end));
start.x *= scale;
start.y *= scale;
end.x *= scale;
end.y *= scale;
// Allocate vertex array buffer
if(vertexBuffer == NULL)
// vertexBuffer = malloc(vertexMax * 2 * sizeof(GLfloat));
vertexBuffer = malloc(vertexMax * 2 * sizeof(GLfloat));
// Add points to the buffer so there are drawing points every X pixels
count = MAX(ceilf(sqrtf((end.x - start.x) * (end.x - start.x) + (end.y - start.y) * (end.y - start.y)) / kBrushPixelStep), 1);
NSLog(#"count %d",count);
for(i = 0; i < count; ++i) {
if(vertexCount == vertexMax) {
vertexMax = 2 * vertexMax;
vertexBuffer = realloc(vertexBuffer, vertexMax * 2 * sizeof(GLfloat));
NSLog(#"if loop");
}
vertexBuffer[2 * vertexCount + 0] = start.x + (end.x - start.x) * ((GLfloat)i / (GLfloat)count);
vertexBuffer[2 * vertexCount + 1] = start.y + (end.y - start.y) * ((GLfloat)i / (GLfloat)count);
vertexCount += 1;
}
NSLog(#"Scale vertex %f",scale);
//NSLog(#"%i",vertexCount);
// Render the vertex array
glVertexPointer(2, GL_FLOAT, 0, vertexBuffer);
glDrawArrays(GL_POINTS, 0, vertexCount);
// Display the buffer
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];
}
touchbegan function code:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
CGRect bounds = [self bounds];
UITouch* touch = [[event touchesForView:self] anyObject];
firstTouch = YES;
// Convert touch point from UIView referential to OpenGL one (upside-down flip)
location = [touch locationInView:self];
location.y = bounds.size.height - location.y;
}
touchmoved function code:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
CGRect bounds = [self bounds];
UITouch* touch = [[event touchesForView:self] anyObject];
// Convert touch point from UIView referential to OpenGL one (upside-down flip)
if (firstTouch) {
firstTouch = NO;
previousLocation = [touch previousLocationInView:self];
previousLocation.y = bounds.size.height - previousLocation.y;
} else {
location = [touch locationInView:self];
location.y = bounds.size.height - location.y;
previousLocation = [touch previousLocationInView:self];
previousLocation.y = bounds.size.height - previousLocation.y;
}
// Render the stroke
[self renderLineFromPoint:previousLocation toPoint:location];
}
I had a similar issue with missing points and it was related to the drawableProprties of my CAEAGLLayer.
Within CAEAGLLayer's drawableProperties, do you have the kEAGLDrawablePropertyRetainedBacking set to YES? If not then the Backing of your drawing is not being retained from frame to frame which can cause missing points and flickering.

How can i zoom in and out in a UIImageView without UIScrollView?

I'm developing an app for iOS 4.2, iPhone, in this app I download images and I save them in the internal storage (NSDocuments).
Well, then I show the first image in a UIImageView. The user can drag their finger on the UIImageView (TouchesMoved), when the user do that, I load other image. If the user drag down, I load one image, if drag up, i load other, and also with right and left.
This is all done. But I want to implement zooming. This is my code until now:
initialDistance --> is the distance between the fingers at first touch
finalDistance --> is the distance between the fingers each time they move
x --> is 0
y --> is 0
// this method calculate the distance between 2 fingers
- (CGFloat)distanceBetweenTwoPoints:(CGPoint)fromPoint toPoint:(CGPoint)toPoint {
float xPoint = toPoint.x - fromPoint.x;
float yPoint = toPoint.y - fromPoint.y;
return sqrt(xPoint * xPoint + yPoint * yPoint);
}
//------------------- Movimientos con los dedos ------------------------------------
#pragma mark -
#pragma mark UIResponder
// First Touch
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSSet *allTouches = [event allTouches];
switch ([allTouches count]) {
case 1: { //Single touch
//Get the first touch.
UITouch *touch1 = [[allTouches allObjects] objectAtIndex:0];
switch ([touch1 tapCount])
{
case 1: //Single Tap.
{
// Guardo la primera localización del dedo cuando pulsa por primera vez
//inicial = [touch1 locationInView:self.view];
} break;
case 2: {//Double tap.
//Track the initial distance between two fingers.
//if ([[allTouches allObjects] count] >= 2) {
// oculto/o no, la barra de arriba cuando se hace un dobleTap
//[self switchToolBar];
} break;
}
} break;
case 2: { //Double Touch
// calculo la distancia inicial que hay entre los dedos cuando empieza a tocar
UITouch *touch1 = [[allTouches allObjects] objectAtIndex:0];
UITouch *touch2 = [[allTouches allObjects] objectAtIndex:1];
initialDistance = [self distanceBetweenTwoPoints:[touch1 locationInView:[self view]]
toPoint:[touch2 locationInView:[self view]]];
}
default:
break;
}
}
// when the finger/s move to
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
NSSet *allTouches = [event allTouches];
switch ([allTouches count])
{
case 1: {
} break;
case 2: {
//The image is being zoomed in or out.
UITouch *touch1 = [[allTouches allObjects] objectAtIndex:0];
UITouch *touch2 = [[allTouches allObjects] objectAtIndex:1];
//Calculate the distance between the two fingers.
CGFloat finalDistance = [self distanceBetweenTwoPoints:[touch1 locationInView:[self view]]
toPoint:[touch2 locationInView:[self view]]];
NSLog(#"Distancia Inicial :: %.f, Ditancia final :: %.f", initialDistance, finalDistance);
float factorX = 20.0;
float factorY = 11.0;
// guardo la posicion de los 2 dedos
//CGPoint dedo1 = [[[touches allObjects] objectAtIndex:0] locationInView:self.view];
//CGPoint dedo2 = [[[touches allObjects] objectAtIndex:1] locationInView:self.view];
// comparo para saber si el usuario esta haciendo zoom in o zoom out
if(initialDistance < finalDistance) {
NSLog(#"Zoom In");
float newWidth = imagen.frame.size.width + (initialDistance - finalDistance + factorX);
float newHeight = imagen.frame.size.height + (initialDistance - finalDistance + factorY);
if (newWidth <= 960 && newHeight <= 640) {
/*
if (dedo1.x >= dedo2.x) {
x = (dedo1.x + finalDistance/2);
y = (dedo1.y + finalDistance/2);
} else {
x = (dedo2.x + finalDistance/2);
y = (dedo2.y + finalDistance/2);
}
*/
//x = (dedo1.x);
//y = (dedo1.y);
imagen.frame = CGRectMake( x, y, newWidth, newHeight);
} else {
imagen.frame = CGRectMake( x, y, 960, 640);
}
}
else {
NSLog(#"Zoom Out");
float newWidth = imagen.frame.size.width - (finalDistance - initialDistance + factorX);
float newHeight = imagen.frame.size.height - (finalDistance - initialDistance + factorY);
if (newWidth >= 480 && newHeight >= 320) {
/*
if (dedo1.x >= dedo2.x) {
x = (dedo1.x + finalDistance/2);
y = (dedo1.y + finalDistance/2);
} else {
x = (dedo2.x + finalDistance/2);
y = (dedo2.y + finalDistance/2);
}
*/
//x -= (finalDistance - initialDistance + factorX);
//y -= (finalDistance - initialDistance + factorX);
//x = (dedo1.x);
//y = (dedo1.y);
imagen.frame = CGRectMake( x, y, newWidth, newHeight);
} else {
imagen.frame = CGRectMake( 0, 0, 480, 320);
}
}
initialDistance = finalDistance;
} break;
}
}
#pragma mark -
Thank you very much!!
PD: If there is a method with UIScrollView whitch I can move between different images, I m open to take a look too.
Ok. You can consider using a UIScrollView if only to use it for its zoom functionality.
Say we have a scrollView and a imageView with both having the same bounds. Add the imageView as the subview of scrollView.
[scrollView addSubview:imageView];
scrollView.contentSize = imageView.frame.size;
To support only zoom and not not panning in the scrollView your viewController will have to adopt the UIScrollViewDelegate protocol.
// Disabling panning/scrolling in the scrollView
scrollView.scrollEnabled = NO;
// For supporting zoom,
scrollView.minimumZoomScale = 0.5;
scrollView.maximumZoomScale = 2.0;
...
// Implement a single scroll view delegate method
- (UIView*)viewForZoomingInScrollView:(UIScrollView *)aScrollView {
return imageView;
}
By now we have zooming available. For swipes, you can use appropriately configured UISwipeGestureRecognizers. Create a gesture for handling each swipe direction and add it to scroll view.
UISwipeGestureRecognizer *rightSwipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self selector:#selector(handleRightSwipe:)];
rightSwipe.direction = UISwipeGestureRecognizerDirectionRight;
rightSwipe.numberOfTouchesRequired = 1;
[scrollView addGesture:rightSwipe];
[rightSwipe release];
And retrieve the appropriate image based on the gesture and set it using imageView.image = yourImage;.
Finally, with Deepak's help, I use the transform property of UIImageView to make the Zooming.
I use the CGFloat at the position [0,0] in the matrix of the CGAffineTransform to set the limits. I have to pass the CGFloat to a string at zoom in because when i compare it with 0 this is always true.
Finally this is my code in touchesMoved, if it's useful for someone:
// comparo para saber si el usuario esta haciendo zoom in o zoom out
if(initialDistance < finalDistance) {
NSLog(#"Zoom Out");
CGAffineTransform transformer = CGAffineTransformScale(imagen.transform, 1.05, 1.05);
NSLog(#"transformer :: A: %.f, B: %.f, C: %.f, D: %.f", imagen.transform.a, imagen.transform.b, imagen.transform.c, imagen.transform.d);
if (transformer.a < 5) {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration: 0.2];
imagen.transform = transformer;
[UIView setAnimationDelegate:self];
[UIView commitAnimations];
}
}
else {
NSLog(#"Zoom In");
CGAffineTransform transformer = CGAffineTransformScale(imagen.transform, 0.95, 0.95);
NSLog(#"transformer :: A: %.f, B: %.f, C: %.f, D: %.f", transformer.a, transformer.b, transformer.c, transformer.d);
NSString *num = [NSString stringWithFormat:#"%.f", transformer.a];
if (![num isEqualToString:#"0"]) {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration: 0.2];
imagen.transform = transformer;
[UIView setAnimationDelegate:self];
[UIView commitAnimations];
}
}
And of course, a lot of thanks to Deepak.

Retrigger CABasicAnimation with NSThread in iOS

I am hacking this Core Animation example:
https://github.com/joericioppo/Shape_05
I want to retrigger the animation in response to an NSThread. The NSLogs show up as expected, but the animation itself does not trigger until all sleeps have completed, even though its method has verifiably been called. How can I retrigger this animation every second?
By the way - this is just an example patch I am working on in order to understand the concept. I know that animations can be set to repeat, but this isn't what I want to do, for various reasons. Here's the code, any help would be most welcome, thanks.
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#interface Shape_05ViewController : UIViewController {
CALayer *rootLayer;
CAShapeLayer *shapeLayer;
CGMutablePathRef pentagonPath;
CGMutablePathRef starPath;
int animationCount;
CABasicAnimation *animation;
}
-(void)startAnimation;
-(void) viewDidLoad;
-(void) startSequence;
-(void) enactSequence;
#end
#import "Shape_05ViewController.h"
#implementation Shape_05ViewController
- (void)loadView
{
UIView *appView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
appView.backgroundColor = [UIColor blackColor];
self.view = appView;
[appView release];
rootLayer = [CALayer layer];
rootLayer.frame = self.view.bounds;
[self.view.layer addSublayer:rootLayer];
//Pentagon Path
pentagonPath = CGPathCreateMutable();
CGPoint center = self.view.center;
CGPathMoveToPoint(pentagonPath, nil, center.x, center.y - 75.0);
for(int i = 1; i < 5; ++i) {
CGFloat x = - 75.0 * sinf(i * 2.0 * M_PI / 5.0);
CGFloat y = - 75.0 * cosf(i * 2.0 * M_PI / 5.0);
CGPathAddLineToPoint(pentagonPath, nil,center.x + x, center.y + y);
}
CGPathCloseSubpath(pentagonPath); //This seems to be fixed in 4.0
//Star Path
starPath = CGPathCreateMutable();
center = self.view.center;
CGPathMoveToPoint(starPath, NULL, center.x, center.y + 75.0);
for(int i = 1; i < 5; ++i) {
CGFloat x = 75.0 * sinf(i * 4.0 * M_PI / 5.0);
CGFloat y = 75.0 * cosf(i * 4.0 * M_PI / 5.0);
CGPathAddLineToPoint(starPath, NULL, center.x + x, center.y + y);
};
CGPathCloseSubpath(starPath);
//Create Shape
shapeLayer = [CAShapeLayer layer];
shapeLayer.path = pentagonPath;
UIColor *fillColor = [UIColor colorWithWhite:0.9 alpha:1.0];
shapeLayer.fillColor = fillColor.CGColor;
shapeLayer.fillRule = kCAFillRuleNonZero;
[rootLayer addSublayer:shapeLayer];
}
-(void) viewDidLoad {
NSLog(#"viewDidLoad");
animationCount = 0;
[NSThread detachNewThreadSelector:#selector(startSequence) toTarget:self withObject:nil];
}
-(void) startSequence {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self enactSequence];
//[NSThread sleepForTimeInterval:100];
[pool release];
}
-(void) enactSequence {
NSLog(#"enactSequence");
for(int i = 0; i < 4; i++) {
[NSThread sleepForTimeInterval:1];
[self performSelector:#selector(startAnimation) withObject:nil];
[rootLayer setNeedsDisplay];
NSLog(#"%i", i);
};
}
-(void)startAnimation
{
NSLog(#"startAnimation");
animation = [CABasicAnimation animationWithKeyPath:#"path"];
animation.duration = 0.25;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.repeatCount = 0;
animation.autoreverses = NO;
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = YES;
if( (animationCount % 2) == 0) {
animation.fromValue = (id)pentagonPath;
animation.toValue = (id)starPath;
} else {
animation.fromValue = (id)starPath;
animation.toValue = (id)pentagonPath;
};
[shapeLayer addAnimation:animation forKey:#"animatePath"];
animationCount++;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (void)dealloc
{
CGPathRelease(starPath);
CGPathRelease(pentagonPath);
[super dealloc];
}
#end
OK after another Google trawl:
http://forums.macrumors.com/showthread.php?t=749518&highlight=cabasicanimation
I found out about this thing called NSTimer! Another search of this site revealed some good info:
How do I use NSTimer?
And about running NSTimer in its own thread:
Keep an NSThread containing an NSTimer around indefinitely? (iPhone)
So now I can offer this updated code example, which I hope may be useful to someone one day. By the way, I didn't try it yet, but apparently the following can be used to stop the timer:
[myTimer invalidate];
myTimer = nil;
Thanks to all here at this site, not only the most beautifully designed corner of the web but also a total lifesaver!
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#interface Shape_05ViewController : UIViewController {
CALayer *rootLayer;
CAShapeLayer *shapeLayer;
CGMutablePathRef pentagonPath;
CGMutablePathRef starPath;
int animationCount;
CABasicAnimation *animation;
NSTimer *repeatingTimer;
}
#property (assign, readwrite) NSTimer *repeatingTimer;
-(void) startAnimation: (NSTimer*)theTimer;
-(void) viewDidLoad;
-(void) startSequence;
#end
#import "Shape_05ViewController.h"
#implementation Shape_05ViewController
#synthesize repeatingTimer;
- (void)loadView
{
UIView *appView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
appView.backgroundColor = [UIColor blackColor];
self.view = appView;
[appView release];
rootLayer = [CALayer layer];
rootLayer.frame = self.view.bounds;
[self.view.layer addSublayer:rootLayer];
//Pentagon Path
pentagonPath = CGPathCreateMutable();
CGPoint center = self.view.center;
CGPathMoveToPoint(pentagonPath, nil, center.x, center.y - 75.0);
for(int i = 1; i < 5; ++i) {
CGFloat x = - 75.0 * sinf(i * 2.0 * M_PI / 5.0);
CGFloat y = - 75.0 * cosf(i * 2.0 * M_PI / 5.0);
CGPathAddLineToPoint(pentagonPath, nil,center.x + x, center.y + y);
}
CGPathCloseSubpath(pentagonPath); //This seems to be fixed in 4.0
//Star Path
starPath = CGPathCreateMutable();
center = self.view.center;
CGPathMoveToPoint(starPath, NULL, center.x, center.y + 75.0);
for(int i = 1; i < 5; ++i) {
CGFloat x = 75.0 * sinf(i * 4.0 * M_PI / 5.0);
CGFloat y = 75.0 * cosf(i * 4.0 * M_PI / 5.0);
CGPathAddLineToPoint(starPath, NULL, center.x + x, center.y + y);
};
CGPathCloseSubpath(starPath);
//Create Shape
shapeLayer = [CAShapeLayer layer];
shapeLayer.path = pentagonPath;
UIColor *fillColor = [UIColor colorWithWhite:0.9 alpha:1.0];
shapeLayer.fillColor = fillColor.CGColor;
shapeLayer.fillRule = kCAFillRuleNonZero;
[rootLayer addSublayer:shapeLayer];
}
-(void) viewDidLoad {
NSLog(#"viewDidLoad");
animationCount = 0;
[NSThread detachNewThreadSelector:#selector(startSequence) toTarget:self withObject:nil];
}
-(void) startSequence {
NSLog(#"startSequence");
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(startAnimation:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] run];
self.repeatingTimer = timer;
[pool release];
}
-(void)startAnimation: (NSTimer*)theTimer {
NSLog(#"startAnimation");
animation = [CABasicAnimation animationWithKeyPath:#"path"];
animation.duration = 0.25;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.repeatCount = 0;
animation.autoreverses = NO;
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
if( (animationCount % 2) == 0) {
animation.fromValue = (id)pentagonPath;
animation.toValue = (id)starPath;
} else {
animation.fromValue = (id)starPath;
animation.toValue = (id)pentagonPath;
};
[shapeLayer addAnimation:animation forKey:#"animatePath"];
animationCount++;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (void)dealloc
{
CGPathRelease(starPath);
CGPathRelease(pentagonPath);
[super dealloc];
}
#end

Making a circular NSSlide look and behave like in GarageBand

I am new to drawing with Cocoa, and I am making some software which will have sliders similar to these found in GarageBand:
GB Sliders http://img33.imageshack.us/img33/2668/schermafbeelding2010061r.png
These look beautiful and can be controld by moving the mouse up and down.
Can you help me with customizing NSSliders by subclassing them, so I can make them look and behave exactly as in GarageBand? Thanks.
I have one image for the knob which should be rotated as they do not need to be in 3D .
The simplest way is to create a NSView subclass that handles both the mouse management and the drawing.
There is a sample code that can help you to start named "TLayer". It is part of the Examples of the XCode 3.1.4. It contains a circular custom view that controls the offset and the radius of the shadow drawn for layers. It is easy to understand and easy to extend.
Note: as it does not seems to be available on the Apple website, so I have pasted the sources below.
ShadowOffsetView.h
#import <AppKit/AppKit.h>
extern NSString *ShadowOffsetChanged;
#interface ShadowOffsetView : NSView
{
CGSize _offset;
float _scale;
}
- (float)scale;
- (void)setScale:(float)scale;
- (CGSize)offset;
- (void)setOffset:(CGSize)offset;
#end
ShadowOffsetView.m
#import "ShadowOffsetView.h"
NSString *ShadowOffsetChanged = #"ShadowOffsetChanged";
#interface ShadowOffsetView (Internal)
- (NSCell *)cell;
#end
#implementation ShadowOffsetView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self == nil)
return nil;
_offset = CGSizeZero;
return self;
}
- (void)dealloc
{
[super dealloc];
}
- (float)scale
{
return _scale;
}
- (void)setScale:(float)scale
{
_scale = scale;
}
- (CGSize)offset
{
return CGSizeMake(_offset.width * _scale, _offset.height * _scale);
}
- (void)setOffset:(CGSize)offset
{
offset = CGSizeMake(offset.width / _scale, offset.height / _scale);
if (!CGSizeEqualToSize(_offset, offset)) {
_offset = offset;
[self setNeedsDisplay:YES];
}
}
- (BOOL)isOpaque
{
return NO;
}
- (void)setOffsetFromPoint:(NSPoint)point
{
float radius;
CGSize offset;
NSRect bounds;
bounds = [self bounds];
offset.width = (point.x - NSMidX(bounds)) / (NSWidth(bounds) / 2);
offset.height = (point.y - NSMidY(bounds)) / (NSHeight(bounds) / 2);
radius = sqrt(offset.width * offset.width + offset.height * offset.height);
if (radius > 1) {
offset.width /= radius;
offset.height /= radius;
}
if (!CGSizeEqualToSize(_offset, offset)) {
_offset = offset;
[self setNeedsDisplay:YES];
[(NSNotificationCenter *)[NSNotificationCenter defaultCenter]
postNotificationName:ShadowOffsetChanged object:self];
}
}
- (void)mouseDown:(NSEvent *)event
{
NSPoint point;
point = [self convertPoint:[event locationInWindow] fromView:nil];
[self setOffsetFromPoint:point];
}
- (void)mouseDragged:(NSEvent *)event
{
NSPoint point;
point = [self convertPoint:[event locationInWindow] fromView:nil];
[self setOffsetFromPoint:point];
}
- (void)drawRect:(NSRect)rect
{
NSRect bounds;
CGContextRef context;
float x, y, w, h, r;
bounds = [self bounds];
x = NSMinX(bounds);
y = NSMinY(bounds);
w = NSWidth(bounds);
h = NSHeight(bounds);
r = MIN(w / 2, h / 2);
context = [[NSGraphicsContext currentContext] graphicsPort];
CGContextTranslateCTM(context, x + w/2, y + h/2);
CGContextAddArc(context, 0, 0, r, 0, 2*M_PI, true);
CGContextClip(context);
CGContextSetGrayFillColor(context, 0.910, 1);
CGContextFillRect(context, CGRectMake(-w/2, -h/2, w, h));
CGContextAddArc(context, 0, 0, r, 0, 2*M_PI, true);
CGContextSetGrayStrokeColor(context, 0.616, 1);
CGContextStrokePath(context);
CGContextAddArc(context, 0, -2, r, 0, 2*M_PI, true);
CGContextSetGrayStrokeColor(context, 0.784, 1);
CGContextStrokePath(context);
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, r * _offset.width, r * _offset.height);
CGContextSetLineWidth(context, 2);
CGContextSetGrayStrokeColor(context, 0.33, 1);
CGContextStrokePath(context);
}
#end
Well, for the actual drawing you'd either have to have images for each rotation angle of the knob (easier to implement) and then just draw the proper one.
(While for a real realistic 3d look—even if possible—programmatic drawing wouldn't be worth its time, I guess.)
Or draw the knob by code. This article should give you an idea I think:
http://katidev.com/blog/2008/03/07/how-to-create-a-custom-control-with-nsview/
(For both, the mouse event handling and basic NSBezerPath drawing of circular and rotating knob-like elements)