i'm a newbie on ios and i'm trying something that it's probably very simple.
i'm making a draw app (it just does circles) and i'm trying to save the draw to the photolibrary but i can't understand how to do that :(
my code so far:
touchdrawview.h
#import <UIKit/UIKit.h>
#interface TouchDrawView : UIView
{
NSMutableDictionary *linesInProcess;
NSMutableArray *completeLines;
}
- (void)clearAll;
- (void)endTouches:(NSSet *)touches;
#end
touchdrawview.m
#import "TouchDrawView.h"
#import "Line.h"
#implementation TouchDrawView
- (id)initWithCoder:(NSCoder *)c
{
self = [super initWithCoder:c];
if (self) {
linesInProcess = [[NSMutableDictionary alloc] init];
// Don't let the autocomplete fool you on the next line,
// make sure you are instantiating an NSMutableArray
// and not an NSMutableDictionary!
completeLines = [[NSMutableArray alloc] init];
[self setMultipleTouchEnabled:YES];
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 10.0);
CGContextSetLineCap(context, kCGLineCapRound);
// Draw complete lines in black
[[UIColor blackColor] set];
for (Line *line in completeLines) {
//CGContextMoveToPoint(context, [line begin].x, [line begin].y);
//CGContextAddLineToPoint(context, [line end].x, [line end].y);
CGRect rectangle = CGRectMake([line begin].x, [line begin].y, ([line end].x - [line begin].x), ([line end].y - [line begin].y));
CGContextAddEllipseInRect(context, rectangle);
CGContextStrokePath(context);
}
// Draw lines in process in red
[[UIColor redColor] set];
for (NSValue *v in linesInProcess) {
Line *line = [linesInProcess objectForKey:v];
//CGContextMoveToPoint(context, [line begin].x, [line begin].y);
//CGContextAddLineToPoint(context, [line end].x, [line end].y);
CGRect rectangle = CGRectMake([line begin].x, [line begin].y, ([line end].x - [line begin].x), ([line end].y - [line begin].y));
CGContextAddEllipseInRect(context, rectangle);
CGContextStrokePath(context);
}
}
- (void)clearAll
{
// Clear the collections
[linesInProcess removeAllObjects];
[completeLines removeAllObjects];
// Redraw
[self setNeedsDisplay];
}
- (void)touchesBegan:(NSSet *)touches
withEvent:(UIEvent *)event
{
for (UITouch *t in touches) {
// Is this a double tap?
if ([t tapCount] > 1) {
[self clearAll];
return;
}
// Use the touch object (packed in an NSValue) as the key
NSValue *key = [NSValue valueWithPointer:t];
// Create a line for the value
CGPoint loc = [t locationInView:self];
Line *newLine = [[Line alloc] init];
[newLine setBegin:loc];
[newLine setEnd:loc];
// Put pair in dictionary
[linesInProcess setObject:newLine forKey:key];
// There is a memory leak in this method
// You will find it using Instruments in the next chapter
}
}
- (void)touchesMoved:(NSSet *)touches
withEvent:(UIEvent *)event
{
// Update linesInProcess with moved touches
for (UITouch *t in touches) {
NSValue *key = [NSValue valueWithPointer:t];
// Find the line for this touch
Line *line = [linesInProcess objectForKey:key];
// Update the line
CGPoint loc = [t locationInView:self];
[line setEnd:loc];
}
// Redraw
[self setNeedsDisplay];
}
- (void)endTouches:(NSSet *)touches
{
// Remove ending touches from dictionary
for (UITouch *t in touches) {
NSValue *key = [NSValue valueWithPointer:t];
Line *line = [linesInProcess objectForKey:key];
// If this is a double tap, 'line' will be nil,
// so make sure not to add it to the array
if (line) {
[completeLines addObject:line];
[linesInProcess removeObjectForKey:key];
}
}
// Redraw
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)touches
withEvent:(UIEvent *)event
{
[self endTouches:touches];
}
- (void)touchesCancelled:(NSSet *)touches
withEvent:(UIEvent *)event
{
[self endTouches:touches];
}
- (void)dealloc
{
[linesInProcess release];
[completeLines release];
[super dealloc];
}
#end
touchdrawappdelegate.h
#import <UIKit/UIKit.h>
#class TouchDrawView;
#interface TouchTrackerAppDelegate : NSObject <UIApplicationDelegate> {
TouchDrawView *view;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
- (IBAction)guardar:(id)sender;
#end
touchdrawappdelegate.m
#import "TouchTrackerAppDelegate.h"
#implementation TouchTrackerAppDelegate
#synthesize window=_window;
-(UIImage *) ChangeViewToImage : (UIView *) view{
UIGraphicsBeginImageContext(view.bounds.size);
[TouchDrawView renderInContext:UIGraphicsGetCurrentContext()];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
- (IBAction)guardar:(id)sender{
NSLog(#"buh");
UIGraphicsBeginImageContext(CGSizeMake(320,480));
UIImage* result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageWriteToSavedPhotosAlbum(result, self, nil, nil);
}
This action only saw a white image because i can't convert the uiview to a uiimage..
how can this be done?
Regards
CALayer has contents, you can save it like this
CGImageRef imageRef = CGImageCreateWithImageInRect((CGImageRef)penView.layer.contents, cropRect);
UIImage *penImage = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
add framework QuartzCore
#import <QuartzCore/QuartzCore.h>
UPDATE
in your code, should be TouchDrawView.layer
UIGraphicsBeginImageContext(view.bounds.size);
[TouchDrawView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Related
I'm developing a drawing app which lets user to draw one UIBezierpath just once over an image which has been drawn in the UIView by drawInRect:blendMode in drawRect method and it's uses over 80% of CPU and 55MB of RAM and when I traced it down in the time profiler, it was drawInRect:blendMode method that was causing most of the CPU usage. I have already done few optimizations but what further optimizations can I do for both drawing the UIImage which is being drawn using [image drawInRect:Rect]; and also for the UIBezierpath which user draws on top of it?
Thanks in advance.
#interface InsideView(){
CGRect imageRect;
CGRect targetBounds;
}
#property (strong, nonatomic) NSMutableArray *strokes;
#property (nonatomic, strong) UIImage * img;
#property (nonatomic, strong) UIBezierPath *drawingPath;
#end
#implementation InsideView
-(void)drawRect:(CGRect)rect{
targetBounds = self.layer.bounds;
imageRect = AVMakeRectWithAspectRatioInsideRect(self.img.size,targetBounds);
[self.img drawInRect:imageRect];
for (int i=0; i < [self.strokes count]; i++) {
UIBezierPath* tmp = (UIBezierPath*)[self.strokes objectAtIndex:i];
[[UIColor whiteColor] setStroke];
[tmp stroke];
}
}
- (CGRect)segmentBoundsFrom:(CGPoint)point1 to:(CGPoint)point2
{
CGRect dirtyPoint1 = CGRectMake(point1.x-10, point1.y-10, 50, 50);
CGRect dirtyPoint2 = CGRectMake(point2.x-10, point2.y-10, 50, 50);
return CGRectUnion(dirtyPoint1, dirtyPoint2);
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
self.drawingPath = [UIBezierPath bezierPath];
self.drawingPath.lineWidth = 10;
self.drawingPath.lineCapStyle = kCGLineCapRound;
self.drawingPath.lineJoinStyle = kCGLineJoinRound;
[self.drawingPath moveToPoint:[[touches anyObject] locationInView:self]];
[self.strokes addObject:self.drawingPath];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
//Dirty Rect Calculations
CGPoint prevPoint = CGPathGetCurrentPoint(self.drawingPath.CGPath);
CGPoint point = [[touches anyObject] locationInView:self];
CGRect dirty = [self segmentBoundsFrom:prevPoint to:point];
[[self.strokes lastObject] addLineToPoint:point];
[self setNeedsDisplayInRect:dirty];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
[[self.strokes lastObject] addLineToPoint:[[touches anyObject] locationInView:self]];
[self setNeedsDisplay];
}
I am currently learning drawing with UIView and CALayer. I quickly did a drawing app to experiment with the two classes. I noticed CALayer had worse performance than UIView. Here is the code:
myView.m
#interface myView()
#property (nonatomic, strong) UIImageView *background;
#ifdef USE_LAYER
#property (nonatomic, strong) drawingLayer *drawCanvas;
#else
#property (nonatomic, strong) drawingView *drawCanvas;
#endif
#end
#implementation myView
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
self.background = [[UIImageView alloc] initWithFrame:frame];
[self addSubview:self.background];
#ifdef USE_LAYER
self.drawCanvas = [[drawingLayer alloc] init];
self.drawCanvas.frame = frame;
[self.layer addSublayer:self.drawCanvas];
#else
self.drawCanvas = [[drawingView alloc] initWithFrame:frame];
self.drawCanvas.backgroundColor = [UIColor clearColor];
[self addSubview:self.drawCanvas];
#endif
}
return self;
}
- (CGPoint)getPoint:(NSSet<UITouch *> *)touches
{
NSArray *touchesArray = [touches allObjects];
UITouch *touch = (UITouch *)[touchesArray objectAtIndex:0];
return [touch locationInView:self];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
CGPoint point = [self getPoint:touches];
self.drawCanvas.points = [[NSMutableArray alloc] init];
self.drawCanvas.startPoint = point;
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
CGPoint point = [self getPoint:touches];
[self.drawCanvas.points addObject:[NSValue valueWithCGPoint:point]];
self.background.image = [self imageFromLayer:self.layer];
self.drawCanvas.points = nil;
[self.drawCanvas setNeedsDisplay];
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
CGPoint point = [self getPoint:touches];
[self.drawCanvas.points addObject:[NSValue valueWithCGPoint:point]];
[self.drawCanvas setNeedsDisplay];
}
- (UIImage *)imageFromLayer:(CALayer *)layer
{
UIGraphicsBeginImageContext([layer frame].size);
[layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return outputImage;
}
#end
drawingView.h
#interface drawingView : UIView
#property (nonatomic, assign) CGPoint startPoint;
#property (nonatomic, strong) NSMutableArray *points;
#end
drawingView.m
#implementation drawingView
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetLineWidth(context, 2.0f);
CGContextMoveToPoint(context, self.startPoint.x, self.startPoint.y);
for (NSValue *point in self.points)
{
CGPoint location = [point CGPointValue];
CGContextAddLineToPoint(context, location.x, location.y);
}
CGContextStrokePath(context);
}
#end
I also have "drawingLayer" which is essentially the same thing as "drawingView", except it a subclass of CALayer and does the same drawing in "drawInContext".
What I found was the drawRect has much better performance, the drawing is almost in-sync with the touch position, with very minor lag.
Why is this the case? I expected the CALayer drawing to be at least the same, if not better. In fact, someone told me today that drawing in CALayer is hardware-accelerated, which is the preferred way if performance is a concern. Certainly this is NOT the case in my little experiment. Why?
It might be caused by CALayer's default animation.
Try to override the action(forKey event: String) -> CAAction? and always return nil while subclassing the CALayer Class
I have the following codes to draw lines in a UIView. But once I start drawing, the view becomes dark grey. How can I make the background transparent? I already set the backgroundColor to clearColor, but the background is still dark gray. What did I miss?
#interface CanvasView () {
UIColor *colorLine;
UIBezierPath *pathBezier;
}
#end
#implementation CanvasView
#synthesize imgCached;
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if(self) {
self.backgroundColor = [UIColor clearColor];
[self setMultipleTouchEnabled:NO];
pathBezier = [[UIBezierPath alloc] init];
pathBezier.lineWidth = 5;
colorLine = [UIColor redColor];
}
return self;
}
- (void)drawRect:(CGRect)rect {
[self.imgCached drawInRect:rect];
[pathBezier stroke];
}
- (void)drawCache {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, 0.0);
[colorLine setStroke];
if(!self.imgCached) {
UIBezierPath *rectpath = [UIBezierPath bezierPathWithRect:self.bounds];
[rectpath fill];
}
[self.imgCached drawAtPoint:CGPointZero];
[pathBezier stroke];
self.imgCached = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
- (void)clearGraphics {
self.imgCached = nil;
[pathBezier removeAllPoints];
[self setNeedsDisplay];
}
#pragma mark - Touch methods
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[touches allObjects] objectAtIndex:0];
[pathBezier moveToPoint:[touch locationInView:self]];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[touches allObjects] objectAtIndex:0];
[pathBezier addLineToPoint:[touch locationInView:self]];
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
[pathBezier addLineToPoint:p];
[self drawCache];
[self setNeedsDisplay];
[pathBezier removeAllPoints];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self touchesEnded:touches withEvent:event];
}
p.s. the imgCached is:
#property (nonatomic, strong) UIImage *imgCached;
Replace your drawRect: and drawCache methods with the following:
- (void)drawRect:(CGRect)rect {
[self.imgCached drawInRect:rect];
[colorLine setStroke];
[pathBezier stroke];
}
- (void)drawCache {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
[colorLine setStroke];
[self.imgCached drawAtPoint:CGPointZero];
[pathBezier stroke];
self.imgCached = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
How can I paint/draw a straight line with 2 fingers with the line being visible while the dragging or touching is still being done by the user? I've already tried simple painting using coregraphics but this one seems a bit complicated for me.
To Justin's point, just handle touchesBegan and touchesMoved.
Thus, if you subclass UIView, the CoreGraphics implementation might look like:
#interface CustomView ()
#property (nonatomic, strong) NSMutableArray *paths;
#property (nonatomic, strong) UIBezierPath *currentPath;
#end
#implementation CustomView
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self setMultipleTouchEnabled:YES];
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if (!self.currentPath)
{
if (!self.paths)
self.paths = [NSMutableArray array];
self.currentPath = [UIBezierPath bezierPath];
self.currentPath.lineWidth = 3.0;
[self.paths addObject:self.currentPath];
[self touchesMoved:touches withEvent:event];
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if ([touches count] != 2)
return;
[self.currentPath removeAllPoints];
__block NSInteger i = 0;
[touches enumerateObjectsUsingBlock:^(UITouch *touch, BOOL *stop) {
CGPoint location = [touch locationInView:self];
if (i++ == 0)
[self.currentPath moveToPoint:location];
else
[self.currentPath addLineToPoint:location];
}];
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
self.currentPath = nil;
}
- (void)drawRect:(CGRect)rect
{
[[UIColor redColor] setStroke];
[[UIColor clearColor] setFill];
for (UIBezierPath *path in self.paths)
{
[path stroke];
}
}
- (void)reset
{
self.paths = nil;
[self setNeedsDisplay];
}
#end
Alternatively, you can also use Quartz 2D and define your own CAShapeLayer objects, and then either your view subclass or your view controller could do something like (needless to say, this is the view controller implementation; the view implementation should be obvious):
#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>
#interface ViewController ()
#property (nonatomic, weak) CAShapeLayer *currentLayer;
#property (nonatomic, strong) UIBezierPath *currentPath;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view setMultipleTouchEnabled:YES];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if (!self.currentLayer)
{
CAShapeLayer *layer = [CAShapeLayer layer];
layer.lineWidth = 3.0;
layer.strokeColor = [[UIColor redColor] CGColor];
layer.fillColor = [[UIColor clearColor] CGColor];
[self.view.layer addSublayer:layer];
self.currentLayer = layer;
self.currentPath = [UIBezierPath bezierPath];
[self touchesMoved:touches withEvent:event];
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if ([touches count] != 2)
return;
[self.currentPath removeAllPoints];
__block NSInteger i = 0;
[touches enumerateObjectsUsingBlock:^(UITouch *touch, BOOL *stop) {
CGPoint location = [touch locationInView:self.view];
if (i++ == 0)
[self.currentPath moveToPoint:location];
else
[self.currentPath addLineToPoint:location];
}];
self.currentLayer.path = [self.currentPath CGPath];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
self.currentPath = nil;
self.currentLayer = nil;
}
- (IBAction)didTouchUpInsideClearButton:(id)sender
{
for (NSInteger i = [self.view.layer.sublayers count] - 1; i >= 0; i--)
{
if ([self.view.layer.sublayers[i] isKindOfClass:[CAShapeLayer class]])
[self.view.layer.sublayers[i] removeFromSuperlayer];
}
}
#end
For this latter approach, you need to add the QuartzCore.framework to your project.
I need to draw a line with dashes at regular gaps by sliding finger on the iPhone screen. I have written the following code for that and it is drawing the dashed line but the gaps are not regular and thus it is not looking good. I have also provided a link to the video showing the issue Please help!
Link to video:
https://dl.dropbox.com/u/56721867/Screen%20Recording%207.mov
CGPoint mid1 = midPoint__5(previousPoint1, previousPoint2);
CGPoint mid2 = midPoint__5(currentPoint, previousPoint1);
CGContextRef context = UIGraphicsGetCurrentContext();
[self.layer renderInContext:context];
self.lineColor=[arrObj objectAtIndex:colorTag];
CGContextMoveToPoint(context, mid1.x, mid1.y);
CGContextAddQuadCurveToPoint(context, previousPoint1.x, previousPoint1.y, mid2.x, mid2.y);
CGContextSetLineCap(context, kCGLineCapRound);//use for making circle ,rect or line effect===============
CGFloat dashArray[] = {2,8,2,8};
CGContextSetLineDash(context,2,dashArray, 4);
CGContextSetLineWidth(context, self.lineWidth);
self.lineColor=[arrObj objectAtIndex:[AppHelper userDefaultsForKey:#"ColorTag"].intValue];//color tag on button click
CGContextSetStrokeColorWithColor(context, self.lineColor.CGColor);
CGContextSetAlpha(context, 1.0);
CGBlendMode blendStyle=isErase?kCGBlendModeClear:kCGBlendModeNormal;
CGContextSetBlendMode(context,blendStyle);
CGContextStrokePath(context);
[super drawRect:rect];
It's rather unclear from the code, but based on the video and a hunch I'd guess a single swipe with the finger ends up as several line segments. This means that the dashing starts from scratch several times, creating the effect in the video. You probably need a NSMutableArray where you put all those currentPoint into, and then draw them all as one line every time you redraw.
This question has more information on how to draw lines in this way: iPhone Core Graphics thicker dashed line for subview
Here is the code to do what you are trying, it uses Quartz instead of CoreGraphics. You can use either Quartz or CoreGraphics.
//
// TestView.h
//
//
// Created by Syed Arsalan Pervez on 2/18/13.
// Copyright (c) 2013 SAPLogix. All rights reserved.
//
#import <UIKit/UIKit.h>
#interface TestView : UIView
{
NSMutableArray *_points;
}
#end
//
// TestView.m
//
//
// Created by Syed Arsalan Pervez on 2/18/13.
// Copyright (c) 2013 SAPLogix. All rights reserved.
//
#import "TestView.h"
#interface UserPoint : NSObject
{
BOOL _start;
BOOL _end;
CGPoint _point;
}
#property (assign, nonatomic) BOOL start;
#property (assign, nonatomic) BOOL end;
#property (assign, nonatomic) CGPoint point;
#end
#implementation UserPoint
#end
#implementation TestView
- (id)init
{
self = [super init];
if (self)
{
_points = [NSMutableArray new];
}
return self;
}
- (UserPoint *)addPoint:(CGPoint)point
{
UserPoint *_userPoint = [UserPoint new];
_userPoint.point = point;
[_points addObject:_userPoint];
[_userPoint release];
return [_points lastObject];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
UserPoint *_userPoint = [self addPoint:[touch locationInView:self]];
_userPoint.start = YES;
_userPoint = nil;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
UserPoint *_userPoint = [self addPoint:[touch locationInView:self]];
_userPoint = nil;
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
UserPoint *_userPoint = [self addPoint:[touch locationInView:self]];
_userPoint.end = YES;
_userPoint = nil;
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
[[UIColor blackColor] setStroke];
[[UIColor blackColor] setFill];
UIBezierPath *path = [UIBezierPath bezierPath];
[path setLineWidth:15];
CGFloat dash[] = {15,20};
[path setLineDash:dash count:2 phase:0];
[path setLineCapStyle:kCGLineCapRound];
CGPoint lastPoint;
for (UserPoint *_point in _points)
{
if (_point.start || _point.end)
[path moveToPoint:_point.point];
else
{
[path addQuadCurveToPoint:_point.point controlPoint:lastPoint];
}
lastPoint = _point.point;
}
[path stroke];
}
- (void)dealloc
{
[_points release];
[super dealloc];
}
#end