I am attempting to take a fingerpainted signature from the iPad's touch screen, print it to PDF, and then email the resulting PDF to a preset address. I have a UIView subclass written that adds lines between the current drag event's location to the last drag event's location, as shown below. I have had no troubles implementing the emailing portion. The subclass's declaration:
#import <UIKit/UIKit.h>
#interface SignatureView : UIView {
UIImageView *drawImage;
#public
UIImage *cachedSignature;
}
#property (nonatomic, retain) UIImage* cachedSignature;
-(void) clearView;
#end
And implementation:
#import "SignatureView.h"
#implementation SignatureView
Boolean drawSignature=FALSE;
float oldTouchX=-1;
float oldTouchY=-1;
float nowTouchX=-1;
float nowTouchY=-1;
#synthesize cachedSignature;
//UIImage *cachedSignature=nil;
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code.
}
return self;
if (cachedSignature==nil){
if(UIGraphicsBeginImageContextWithOptions!=NULL){
UIGraphicsBeginImageContextWithOptions(self.frame.size, NO, 0.0);
}else{
UIGraphicsBeginImageContext(self.frame.size);
}
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
cachedSignature = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
}
- (void)drawRect:(CGRect)rect {
//get image of current state of signature field
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
cachedSignature = UIGraphicsGetImageFromCurrentImageContext();
//draw cached signature onto signature field, no matter what.
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, CGRectMake(0,0,302,90),cachedSignature.CGImage);
if(oldTouchX>0 && oldTouchY>0 && nowTouchX>0 && nowTouchY>0){
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGContextSetLineWidth(context, 2);
//make change to signature field
CGContextMoveToPoint(context, oldTouchX, oldTouchY);
CGContextAddLineToPoint(context, nowTouchX, nowTouchY);
CGContextStrokePath(context);
}
}
-(void) clearView{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetGrayFillColor(context, 0.75, 1.0);
CGContextFillRect(context, CGRectMake(0,0,800,600));
CGContextFlush(context);
cachedSignature = UIGraphicsGetImageFromCurrentImageContext();
[self setNeedsDisplay];
}
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint location=[[touches anyObject] locationInView:self];
oldTouchX=nowTouchX;
oldTouchY=nowTouchY;
nowTouchX=location.x;
nowTouchY=location.y;
[self setNeedsDisplay];
}
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint location=[[touches anyObject] locationInView:self];
oldTouchX=nowTouchX;
oldTouchY=nowTouchY;
nowTouchX=location.x;
nowTouchY=location.y;
[self setNeedsDisplay];
}
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
oldTouchX=-1;
oldTouchY=-1;
nowTouchX=-1;
nowTouchY=-1;
}
- (void)dealloc {
[super dealloc];
}
#end
I am, however, having trouble printing the signature to a PDF. As I understand the above code, it should keep a copy of the signature before the last move was made as a UIImage named cachedSignature, accessible to all. However, when I try to write it to a PDF context with the following:
UIGraphicsBeginPDFContextToFile(fileName, CGRectZero, nil);
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);
UIImage* background=[[UIImage alloc] initWithContentsOfFile:backgroundPath];
// Create the PDF context using the default page size of 612 x 792.
// Mark the beginning of a new page.
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0.0, 792);
CGContextScaleCTM(context, 1.0, -1.0);
//CGContextDrawImage(context, CGRectMake(0,0,612,790),background.CGImage);
//draw signature images
UIImage *y=clientSignatureView.cachedSignature;
CGContextDrawImage(context, CGRectMake(450, 653, 600, 75), y.CGImage);
//CGContextDrawImage(context, CGRectMake(75, 75, 300, 300), techSignatureView.cachedSignature.CGImage);
CGContextTranslateCTM(context, 0.0, 792);
CGContextScaleCTM(context, 1.0, -1.0);
It fails. In this instance 'techSignatureView' and 'clientSignatureView' are instances of the custom UIView as defined above, connected to outlets in the same parent UIView as is running this code.
I don't know what's going wrong. I've taken out the call to print the background image, in case they were being printed 'behind' it, with no results. Using the debugger to inspect 'y' in the above code reveals that it has nil protocol entries and nil method entries; so I suspect it's not being accessed properly - beyond that, I'm clueless and don't know how to proceed.
First of all, your cachedSignature probably doesn't exist anymore when you try to draw it, because it's autoreleased. Use the property setter when you assign it, so instead of cachedSignature = foo write self.cachedSignature = foo.
Also, UIGraphicsGetImageFromCurrentImageContext only works for image contexts, you cannot use this in drawRect: with the current context. The drawRect: method takes care of drawing your view to the screen, you cannot create an image from the same context, you have to use UIGraphicsBeginImageContext to create a new context that you draw the image into. The drawRect: method is not the best place for that anyway, because performance will likely be bad if you render the image every time a touch moves. Instead, I'd suggest to render the signature to the image in your touchesEnded:withEvent: implementation.
Related
I am trying to draw an image on a page on touch.
I have this method here, the comment out code will draw a rectangle and that works, but when I try to draw an image, it does not work at all:
- (void)draw
{
//CGContextRef context = UIGraphicsGetCurrentContext();
// set the properties
//CGContextSetAlpha(context, self.lineAlpha);
// draw the rectangle
//CGRect rectToFill = CGRectMake(self.firstPoint.x, self.firstPoint.y, self.lastPoint.x - self.firstPoint.x, self.lastPoint.y - self.firstPoint.y);
//CGContextSetFillColorWithColor(context, self.lineColor.CGColor);
//CGContextFillRect(UIGraphicsGetCurrentContext(), rectToFill);
UIImage *_originalImage = [UIImage imageNamed: #"approved"]; // your image
UIGraphicsBeginImageContext(_originalImage.size);
CGContextRef _context = UIGraphicsGetCurrentContext(); // here you don't need this reference for the context but if you want to use in the future for drawing anything else on the context you could get it for it
[_originalImage drawInRect:CGRectMake(0.f, 0.f, _originalImage.size.width, _originalImage.size.height)];
UIImage *_newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
Here is my entire code:
#pragma mark - LazyPDFDrawingApproved
#interface LazyPDFDrawingApproved ()
#property (nonatomic, assign) CGPoint firstPoint;
#property (nonatomic, assign) CGPoint lastPoint;
#end
#pragma mark -
#implementation LazyPDFDrawingApproved
#synthesize lineColor = _lineColor;
#synthesize lineAlpha = _lineAlpha;
#synthesize lineWidth = _lineWidth;
- (void)setInitialPoint:(CGPoint)firstPoint
{
self.firstPoint = firstPoint;
}
- (void)moveFromPoint:(CGPoint)startPoint toPoint:(CGPoint)endPoint
{
self.lastPoint = endPoint;
}
- (void)draw
{
//CGContextRef context = UIGraphicsGetCurrentContext();
// set the properties
//CGContextSetAlpha(context, self.lineAlpha);
// draw the rectangle
//CGRect rectToFill = CGRectMake(self.firstPoint.x, self.firstPoint.y, self.lastPoint.x - self.firstPoint.x, self.lastPoint.y - self.firstPoint.y);
//CGContextSetFillColorWithColor(context, self.lineColor.CGColor);
//CGContextFillRect(UIGraphicsGetCurrentContext(), rectToFill);
UIImage *_originalImage = [UIImage imageNamed: #"approved"]; // your image
UIGraphicsBeginImageContext(_originalImage.size);
CGContextRef _context = UIGraphicsGetCurrentContext(); // here you don't need this reference for the context but if you want to use in the future for drawing anything else on the context you could get it for it
[_originalImage drawInRect:CGRectMake(0.f, 0.f, _originalImage.size.width, _originalImage.size.height)];
UIImage *_newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
- (void)dealloc
{
self.lineColor = nil;
#if !LazyPDF_HAS_ARC
[super dealloc];
#endif
}
#end
What am I doing wrong?
UPDATE
More code, these methods call draw:
#pragma mark - Drawing
- (void)drawRect:(CGRect)rect
{
#if PARTIAL_REDRAW
// TODO: draw only the updated part of the image
[self drawPath];
#else
[self.image drawInRect:self.bounds];
[self.currentTool draw];
#endif
}
- (void)updateCacheImage:(BOOL)redraw
{
// init a context
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
if (redraw) {
// erase the previous image
self.image = nil;
// load previous image (if returning to screen)
[[self.prev_image copy] drawInRect:self.bounds];
// I need to redraw all the lines
for (id<LazyPDFDrawingTool> tool in self.pathArray) {
[tool draw];
}
} else {
// set the draw point
[self.image drawAtPoint:CGPointZero];
[self.currentTool draw];
}
// store the image
self.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
In your draw method, you don't need to begin a new image context.
You already begin an image context before invoking the method, so you can draw directly into that.
For example:
-(void) draw {
CGContextRef context = UIGraphicsGetCurrentContext(); // not needed, but if you're doing other drawing, it'll be needed.
UIImage *_originalImage = [UIImage imageNamed: #"approved"]; // your image
[_originalImage drawInRect:CGRectMake(0.f, 0.f, _originalImage.size.width, _originalImage.size.height)];
}
The only reason a new image context would make sense here is if you were applying transforms to the image itself, and therefore required a context that was the same size as the image.
The reason your current code doesn't work is that you're creating a new image context, drawing into it, creating a UIImage from the result, but then you're not doing anything with that image. Therefore it'll never reach the screen.
If you wanted to do it that way, then you would need to call drawInRect: again on your outputted image (_newImage in your case) in order to draw it into the previous context.
I want to have a UIElement (any of UIView, UIButton, UILabel etc.) with custom shape lets say a triangle.
How do you suggest to achieve it.
Thanks in advance.
Here is a sample of a UIView subclass (triangle):
#implementation TriangleView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void)drawRect:(CGRect)rect
{
// Drawing code
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextBeginPath(ctx);
CGContextMoveToPoint (ctx, CGRectGetMinX(rect), CGRectGetMaxY(rect)); // top left
CGContextAddLineToPoint(ctx, CGRectGetMidX(rect), CGRectGetMinY(rect)); // mid right
CGContextAddLineToPoint(ctx, CGRectGetMaxX(rect), CGRectGetMaxY(rect)); // bottom left
CGContextClosePath(ctx);
CGContextSetRGBFillColor(ctx, 241/255.0, 241/255.0, 241/255.0, 1);
CGContextFillPath(ctx);
}
#end
I'm writing application which user can draw line with his finger.
This is code:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"BEGAN");//TEST OK
UITouch* tap=[touches anyObject];
start_point=[tap locationInView:self];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"MOVED");//TEST OK
UITouch* tap=[touches anyObject];
current_point=[tap locationInView:self];
[self DrawLine:start_point end:current_point];
start_point=current_point;
}
-(void)DrawLine: (CGPoint)start end:(CGPoint)end
{
context= UIGraphicsGetCurrentContext();
CGColorSpaceRef space_color= CGColorSpaceCreateDeviceRGB();
CGFloat component[]={1.0,0.0,0.0,1};
CGColorRef color = CGColorCreate(space_color, component);
//draw line
CGContextSetLineWidth(context, 1);
CGContextSetStrokeColorWithColor(context, color);
CGContextMoveToPoint(context, start.x, start.y);
CGContextAddLineToPoint(context,end.x, end.y);
CGContextStrokePath(context);
}
My problem is when I draw line on screen but the line is not visible.
P.S I draw on main View of application
context= UIGraphicsGetCurrentContext();
You're calling UIGraphicsGetCurrentContext() from outside the drawRect: method. So it will return nil. Therefore the following functions try to draw on a context that is actually nil which obviously cannot work
As #Jorg mentioned, there is no current context outside of drawRect: method, so UIGraphicsGetCurrentContext() will return nil most probably.
You can use a CGLayerRef for drawing offscreen, while in your drawRect: method, you can draw the contents of the layer on your view.
First, you'll need to declare the layer as a member of your class, so in your #interface declare CGLayerRef _offscreenLayer;. You may also create a property for it, however, I'll use it directly in this example.
Somewhere in your init method:
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, self.frame.size.width, self.frame.size.height, 8, 4 * self.frame.size.width, colorspace, (uint32_t)kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorspace);
_offscreenLayer = CGLayerCreateWithContext(context, self.frame.size, NULL);
Now, let's handle the drawing:
-(void)DrawLine: (CGPoint)start end:(CGPoint)end
{
CGContextRef context = CGLayerGetContext(_offscreenLayer);
// ** draw your line using context defined above
[self setNeedsDisplay]; // or even better, use setNeedsDisplayInRect:, and compute the dirty rect using start and end points
}
-(void)drawRect:(CGRect)rect {
CGContextRef currentContext = UIGraphicsGetCurrentContext(); // this will work now, since we're in drawRect:
CGRect drawRect = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
CGContextDrawLayerInRect(currentContext, drawRect, _offscreenLayer);
}
Note that you may need to make small changes in order for the code to work, but should give you some idea on how you can implement it.
This question already has answers here:
What's the best approach to draw lines between views?
(2 answers)
Closed 9 years ago.
I have a very simple (well hopefully very simple) question. In Objective-C, how do you draw a line between two points and add it to a UIView? I have tried using a UIImageView and manipulating its Transform property, but that ends up turning the line into a square or a rectangle when using the following code:
[[self tline] setFrame:CGRectMake(start.x, start.y, width, 5)];
[[self tline] setTransform:CGAffineTransformMakeRotation(angle)];
I have two CGPoints, start and end, and I would like to draw a dynamic 5px line between the two points and add it to my subview.
BK:
The point start is the point where the user begins touching the screen, and the point end is the point where the user's finger is currently. Obviously this will move a lot during gameplay. I need to be able to move this line to connect these two points.
I am using the touchesBegan:, Moved:, and Ended: methods to create, move, and destroy the line.
CoreGraphics
I have the following code; how do I add this line to self.view?
CGContextRef c = UIGraphicsGetCurrentContext();
CGFloat color[4] = {1.0f, 1.0f, 1.0f, 0.6f};
CGContextSetStrokeColor(c, color);
CGContextBeginPath(c);
CGContextMoveToPoint(c, start.x, start.y);
CGContextAddLineToPoint(c, end.x, end.y);
CGContextSetLineWidth(c, 5);
CGContextSetLineCap(c, kCGLineCapRound);
CGContextStrokePath(c);
Custom UIView:
#import <UIKit/UIKit.h>
#interface DrawingView : UIView
#property (nonatomic) CGPoint start;
#property (nonatomic) CGPoint end;
- (void)drawRect:(CGRect)rect;
#end
#import "DrawingView.h"
#implementation DrawingView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetLineCap(context, kCGLineCapSquare);
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor); //change color here
CGFloat lineWidth = 5.0; //change line width here
CGContextSetLineWidth(context, lineWidth);
CGPoint startPoint = [self start];
CGPoint endPoint = [self end];
CGContextMoveToPoint(context, startPoint.x + lineWidth/2, startPoint.y + lineWidth/2);
CGContextAddLineToPoint(context, endPoint.x + lineWidth/2, endPoint.y + lineWidth/2);
CGContextStrokePath(context);
CGContextRestoreGState(context);
NSLog(#"%f",_end.x);
}
- (void)setEnd:(CGPoint)end
{
_end = end;
[self setNeedsDisplay];
}
#end
drawRect: is only called when I initialize the view...
Draw method in UIViewController:
- (void)drawTLine:(CGPoint)start withEndPoint:(CGPoint)end
{
[[self dview] setStart:start];
[[self dview] setEnd:end];
[[self dview] drawRect:[self dview].frame];
}
This is how I add the drawing view:
DrawingView* dview = [[DrawingView alloc] initWithFrame:self.view.frame];
[dview setBackgroundColor:[UIColor clearColor]];
[self.view addSubview:dview];
Subclass UIView and perform custom drawing using the drawRect: method
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetLineCap(context, kCGLineCapSquare);
CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor); //change color here
CGFloat lineWidth = 1.0; //change line width here
CGContextSetLineWidth(context, lineWidth);
CGPoint startPoint = rect.origin; //change start point here
CGPoint endPoint = {.x = CGRectGetMaxX(rect), .y = startPoint.y} //change end point here
CGContextMoveToPoint(context, startPoint.x + lineWidth/2, startPoint.y + lineWidth/2);
CGContextAddLineToPoint(context, endPoint.x + lineWidth/2, endPoint.y + lineWidth/2);
CGContextStrokePath(context);
CGContextRestoreGState(context);
}
This will draw a black 1px line at the top of your UIView.
If you need to update the line you can just use some properties like
#property (nonatomic) CGPoint startPoint;
and provide a custom implementation for the setter like
- (void)setStartPoint:(CGPoint)point {
_point = point;
[self setNeedsDisplay]; // this will cause drawRect: to be called again
}
Do that for every property that you wish to control and make your drawRect: use such properties for drawing.
you can create and UIBezierPath like this
UIBezierPath path = [[UIBezierPath alloc] init]
[path moveToPoint: CGPoint(x,y)] // X and Y, start point
[path addLineToPoint:CGPoint(x2,y2) // X2 and Y2, end point
if you want to create a shape you can put more points with addLineToPoint: method and finish use
closePath method
I hope help you
I have implemented a highlight function in my app. This highlight is being drawn in UIImage so that it can be saved as a PNG Representation. Everything is working perfectly but recently I realized somewhat a very confusing issue. Sometimes when I am highlighting, the drawings are being distorted. Here is what it looks like:
Whenever I move my finger to highlight the characters, the drawn highlights are stretching to the left. Another one:
In this one, every time I move my finger to highlight, the drawn highlights are moving upward!
This is all very confusing for me. This happens from time to time, and sometimes to certain pages only. Sometimes it works well just like this:
I am very confused on why is this happening. Can anyone tell me or at least give me an idea on why is this happening? Please help me.
THE CODE:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
currPoint = [[touches anyObject]locationInView:self];
for (int r = 0; r < [rectangles count]; r++)
{
CGRect rect = [[rectangles objectAtIndex:r]CGRectValue];
//Get the edges of the rectangles
CGFloat xEdge = rect.origin.x + rect.size.width;
CGFloat yEdge = rect.origin.y + rect.size.height;
if ((currPoint.x > rect.origin.x && currPoint.x < xEdge) && (currPoint.y < rect.origin.y && currPoint.y > yEdge))
{
imgView.image = [self drawRectsToImage:imgView.image withRectOriginX:rect.origin.x originY:rect.origin.y rectWidth:rect.size.width rectHeight:rect.size.height];
break;
}
}
}
//The function that draws the highlight
- (UIImage *)drawRectsToImage:(UIImage *)image withRectOriginX:(CGFloat)x originY:(CGFloat)y rectWidth:(CGFloat)width rectHeight:(CGFloat)ht
{
UIGraphicsBeginImageContext(self.bounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[image drawInRect:self.bounds];
CGContextSetStrokeColorWithColor(context, [UIColor clearColor].CGColor);
CGRect rect = CGRectMake(x, y, width, ht);
CGContextAddRect(context, rect);
CGContextSetCMYKFillColor(context, 0, 0, 1, 0, 0.5);
CGContextFillRect(context, rect);
UIImage *ret = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return ret;
}
I can't tell you exactly why these artifacts occur, but...
I don't think it's a good idea to render an image in a touch handler. Touch handling should do as little as possible.
You might want to try using CoreAnimation's CALayer:
Set your image as background image like this (assuming your class is a subclass of UIView) or use an actual UIImageView:
self.layer.contents = (id)image.CGImage;
When you detect that another rectangle rect has been touched, add the highlight as a sublayer above your image background:
CALayer *highlightLayer = [CALayer layer];
highlightLayer.frame = rect;
highlightLayer.backgroundColor = [UIColor yellowColor].CGColor;
highlightLayer.opacity = 0.25f;
[self.layer addSublayer:highlightLayer];
The shouldRasterize layer property may help to improve performance if necessary:
self.layer.shouldRasterize = YES;
In order do use all this, use the QuartzCore framework and import <QuartzCore/QuartzCore.h> in your implementation file.
You can create a PNG representation of your layer hierarchy by rendering the self.layer to an image context, then get the image and your PNG representation.