QuartzCore performance on clear UIView - objective-c

I have a UIView where I set the background color to clear in the init method. I then draw a blue line between 2 points in drawRect. The first point is the very first touch on the view, and the second point is changed when touchesMoved is called.
So that one end of the line is fixed, and the other end moves with the users finger. However when the backgroundcolor is clear the line has quite a big of delay and skips (is not smooth).
If I change the background color to black (just uncommenting the setbackgroundcolor line) then the movement is much smoother and it looks great, apart from you can't see the views behind it.
How can I solve this issue? (on the iPad, I only have the simulator, and haven't currently got access to a device) So that the performance is smooth, but the background is clear so you can see the background views.
-(id) initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
//self.backgroundColor = [UIColor clearColor];
}
return self;
}
-(void) drawRect:(CGRect)rect {
if (!CGPointEqualToPoint(touchPoint, CGPointZero)) {
if (touchEnded) {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, rect);
} else {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, rect);
CGContextSetLineWidth(context, 3.0f);
CGFloat blue[4] = {0.0f, 0.659f, 1.0f, 1.0f};
CGContextSetAllowsAntialiasing(context, YES);
CGContextSetStrokeColor(context, blue);
CGContextMoveToPoint(context, startPoint.x, startPoint.y);
CGContextAddLineToPoint(context, touchPoint.x, touchPoint.y);
CGContextStrokePath(context);
CGContextSetFillColor(context, blue);
CGContextFillEllipseInRect(context, CGRectMakeForSizeAroundCenter(CGSizeMake(10, 10), touchPoint));
CGContextFillEllipseInRect(context, CGRectMakeForSizeAroundCenter(CGSizeMake(10, 10), startPoint));
}
}
}
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.touchEnded = NO;
if ([[touches allObjects] count] > 0)
startPoint = [[[touches allObjects] objectAtIndex:0] locationInView:self];
touchPoint = [[[touches allObjects] objectAtIndex:0] locationInView:self];
[self setNeedsDisplay];
}
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
self.touchEnded = NO;
if ([[touches allObjects] count] > 0)
touchPoint = [[[touches allObjects] objectAtIndex:0] locationInView:self];
[self setNeedsDisplay];
}
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
self.touchEnded = YES;
[self setNeedsDisplay];
[self removeFromSuperview];
}
- (void)dealloc {
[super dealloc];
}

First of all, you should test performance on a device, rather than in Simulator. Most of graphics-related stuff works very differently in Simulator, some being much faster and some, surprisingly, much slower.
That said, if a transparent background does prove to be a performance problem on a device, it's most likely because you fill too many pixels with CGContextClearRect(). I can think of two solutions off the top of my head:
In touchesBegan:withEvent: you can make a snapshot of the underlying view(s) using CALayer's renderInContext: method and draw the snapshot as the background instead of clearing it. That way you can leave the view opaque and thus save time on compositing the view with views underneath it.
You can create a one-point-tall CALayer representing a line and transform it in touchesMoved:withEvent: so that its ends are in correct positions. Same goes for dots representing the line ends (CAShapeLayer is great for that). There will be some ugly trigonometry involved, but the performance is going to be excellent.

Related

Draw stuff on touch objective c

