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

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.

Related

cocso2d pinch-zoom like Fieldrunners

I've been trying to set up the parallax node correctly but need some help with this.
This http://goo.gl/Piqy5 would be the frame of the game and for the parallax node area I have 3 layers:
background layer (zoomable but not scrollable, z order -1)
layer 1 (z order 1)
layer 2 (z order 2)
//Adding the layers to the parallax node
CGPoint offsetLayer = ccp(0,0);
//background layer
[parallaxNode addChild:backgroundLayer z:-1 parallaxRatio: ccp(0,0) positionOffset: offsetLayer];
//layer 1
[parallaxNode addChild:secondParallaxLayer z:1 parallaxRatio: ccp(0.5,0) positionOffset: offsetLayer];
//layer 2
[parallaxNode addChild:firstParallaxLayer z:2 parallaxRatio: ccp(1.1,0) positionOffset: offsetLayer];
//the pan/zoom & scroll controller
_controller = [[CCPanZoomController controllerWithNode:baseLayer] retain];
_controller.boundingRect = boundingRect;
_controller.zoomOutLimit = _controller.optimalZoomOutLimit;
_controller.zoomInLimit = 2.0f;
[_controller centerOnPoint:CGPointMake(screenSize.width/2.0, screenSize.height/2.0)];
[_controller enableWithTouchPriority:-2 swallowsTouches:YES];
I think I should fix using:
//Setting the touch delegate to my CCScene
#interface GameScene : CCScene <CCStandardTouchDelegate>
//and add register to touch delegate
[[CCTouchDispatcher sharedDispatcher] addStandardDelegate:self priority:2];
- (CGPoint)convertPoint:(CGPoint)point fromNode:(CCNode *)node {
return [self convertToNodeSpace:[node convertToWorldSpace:point]];
}
- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
#define CLAMP(x,y,z) MIN(MAX(x,y),z)
if ([[[event allTouches] allObjects] count] == 2) {
UITouch* touch1 = [[[event allTouches] allObjects] objectAtIndex:0];
UITouch* touch2 = [[[event allTouches] allObjects] objectAtIndex:1];
// calculate scale value
double prevDistance = ccpDistance([touch1 previousLocationInView:[touch1 view]], [touch2 previousLocationInView:[touch2 view]]);
double newDistance = ccpDistance([touch1 locationInView:[touch1 view]], [touch2 locationInView:[touch2 view]]);
CGFloat relation = newDistance / prevDistance;
CGFloat newScale = self.scale * relation;
if ((newScale >= MIN_SCALE) && (newScale <= MAX_SCALE)) {
CGPoint touch1Location = [baseLayer convertTouchToNodeSpace:touch1];
CGPoint touch2Location = [baseLayer convertTouchToNodeSpace:touch2];
// calculate center point between two touches
CGPoint centerPoint = ccpMidpoint(touch1Location, touch2Location);
// store center point location (ScrollableView space)
CGPoint centerPointInParentNodeSpace = [self convertPoint:centerPoint fromNode:baseLayer];
CGPoint oldPoint = ccp(centerPointInParentNodeSpace.x * (self.scale), centerPointInParentNodeSpace.y * (self.scale));
self.scale = newScale;
CGPoint newPoint = ccp(centerPointInParentNodeSpace.x * (self.scale), centerPointInParentNodeSpace.y * (self.scale));
CGPoint diff = ccp(oldPoint.x - newPoint.x , oldPoint.y - newPoint.y);
[baseLayer setPosition:ccp(baseLayer.position.x + (diff.x*(1/self.scale)), baseLayer.position.y + (diff.y*(1/self.scale)))];
}
} else if ([[[event allTouches] allObjects] count] == 1) {
// get touch locations
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchPosition = [[CCDirector sharedDirector] convertToGL:[touch locationInView:[touch view]]];
CGPoint oldPosition = [[CCDirector sharedDirector] convertToGL:[touch previousLocationInView:[touch view]]];
// calculate difference in position
CGPoint diff = ccpSub(touchPosition, oldPosition);
self.position = ccpAdd(self.position, diff);
}
#undef CLAMP
}
Any remarks or help would be great! :)
Check out CCLayerPanZoom in the Cocos2d Extensions on Github. I have used this class to enable pinch zoom and pan in my game

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.

Detecting Touch On UIView

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.......

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)

How to zoom in/out an UIImage object when user pinches screen?

