How to create and draw a visual swipe gesture - iphone

I'd like to try implementing a visual swipe for an iPhone project, like they do in some games, like Fruit Ninja. As you drag your finger around the screen, it leaves a trail that disappears after a while. I would think that you could have a fixed number of points in the "chain" and as new points are added to the front, old ones are removed from the rear. I can see using -touchesMoved to generate new points and an NSMutableArray to keep track of the points. I just can't imaging what method I'd use to actually draw the segments. Would I make one CALayer and draw a line connecting the active points? Or use some other view object and join them together at the points...
Any ideas?

Something like this would work, if you had populated 'points' with CGPoints. Caveat: this is a quick cut, paste and edit job - so there will probably be errors. Also, I use stl::vector for 'points'. You may want to use some other structure.
CGContextRef context = UIGraphicsGetCurrentContext();
CGMutablePathRef dataPath = CGPathCreateMutable();
bool firstPoint = YES;
for (int i=0; i < points.size(); ++i)
{
CGPoint point = points[i];
if (firstPoint)
{
CGPathMoveToPoint(dataPath, NULL, point.x, point.y);
firstPoint = NO;
}
else
{
CGPathAddLineToPoint(dataPath, NULL, point.x, point.y);
}
}
CGContextSetRGBStrokeColor( context, 1.0, 0.0, 0.0, 1.0);
CGContextSetLineWidth( context, 5);
CGContextBeginPath( context );
CGContextAddPath( context, dataPath );
CGContextDrawPath( context, kCGPathStroke);
CGPathRelease(dataPath);

Related

Questions regarding drawing in iOS

I want to create a little navigation bar on the bottom of my iPhone screen where I basically just draw 5 rectangles next to each other. However, only the active page should have the opacity of 1.0 and others should be slightly transparent (alpha=0.4). That is what I already have.
Now my questions:
How do I change the opacity of the individual elements of my navigation ? Do I have to redraw the whole thing whenever something changes ? So I would have global variables called nav1Opacity,nav2Opacity...nav5Opacity, change them when the navigation changes and redraw the whole thing ? If so,
How do I clear what I have drawn before ? Do i create the rectangles as CGMutablePathRef()s and store them in an array and clear them all ?
I have very little experience with drawing, so I am a little lost there. I have read the documentation of Quartz2d and contexts, but still, as I mentioned I have not fully figured out how it works.
Here is some code I use:
-(void)drawRect:(CGRect)rect{
CGContextRef context = UIGraphicsGetCurrentContext();
//save state
CGContextSaveGState(context);
//NAV1
CGMutablePathRef nav1 = CGPathCreateMutable();
CGPathAddRect(nav1, NULL, CGRectMake(0 , 15, 64, 10));
UIColor *blueColor = UIColorFromRGB(0x35BFE5,0.1);
CGColorRef bC = [blueColor CGColor];
[colorArray addObject:(__bridge id)bC];
[navArray addObject:(__bridge id)nav1];
CGPathRelease(nav1);
/*
*
*
... I do this for all 5 navigation elements
*
*
*/
//then I go through all my rectangles and add/fill them
for(int i=0;i<[navArray count];i++){
CGContextAddPath(context, (__bridge CGMutablePathRef)[navArray objectAtIndex:i]);
CGContextSetFillColorWithColor(context, (__bridge CGColorRef)[colorArray objectAtIndex:i]);
CGContextFillPath(context);
}
// restore to last saved context state
CGContextRestoreGState(context);
}
//and this is how I redraw
-(void)updateActiveNav{
[navArray removeAllObjects];
[colorArray removeAllObjects];
[self setNeedsDisplay];
}
If you actually draw the interface, you will have to redraw it whenever it changes, at least the rectangles that change. You can reuse CGPaths, but they aren't graphical objects on screen, they are just instructions on how to draw shapes, so you will have to draw everything again.
That being said, you can use individual UIViews instead, which represent onscreen objects, and you can change their opacity, which will reflect on screen.
This is the problem:
for(int i=0;i<[navArray count];i++){
CGContextAddPath(context, (__bridge CGMutablePathRef)[navArray objectAtIndex:i]);
CGContextSetFillColorWithColor(context, (__bridge CGColorRef)[colorArray objectAtIndex:i]);
CGContextFillPath(context);
}
You are added the path to your context, then setting a fill color, then filling it. Then without restoring your context you're doing it again so your filling the previous path and the new one. Its not the drawing from the last drawRect its the drawing from here. Try something like below so that after you fill the path you reset the context and draw the next block by itself instead of both the 1st and 2nd etc.
for(int i=0;i<[navArray count];i++){
CGContextSaveGState(context);
// Add Path, Fill
CGContextRestoreGState(context);
}