hy,
How can i make something that changes color when i touch it in objective c, i want the whole screen to be touchable and want to draw stuff on it with finger.
Everywhere i touch it should change the color.
Whats the best way to do it? I already have touchesMoved implemented to get the coordinates of the touch.
UITouch * touch = [touches anyObject];
CGPoint pos = [touch locationInView: [UIApplication sharedApplication].keyWindow];
Any example code would be nice thanks.
thank you in advance,
i have the following code but it doesnt print anything where i touch
-(void)setPaths:(NSMutableArray *)paths
{
self.paths =[[NSMutableArray alloc]init];
}
-(void)setAPath:(UIBezierPath *)aPath
{
self.aPath=[UIBezierPath bezierPath];
}
-(void) drawRect:(CGRect)rect{
[super drawRect:rect];
[[UIColor blackColor] set];
for (UIBezierPath *path in paths) {
[path stroke];
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.paths addObject:self.aPath];
//self.aPath=[UIBezierPath bezierPath];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch * touch = [touches anyObject];
CGPoint pos = [touch locationInView: [UIApplication sharedApplication].keyWindow];
[self.aPath addLineToPoint:[touch locationInView:self]];
// [self.aPath addLineToPoint:[touch locationInView:touch]];
NSLog(#"Position of touch: %.3f, %.3f", pos.x, pos.y);
}
#end
When your touch starts, create a UIBezierPath:
self.myPath = [UIBezierPath bezierPath];
Then, every time the touches moves, add a point to the path:
[self.myPath addLineToPoint:[touch locationInView:self]];
Once your touch has ended, in your drawRect just draw the path:
-(void) drawRect:(CGRect)rect{
[super drawRect:rect];
[[UIColor blueColor] set];
for (UIBezierPath *path in paths) {
[path stroke];
}
}
Find all the doc here:
UIBezierPath Class Reference
Maybe you're going to change your drawRect: method. insert these 2 lines:
[[UIColor blackColor] setStroke];
[path stroke];
and you should add [self setNeedsDisplay]; after you've added a line inside touchesMoved:
I just passed following tutorial, it should solve your problem. It also explains how you can improve your code so it won't get too lazy if you're drawing longer lines:
http://mobile.tutsplus.com/tutorials/iphone/ios-sdk_freehand-drawing/

Scrolling with two finger gesture

I have a drawing view on a UIScrollView.
What I want to do is draw lines with one finger, and scrolling with two fingers.
The drawing view is to draw lines through touchesMoved as below.
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch;
CGPoint lastTouch, currentTouch;
for (touch in touches)
{
lastTouch = [touch previousLocationInView:self];
currentTouch = [touch locationInView:self];
CGContextRef ctx = CGLayerGetContext(drawLayer);
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, lastTouch.x, lastTouch.y);
CGContextAddLineToPoint(ctx, currentTouch.x, currentTouch.y);
CGContextStrokePath(ctx);
}
[self setNeedsDisplay];
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
CGContextDrawLayerInRect(drawContext, self.bounds, drawLayer);
CGContextClearRect(CGLayerGetContext(drawLayer), self.bounds);
[self setNeedsDisplay];
}
and on a viewController,
_scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
[_scrollView setContentSize:CGSizeMake(320, 800)];
[self.view addSubview:_scrollView];
_drawingView = [[DrawingView alloc] initWithFrame:CGRectMake(0, 0, 320, 800)];
[_scrollView addSubview:_drawingView];
for (UIGestureRecognizer *gestureRecognizer in _scrollView.gestureRecognizers)
{
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]])
{
UIPanGestureRecognizer *panGR = (UIPanGestureRecognizer *) gestureRecognizer;
panGR.minimumNumberOfTouches = 2;
}
}
It works ok on simulator however the drawing is too slow on a real device. What is wrong and any suggestion?
Ty!
I solved.
Shouldn't draw whole screen with [self setNeedsDisplay]. Should draw a area where need to redraw with [self setNeedsDisplay withRect:]
Better use panGesture recogniger than touchesBegin~End. There's delay between touchesBegin and touchesEnd.

CCPanZoomController + Tappable Sprites