I would like to zoom in/out an UIImage object when the user performs the standard pinch action on my application. I'm currently using a UIImageView to display my image, if that detail helps in any way.
I'm trying to figure out how to do this, but no such luck so far.
Any clues?
As others described, the easiest solution is to put your UIImageView into a UIScrollView. I did this in the Interface Builder .xib file.
In viewDidLoad, set the following variables. Set your controller to be a UIScrollViewDelegate.
- (void)viewDidLoad {
[super viewDidLoad];
self.scrollView.minimumZoomScale = 0.5;
self.scrollView.maximumZoomScale = 6.0;
self.scrollView.contentSize = self.imageView.frame.size;
self.scrollView.delegate = self;
}
You are required to implement the following method to return the imageView you want to zoom.
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.imageView;
}
In versions prior to iOS9, you may also need to add this empty delegate method:
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale
{
}
The Apple Documentation does a good job of describing how to do this:
Another easy way to do this is to place your UIImageView within a UIScrollView. As I describe here, you need to set the scroll view's contentSize to be the same as your UIImageView's size. Set your controller instance to be the delegate of the scroll view and implement the viewForZoomingInScrollView: and scrollViewDidEndZooming:withView:atScale: methods to allow for pinch-zooming and image panning. This is effectively what Ben's solution does, only in a slightly more lightweight manner, as you don't have the overhead of a full web view.
One issue you may run into is that the scaling within the scroll view comes in the form of transforms applied to the image. This may lead to blurriness at high zoom factors. For something that can be redrawn, you can follow my suggestions here to provide a crisper display after the pinch gesture is finished. hniels' solution could be used at that point to rescale your image.
Shefali's solution for UIImageView works great, but it needs a little modification:
- (void)pinch:(UIPinchGestureRecognizer *)gesture {
if (gesture.state == UIGestureRecognizerStateEnded
|| gesture.state == UIGestureRecognizerStateChanged) {
NSLog(#"gesture.scale = %f", gesture.scale);
CGFloat currentScale = self.frame.size.width / self.bounds.size.width;
CGFloat newScale = currentScale * gesture.scale;
if (newScale < MINIMUM_SCALE) {
newScale = MINIMUM_SCALE;
}
if (newScale > MAXIMUM_SCALE) {
newScale = MAXIMUM_SCALE;
}
CGAffineTransform transform = CGAffineTransformMakeScale(newScale, newScale);
self.transform = transform;
gesture.scale = 1;
}
}
(Shefali's solution had the downside that it did not scale continuously while pinching. Furthermore, when starting a new pinch, the current image scale was reset.)
Below code helps to zoom UIImageView without using UIScrollView :
-(void)HandlePinch:(UIPinchGestureRecognizer*)recognizer{
if ([recognizer state] == UIGestureRecognizerStateEnded) {
NSLog(#"======== Scale Applied ===========");
if ([recognizer scale]<1.0f) {
[recognizer setScale:1.0f];
}
CGAffineTransform transform = CGAffineTransformMakeScale([recognizer scale], [recognizer scale]);
imgView.transform = transform;
}
}
Keep in mind that you're NEVER zooming in on a UIImage. EVER.
Instead, you're zooming in and out on the view that displays the UIImage.
In this particular case, you chould choose to create a custom UIView with custom drawing to display the image, a UIImageView which displays the image for you, or a UIWebView which will need some additional HTML to back it up.
In all cases, you'll need to implement touchesBegan, touchesMoved, and the like to determine what the user is trying to do (zoom, pan, etc.).
Here is a solution I've used before that does not require you to use the UIWebView.
- (UIImage *)scaleAndRotateImage(UIImage *)image
{
int kMaxResolution = 320; // Or whatever
CGImageRef imgRef = image.CGImage;
CGFloat width = CGImageGetWidth(imgRef);
CGFloat height = CGImageGetHeight(imgRef);
CGAffineTransform transform = CGAffineTransformIdentity;
CGRect bounds = CGRectMake(0, 0, width, height);
if (width > kMaxResolution || height > kMaxResolution) {
CGFloat ratio = width/height;
if (ratio > 1) {
bounds.size.width = kMaxResolution;
bounds.size.height = bounds.size.width / ratio;
}
else {
bounds.size.height = kMaxResolution;
bounds.size.width = bounds.size.height * ratio;
}
}
CGFloat scaleRatio = bounds.size.width / width;
CGSize imageSize = CGSizeMake(CGImageGetWidth(imgRef), CGImageGetHeight(imgRef));
CGFloat boundHeight;
UIImageOrientation orient = image.imageOrientation;
switch(orient) {
case UIImageOrientationUp: //EXIF = 1
transform = CGAffineTransformIdentity;
break;
case UIImageOrientationUpMirrored: //EXIF = 2
transform = CGAffineTransformMakeTranslation(imageSize.width, 0.0);
transform = CGAffineTransformScale(transform, -1.0, 1.0);
break;
case UIImageOrientationDown: //EXIF = 3
transform = CGAffineTransformMakeTranslation(imageSize.width, imageSize.height);
transform = CGAffineTransformRotate(transform, M_PI);
break;
case UIImageOrientationDownMirrored: //EXIF = 4
transform = CGAffineTransformMakeTranslation(0.0, imageSize.height);
transform = CGAffineTransformScale(transform, 1.0, -1.0);
break;
case UIImageOrientationLeftMirrored: //EXIF = 5
boundHeight = bounds.size.height;
bounds.size.height = bounds.size.width;
bounds.size.width = boundHeight;
transform = CGAffineTransformMakeTranslation(imageSize.height, imageSize.width);
transform = CGAffineTransformScale(transform, -1.0, 1.0);
transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
break;
case UIImageOrientationLeft: //EXIF = 6
boundHeight = bounds.size.height;
bounds.size.height = bounds.size.width;
bounds.size.width = boundHeight;
transform = CGAffineTransformMakeTranslation(0.0, imageSize.width);
transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
break;
case UIImageOrientationRightMirrored: //EXIF = 7
boundHeight = bounds.size.height;
bounds.size.height = bounds.size.width;
bounds.size.width = boundHeight;
transform = CGAffineTransformMakeScale(-1.0, 1.0);
transform = CGAffineTransformRotate(transform, M_PI / 2.0);
break;
case UIImageOrientationRight: //EXIF = 8
boundHeight = bounds.size.height;
bounds.size.height = bounds.size.width;
bounds.size.width = boundHeight;
transform = CGAffineTransformMakeTranslation(imageSize.height, 0.0);
transform = CGAffineTransformRotate(transform, M_PI / 2.0);
break;
default:
[NSException raise:NSInternalInconsistencyException format:#"Invalid image orientation"];
}
UIGraphicsBeginImageContext(bounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
if (orient == UIImageOrientationRight || orient == UIImageOrientationLeft) {
CGContextScaleCTM(context, -scaleRatio, scaleRatio);
CGContextTranslateCTM(context, -height, 0);
}
else {
CGContextScaleCTM(context, scaleRatio, -scaleRatio);
CGContextTranslateCTM(context, 0, -height);
}
CGContextConcatCTM(context, transform);
CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, width, height), imgRef);
UIImage *imageCopy = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return imageCopy;
}
The article can be found on Apple Support at:
http://discussions.apple.com/message.jspa?messageID=7276709#7276709
Shafali and JRV's answers extended to include panning and pinch to zoom:
#define MINIMUM_SCALE 0.5
#define MAXIMUM_SCALE 6.0
#property CGPoint translation;
- (void)pan:(UIPanGestureRecognizer *)gesture {
static CGPoint currentTranslation;
static CGFloat currentScale = 0;
if (gesture.state == UIGestureRecognizerStateBegan) {
currentTranslation = _translation;
currentScale = self.view.frame.size.width / self.view.bounds.size.width;
}
if (gesture.state == UIGestureRecognizerStateEnded || gesture.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [gesture translationInView:self.view];
_translation.x = translation.x + currentTranslation.x;
_translation.y = translation.y + currentTranslation.y;
CGAffineTransform transform1 = CGAffineTransformMakeTranslation(_translation.x , _translation.y);
CGAffineTransform transform2 = CGAffineTransformMakeScale(currentScale, currentScale);
CGAffineTransform transform = CGAffineTransformConcat(transform1, transform2);
self.view.transform = transform;
}
}
- (void)pinch:(UIPinchGestureRecognizer *)gesture {
if (gesture.state == UIGestureRecognizerStateEnded || gesture.state == UIGestureRecognizerStateChanged) {
// NSLog(#"gesture.scale = %f", gesture.scale);
CGFloat currentScale = self.view.frame.size.width / self.view.bounds.size.width;
CGFloat newScale = currentScale * gesture.scale;
if (newScale < MINIMUM_SCALE) {
newScale = MINIMUM_SCALE;
}
if (newScale > MAXIMUM_SCALE) {
newScale = MAXIMUM_SCALE;
}
CGAffineTransform transform1 = CGAffineTransformMakeTranslation(_translation.x, _translation.y);
CGAffineTransform transform2 = CGAffineTransformMakeScale(newScale, newScale);
CGAffineTransform transform = CGAffineTransformConcat(transform1, transform2);
self.view.transform = transform;
gesture.scale = 1;
}
}
The simplest way to do this, if all you want is pinch zooming, is to place your image inside a UIWebView (write small amount of html wrapper code, reference your image, and you're basically done). The more complcated way to do this is to use touchesBegan, touchesMoved, and touchesEnded to keep track of the user's fingers, and adjust your view's transform property appropriately.
Keep in mind that you don't want to zoom in/out UIImage. Instead try to zoom in/out the View which contains the UIImage View Controller.
I have made a solution for this problem. Take a look at my code:
#IBAction func scaleImage(sender: UIPinchGestureRecognizer) {
self.view.transform = CGAffineTransformScale(self.view.transform, sender.scale, sender.scale)
sender.scale = 1
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
view.backgroundColor = UIColor.blackColor()
}
N.B.: Don't forget to hook up the PinchGestureRecognizer.