How to draw a line with an array of CGPoint

I have an array of CGpoint and I want to connect these points to create a line. I know how to do it by subclass the UIView. but what I want to do here is that I already have an array of CGPoint and I have a button, when I click the button the line will be shown.
I don't know how to do this.
However I try something like following: (aLine is the array with CGPoint)
#define POINT(X) [[aLine objectAtIndex:X]CGPointValue]
CGContextRef context = UIGraphicsGetCurrentContext();
for (int i = 0;i < (aLine.count-1);i++){
CGPoint pt1 = POINT(i);
CGPoint pt1 = POINT(i+1);
CGContextMoveToPoint(context,pt1.x,pt1.y);
CGContextAddLineToPoint(context,pt2.x,pt2.y);
CGContextStrokePath(context);
}
Anyone can help me? thanks.
You're almost there! Firstly you need to set the stroke colour...
CGContextSetStrokeColor(context, CGColorGetComponents([colour CGColor]));
Then when you're done connecting your points (exactly what your code is currently doing) just close the path and stroke it...
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathStroke);

Draw line or rectangle on cocos2d Layer

Can you let me know what is the best way to draw a line or rectangle on a scene layer using Cocos2d ios4 iphone.
So far have tried Texture2d, but it is more like a paint brush and is not so good. Tried drawing a line using draw method, but previous line disappears on drawing another line.
Basically want to draw multiple horizontal ,vertical, oblique beams. Please suggest. Any code would help a lot .
The code to draw using texture is below:
CGPoint start = edge.start;
CGPoint end = edge.end;
// begin drawing to the render texture
[target begin];
// for extra points, we'll draw this smoothly from the last position and vary the sprite's
// scale/rotation/offset
float distance = ccpDistance(start, end);
if (distance > 1)
{
int d = (int)distance;
for (int i = 0; i < d; i++)
{
float difx = end.x - start.x;
float dify = end.y - start.y;
float delta = (float)i / distance;
[brush setPosition:ccp(start.x + (difx * delta), start.y + (dify * delta))];
[brush setScale:0.3];
// Call visit to draw the brush, don't call draw..
[brush visit];
}
}
// finish drawing and return context back to the screen
[target end];
The rendering is not good esp. with oblique lines as the scaling affects the quality.
Cheers
You could create a separate layer and call the draw method like this:
-(void) draw
{
CGSize s = [[Director sharedDirector] winSize];
drawCircle( ccp(s.width/2, s.height/2), circleSize, 0, 50, NO);
It's for a circle but the principle is the same. This is from a project I made a while back and it worked then. Don't know if anything has changed since.
You need to add draw method to your layer:
-(void) draw {
// ...
}
Inside it you can use some openGL like functions and cocos2d wrapper methods for openGL.
Hint: other methods can be called inside draw method.
But keep in mind that using other name for method
containing openGL instructions, that's not called inside draw mentioned above simply won't work.
Even when called from update method or other method used by scheduleUpdate selector.
So you will end up with something like this:
-(void) draw {
glEnable(GL_LINE_SMOOTH);
glColor4ub(255, 0, 100, 255);
glLineWidth(4);
CGPoint verts[] = { ccp(0,200), ccp(300,200) };
ccDrawLine(verts[0], verts[1]);
[self drawSomething];
[self drawSomeOtherStuffFrom:ccp(a,b) to:ccp(c,d)];
[someObject doSomeDrawingAsWell];
}
For more information check out cocos2d-iphone programming guide :
http://www.cocos2d-iphone.org/wiki/doku.php/prog_guide:draw_update?s[]=schedule#draw

drawRect function hindering performance

I am working on drawing a snake which moves using a CADisplayLink using DrawRect.
The problem is when the snake is small the speed is fine, but when the snake grows in length the snake becomes really slow.
I keep track of the snake using the followig variables:
- variable to keep track of snakes head
- variable to keep track of snakes tail
- variable to keep track of snakes head direction
- variable to keep track of snakes tail direction
- Array with all the points the snake bends
- Array to keep track of direction of each bend
Below is the code i am using to draw the snake.
-(void)drawRect:(CGRect)rect{
CGContextBeginPath(context);
CGContextSetLineWidth(context, 10.0);
float glowWidth = 10.0;
float colorValues[] = {0.4,0.4,0.4,1.0};
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGColorRef glowColor = CGColorCreate(colorspace, colorValues);
CGContextSetShadowWithColor(context, CGSizeMake(0.0, 0.0), glowWidth, glowColor);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetStrokeColorWithColor(context,[UIColor blackColor].CGColor);
GContextMoveToPoint(context, snake.tail_x,snake.tail_y);
for (int i = 0; i < [snake.bends count]; i++) {
NSData *bend_dir_value = [snake.bend_direction objectAtIndex:i];
Direction bend_dir = * (Direction *)[bend_dir_value bytes];
NSData *bend_value = [snake.bends objectAtIndex:i];
CGPoint bend_point = * (CGPoint * )[bend_value bytes];
if (bend_dir == kEastIn || bend_dir == kWestIn || bend_dir == kNorthIn || bend_dir == kSouthIn) {
CGContextMoveToPoint(context, bend_point.x, bend_point.y);
}
else {
CGContextAddLineToPoint(context, bend_point.x, bend_point.y);
}
}
CGContextAddLineToPoint(context, snake.head_x, snake.head_y);
}
Why is my performance being so bad when the snake lengths/bend increases?
Is there another way i should be drawing the snake?
Could it be that you're not closing your path? Right at the end you might need:
CGContextClosePath(context);
Have you profiled your app to figure out where most of the time is spent when it slows down?
It could be in the path stroke in core graphics, in your computational methods, or somewhere else. If you start optimizing before you have any profile/performance data, you may end up spending time in the wrong place.

setNeedsDisplayInRect: paints a white rectangle only

I'm still a little fresh to CoreGraphics programming, so please bear with me. I'm trying to write an application, which allows the user to rub stuff off an image with the finger. I have the basic functionality nailed down, but the result is sluggish since the screen is redrawn completely every time a touch is rendered. I did some research and found out that I can refresh only a portion of the screen using UIView's setNeedsDisplayInRect: method.
This does call drawRect: as expected, however everything I draw in the drawRect: following the setNeedsDisplayInRect: is ignored. Instead, the area in the rect parameter is simply filled with white. No matter what I draw inside, all I end up with is a white rectangle.
In essence, this is what I do:
1) when user touches screen, this touch is rendered into a mask
2) when the drawRect: is called, the image is masked with that mask
There must be something simple I'm overlooking, surely?
I found a solution, however it still escapes me how exactly this works. Here's the code:
This method flips the given rectangle in the same manner, in which the coordinate transformation in the context flips the context coordinate system:
- (CGRect) flippedRect:(CGRect)rect
{
CGRect flippedRect = rect;
flippedRect.origin.y = self.bounds.size.height - rect.origin.y - rect.size.height;
return CGRectIntersection( self.bounds, flippedRect );
}
This calculates the rectangle to be updated from the touch location. Note that the rectangle gets flipped:
- (CGRect) updateRectFromTouch:(UITouch *)touch
{
CGPoint location = [touch locationInView:self];
int d = RubbingSize;
CGRect touchRect = [self flippedRect:CGRectMake( location.x - d, location.y - d, 2*d, 2*d )];
return CGRectIntersection( self.frame, touchRect );
}
In render touch the 'flipped' update rectangles are coerced:
- (void) renderTouch:(UITouch *)touch
{
//
// Code to render into the mask here
//
if ( m_updateRect.size.width == 0 )
{
m_updateRect = [self updateRectFromTouch:touch];
}
else
{
m_updateRect = CGRectUnion( m_updateRect, [self updateRectFromTouch:touch] );
}
}
The whole view is refreshed with about 20Hz during the fingerpainting process. The following method is called every 1/20th second and submits the rectangle for rendering:
- (void) refreshScreen
{
if ( m_updateRect.size.width > 0 )
{
[self setNeedsDisplayInRect:[self flippedRect:m_updateRect]];
}
}
Here's a helper method to compare to rectangles:
BOOL rectIsEqualTo(CGRect a, CGRect b)
{
return a.origin.x == b.origin.x && a.origin.y == b.origin.y && a.size.width == b.size.width && a.size.height == b.size.height;
}
In the drawRect: method, the update rectangle is used to draw only the portion that needs updating.
- (void)drawRect:(CGRect)rect
{
BOOL drawFullScreen = rectIsEqualTo( rect, self.frame );
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
// Turn coordinate system around
CGContextTranslateCTM( context, 0.0, self.frame.size.height );
CGContextScaleCTM( context, 1.0, -1.0 );
if ( drawFullScreen )
{
// draw the full thing
CGContextDrawImage( context, self.frame, self.image );
}
else
{
CGImageRef partialImage = CGImageCreateWithImageInRect( self.image, [self flippedRect:m_updateRect] );
CGContextDrawImage( context, m_updateRect, partialPhoto );
CGImageRelease( partialImage );
}
...
// Reset update box
m_updateRect = CGRectZero;
}
If someone can explain to me why the flipping works, I'd appreciate it.
The flipping issue is most likely because UIKit has a 'flipped' coordinate compared to Core Graphics.
Apple documentation here