I am creating a zoomable and pan-able map for my game (using CCPanZoomController) . Within this map I would like to have a tappable sprite, and when it is tapped, "do something"…
I can get the two things to work perfectly in separate projects, however when I try to combine them, nothing happens when I tap my sprite.
I have included an image to demonstrate further:
//in my init section
self.isTouchEnabled = YES;
mapBase = [CCSprite spriteWithFile:#"MapBase.png"];
mapBase.anchorPoint = ccp(0, 0);
[self addChild:mapBase z:-10];
gym = [CCSprite spriteWithFile:#"Gym.png"];
gym.scale = 0.3;
gym.position = ccp(1620, 250);
[self addChild:gym z:1];
CGRect boundingRect = CGRectMake(0, 0, 2499, 1753);
_controller = [[CCPanZoomController controllerWithNode:self] retain];
_controller.boundingRect = boundingRect;
_controller.zoomOutLimit = _controller.optimalZoomOutLimit;
_controller.zoomInLimit = 2.0f;
[_controller enableWithTouchPriority:1 swallowsTouches:YES];
//end of init
-(void) registerWithTouchDispatcher
{
[[[CCDirector sharedDirector] touchDispatcher] addTargetedDelegate:self
priority:0 swallowsTouches:NO];
}
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event{
CGPoint touchPoint1 = [touch locationInView:[touch view]];
if (CGRectContainsPoint(gym.boundingBox, touchPoint1)) return YES;
return NO;
}
-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event{
CGPoint touchPoint2 = [touch locationInView:[touch view]];
if (CGRectContainsPoint(gym.boundingBox, touchPoint2)){
CCLOG(#"SPRITE HAS BEEN TAPPED");
}
}
I want to beable to zoom in/out and pan the wholemap (including the ‘Gym’ sprite).
And if the sprite 'Gym' is tapped by the user, then i would like to “do something”.
If anyone can figure this out, I would be extremely grateful!
Thanks.

iOS4.2: TouchBegan does not draw more than one circle per sensed touch

quick question (which might be a no-brainer for most here) :)
My code below should draw a circle for every time touch that is recognised but although more than ones touches are sensed only one circle will drawn up at a time.
Can anyone see any obvious issues?
This method sits in the XYZViewControler.m class.
TouchPoint.m is the class that defines the circle.
Thanks a bundle for your help and redirects.
Chris
- (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *)event {
NSSet * allTouches = [event allTouches]; // get all events
for (UITouch * touch in touches) {
TouchPoint * touchPoint = [[TouchPoint alloc] initWithFrame:CGRectMake(0, 0, circleWidth, circleWidth)];
touchPoint.center = [touch locationInView:[self view]];
touchPoint.color = [UIColor redColor];
touchPoint.backgroundColor = [UIColor whiteColor];
[[self view] addSubview: touchPoint];
[touchPoint release];
CFDictionarySetValue(touchMap, touch , touchPoint);
}
[[self view] setNeedsDisplay];
}
code if fine! One has to enable multitouch for the view in order to make it work!
#property(nonatomic, getter=isMultipleTouchEnabled) BOOL multipleTouchEnabled

Saving CGContextRef

I have a drawing app in which I would like to create an undo method. The drawing takes place inside the TouchesMoved: method.
I am trying to create a CGContextRef and push it to the stack OR save it in a context property that can be restored later but am not having any luck. Any advice would be great. Here is what I have ...
UIImageView *drawingSurface;
CGContextRef undoContext;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UIGraphicsBeginImageContext(self.view.frame.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[drawingSurface.image drawInRect:CGRectMake(0, 0, drawingSurface.image.size.width, drawingSurface.image.size.height)];
UIGraphicsPushContext(context);
// also tried but cant figure how to restore it
undoContext = context;
UIGraphicsEndImageContext();
}
Then I have a method triggered by my undo button ...
- (IBAction)restoreUndoImage {
UIGraphicsBeginImageContext(self.view.frame.size);
UIGraphicsPopContext();
drawingSurface.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
When I run this, I believe my drawingSurface is being assigned nil because it just erases everything in the image.
My guess is I can't use pop and push this way. But I can't seem to figure out how to just save the context and then push it back onto the drawingSurface. Hmmmm. Any help would be ... well ... helpfull. Thanks in advance -
And, just for reference, here is what I am doing to draw to the screen, which is working great. This is inside my TouchesMoved:
UIGraphicsBeginImageContext(self.view.frame.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[drawingSurface.image drawInRect:CGRectMake(0, 0, drawingSurface.image.size.width, drawingSurface.image.size.height)];
CGContextSetLineCap(context, kCGLineCapRound); //kCGLineCapSquare, kCGLineCapButt, kCGLineCapRound
CGContextSetLineWidth(context, self.brush.size); // for size
CGContextSetStrokeColorWithColor (context,[currentColor CGColor]);
CGContextBeginPath(context);
CGContextMoveToPoint(context, lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(context, currentPoint.x, currentPoint.y);
CGContextStrokePath(context);
drawingSurface.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
I think you're approaching the problem the wrong way and confusing contexts.
In an immediate mode API you save the 'state' of objects with push/pop, not the graphical representation. The state consists of things like line widths, colours and positions. The graphical representation is the result of a paint operation (a bitmap) and generally something you don't want to save.
Instead try to save 'information' you use to create the drawing.
My initial suggestion would be to decouple your shape creation and painting. On OSX you can use NSBezierPath, but for iOS we have to use an array of points.
For example given this protocol:
// ViewController.h
#protocol DrawSourceProtocol <NSObject>
- (NSArray*)pathsToDraw;
#end
#interface ViewController : UIViewController<DrawSourceProtocol>
#end
You can implement these functions:
// ViewController.m
#interface ViewController () {
NSMutableArray *currentPath;
NSMutableArray *allPaths;
MyView *view_;
}
#end
...
- (void)viewDidLoad {
[super viewDidLoad];
currentPath = [[NSMutableArray alloc] init];
allPaths = [[NSMutableArray alloc] init];
view_ = (MyView*)self.view;
view_.delegate = self;
}
- (NSArray*)pathsToDraw {
// Return the currently draw path too
if (currentPath && currentPath.count) {
NSMutableArray *allPathsPlusCurrent = [[NSMutableArray alloc] initWithArray:allPaths];
[allPathsPlusCurrent addObject:currentPath];
return allPathsPlusCurrent;
}
return allPaths;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
currentPath = [[NSMutableArray alloc] init];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
// When a touch ends, save the current path
[allPaths addObject:currentPath];
currentPath = [[NSMutableArray alloc] init];
[view_ setNeedsDisplay];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self.view];
// We store the point with the help of NSValue
[currentPath addObject:[NSValue valueWithCGPoint:currentPoint]];
// Update the view
[view_ setNeedsDisplay];
}
Now subclass your view (I call mine MyView here) and implement something like this:
// MyView.h
#import "ViewController.h"
#protocol DrawSourceProtocol;
#interface MyView : UIView {
__weak id<DrawSourceProtocol> delegate_;
}
#property (weak) id<DrawSourceProtocol> delegate;
#end
// MyView.m
#synthesize delegate = delegate_;
...
- (void)drawRect:(CGRect)rect {
NSLog(#"Drawing!");
// Setup a context
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0);
CGContextSetLineWidth(context, 2.0);
// Get the paths
NSArray *paths = [delegate_ pathsToDraw];
for (NSArray *aPath in paths) {
BOOL firstPoint = TRUE;
for (NSValue *pointValue in aPath) {
CGPoint point = [pointValue CGPointValue];
// Always move to the first point
if (firstPoint) {
CGContextMoveToPoint(context, point.x, point.y);
firstPoint = FALSE;
continue;
}
// Draw a point
CGContextAddLineToPoint(context, point.x, point.y);
}
}
// Stroke!
CGContextStrokePath(context);
}
The only cavet here is that setNeedsDisplay isn't very performant. It's better to use setNeedsDisplayInRect:, see my last post regarding an efficient way of determining the 'drawn' rect.
As for undo? Your undo operation is merely popping the last object from the allPaths array. This exercise I'll leave you to :)
Hope this helps!