I have a view in which I paint a dotted line when the user moves their finger across the screen of the iPad/iPhone. I draw the line between a point stored as LastLocation, and the point that is the CurrentLocation. The drawings are to be persistant on the screen.
This happens every time the touchMoved event is fired, and ends up letting me draw a dotted line tracing where the person has dragged their finger.... just like a painting application.
I have a function that gets called that does the following when the touch event is fired.
The view contains a UIImageView called drawImage. I use UIImageView as a means of persisting the lines drawn. This clearly isnt how people usually do paint applications, as it is quite slow.
Any insight in to a better way to do persistant painting using the CGContextRef calls would be appreciated.
/* Enter bitmap graphics drawing context */
UIGraphicsBeginImageContext(frame.size);
/* Draw the previous frame back in to the bitmap graphics context */
[drawImage.image drawInRect:CGRectMake(0, 0, drawImage.frame.size.width, drawImage.frame.size.height)]; //originally self.frame.size.width, self.frame.size.height)];
/* Draw the new line */
CGContextRef ctx = UIGraphicsGetCurrentContext();
/* Set line draw color to green, rounded cap end and width of 5 */
CGContextSetLineDash(ctx, 0, dashPattern, 2);
CGContextSetStrokeColorWithColor(ctx, color);
CGContextSetLineCap(ctx, kCGLineCapRound); //kCGLineCapSquare, kCGLineCapButt, kCGLineCapRound
CGContextSetLineWidth(ctx, 5.0); // for size
/* Start the new path point */
CGContextMoveToPoint(ctx, LastLocation.x, LastLocation.y);
CGContextAddLineToPoint(ctx, Current.x, Current.y);
CGContextStrokePath(ctx);
/* Push the updated graphics back to the image */
drawImage.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
The 'drawImage.image drawInRect' call is extremely slow, and is in essence redrawing the entire image.
Is there a faster way of doing this? I have seen drawing code like this in a few places on blogs for painting, but it's just a bit slow.
Would love to hear some thoughts on the topic.
There is no need to composite the image and line manually. Have the view that draws the line above another UIImageView that draws the image. Let the system do the compositing and draw the image.
In your code, just do the stuff between the two drawImage lines in the drawRect: method of the line drawing view.
-(void) drawRect:(CGRect)dirty {
CGContextRef ctx = UIGraphicsGetCurrentContext();
/* Set line draw color to green, rounded cap end and width of 5 */
CGContextSetLineDash(ctx, 0, dashPattern, 2);
CGContextSetStrokeColorWithColor(ctx, color);
CGContextSetLineCap(ctx, kCGLineCapRound); //kCGLineCapSquare, kCGLineCapButt, kCGLineCapRound
CGContextSetLineWidth(ctx, 5.0); // for size
/* Start the new path point */
CGContextMoveToPoint(ctx, LastLocation.x, LastLocation.y);
CGContextAddLineToPoint(ctx, Current.x, Current.y);
CGContextStrokePath(ctx);
}
When one end of the line moves, save the new point and mark the line drawing view as needing to display. So both Current and LastLocation should be members of the line drawing view, and in the setter method for each, call setNeedsDisplay.
Make sure clearsContextBeforeDrawing is YES and opaque is NO for the line drawing view.
Related
Here is my drawRect:
- (void)drawRect:(CGRect)rect
{
CGContextFillEllipseInRect(UIGraphicsGetCurrentContext(), self.bounds);
}
and the instances of this class are added a subviews the next way:
(#define DOTS_SIZE 30)
[self addSubview:[[VertexView alloc] initWithFrame:CGRectMake(anchor.x-DOTS_SIZE/2, anchor.y-DOTS_SIZE/2, DOTS_SIZE, DOTS_SIZE)]];
As far as I understand, I should get and ellipse (circle my case) in the views bounds. But I get it fully filled with rectangle (square).
By the way, I have logged bounds and their size is 30x30, so I should get nice little circles, but I get squares (T_T)
I'll be thankful for any advise!
The problem is that you have to make some setup before drawing the ellipse. For example:
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(ctx, [UIColor redColor].CGColor); // Or any other color.
CGContextFillEllipseInRect(ctx, self.bounds);
}
And to make background transparent you can check out: Setting A CGContext Transparent Background
Hi i am making a sample app in which i wanto create a square for which i used the following code
- (void)viewDidLoad {
[super viewDidLoad];
[self drawRect:CGRectMake(0, 0, 300, 200)];
[[self view] setNeedsDisplay];
}
- (void) drawRect:(CGRect)rect
{
NSLog(#"drawRect");
CGFloat centerx = rect.size.width/2;
CGFloat centery = rect.size.height/2;
CGFloat half = 100/2;
CGRect theRect = CGRectMake(-half, -half, 100, 100);
// Grab the drawing context
CGContextRef context = UIGraphicsGetCurrentContext();
// like Processing pushMatrix
CGContextSaveGState(context);
CGContextTranslateCTM(context, centerx, centery);
// Uncomment to see the rotated square
//CGContextRotateCTM(context, rotation);
// Set red stroke
CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0);
{
CGContextSetRGBFillColor(context, 0.0, 1.0, 0.0, 1.0);
}
// Draw a rect with a red stroke
CGContextFillRect(context, theRect);
CGContextStrokeRect(context, theRect);
// like Processing popMatrix
CGContextRestoreGState(context);
[[self view] setNeedsDisplay];
}
But nothing is drawn on screen , dont know wheres the issue is .When i debug it the CGContextRef context was always 0x0 , i dont know why its 0x0 always am i missing something in my code.
It looks like you're trying to draw in a subclass of UIViewController. You need to subclass UIView to override the drawRect: method, which is then called automatically with a valid graphics context in place. You almost never call this method yourself.
To quote the Apple docs:
"To draw to the screen in an iOS application, you set up a UIView object and implement its drawRect: method to perform drawing. The view’s drawRect: method is called when the view is visible onscreen and its contents need updating. Before calling your custom drawRect: method, the view object automatically configures its drawing environment so that your code can start drawing immediately. As part of this configuration, the UIView object creates a graphics context (a CGContextRef opaque type) for the current drawing environment. You obtain this graphics context in your drawRect: method by calling the UIKit function UIGraphicsGetCurrentContext."
So essentailly, your code is on track, you just need to get it in the right place. It needs to be in the View object.
I have two UIScrollView instances and I want to draw a vertical line between them (not 100% height, more like 80%). How can I achieve this?
Use Core Graphics. Nest the two UIScrollViews inside another view and override the drawRect method on the parent. See Apple's UIView documentation for drawRect for more detail and the Core Graphics Context Reference for more information about drawing.
- (void) drawRect: (CGRect) rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
// Draw divider
UIGraphicsPushContext(context);
// Fill the background, this must happen if the view is not opaque.
if (self.opaque)
{
[[UIColor whiteColor ] set];
CGContextFillRect(context, rect);
}
[[UIColor grayColor] set];
CGContextSetLineWidth(context, 1);
CGContextBeginPath(context);
CGContextMoveToPoint(context, rect.size.width * 0.5, 0.1 * rect.size.height);
CGContextAddLineToPoint(context, rect.size.width * 0.5, 0.9 * rect.size.height);
CGContextStrokePath(context);
UIGraphicsPopContext();
}
edit
To address my oversight, there is no simple way that I know of to draw on top of subviews.
This behavior can be simulated using the same basic view hierarchy as above. Subclass UIView to create a completely transparent view that does not accept any touch events. Place the drawing code (I added a conditional for opacity in the code above) in the custom view and then insert an instance of the custom view at the front of the view hierarchy.
A vertical line between your views could also just be a thin, tall UIView (on top of the scroll views) with, say, a solid background color. This would mean you don't need to worry about custom drawing code. This also lets you lay it out in Interface Builder, use autosizing, etc.
I'm using this code in my UIView subclass to draw a circle with a gradient fill:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetShadow (context, CGSizeMake(4,4), 5);
CGContextBeginPath (context);
CGContextAddEllipseInRect(context, CGRectMake(self.bounds.origin.x, self.bounds.origin.y, 38, 38));
CGContextClosePath (context);
CGContextClip (context);
CGContextDrawLinearGradient(context, gradient, CGPointMake(CGRectGetMinX(self.bounds), CGRectGetMaxX(self.bounds)), CGPointMake(CGRectGetMaxX(self.bounds), CGRectGetMinY(self.bounds)), 0);
}
The circle and the gradient draw fine, but I see no shadow. I'm not sure why its not working, because I used the same CGContextSetShadow function in a different view subclass and it worked fine.
NOTE: In the above code, "gradient" is an ivar that was defined previously.
A gradient draw doesn't count as a fill; only fills and strokes get shadows. (You may want to report this as a bug.)
As a workaround, fill the path (with solid black), then turn off the shadow, then draw the gradient.
i'm trying to draw a line from the originate point of an image view to its destination point.
my problem is (i guess) how to set the superview as current drawing context (from the image view).
can someone help please.
this is the code i'm using in image view..
//UIGraphicsPopContext();
CGContextRef context = UIGraphicsGetCurrentContext(); //(problem here????????)
CGContextSetLineWidth(context, 5.0);
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGContextMoveToPoint(context, startLocationInView.x, startLocationInView.y);
CGContextAddLineToPoint(context, destinationPositionInView.x, destinationPositionInView.y);
CGContextStrokePath(context);
You cannot draw in other views. What you should do is expand your view's bounds to cover the whole area in which you want to draw.
Alternatively, you could add a new CALayer to your view's layer to cover areas that are outside of your view's bounds.