I'm trying to clear the content of what I have drawn when i press a button. But, I cant seem to do it figure out how to do it. I have google around abit and it seems like you need to do this inside of draw rect. This is the full code that I am using:
#import "PaintView.h"
#implementation PaintView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
hue = 0.0;
[self initContext:frame.size];
}
return self;
}
- (BOOL) initContext:(CGSize)size {
int bitmapByteCount;
int bitmapBytesPerRow;
// Declare the number of bytes per row. Each pixel in the bitmap in this
// example is represented by 4 bytes; 8 bits each of red, green, blue, and
// alpha.
bitmapBytesPerRow = (size.width * 4);
bitmapByteCount = (bitmapBytesPerRow * size.height);
// Allocate memory for image data. This is the destination in memory
// where any drawing to the bitmap context will be rendered.
self.cacheBitmap = malloc( bitmapByteCount );
if (self.cacheBitmap == NULL){
return NO;
}
self.cacheContext = CGBitmapContextCreate (self.cacheBitmap, size.width, size.height, 8, bitmapBytesPerRow, CGColorSpaceCreateDeviceRGB(), kCGImageAlphaNoneSkipFirst);
return YES;
}
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
[self drawToCache:touch];
}
- (void) drawToCache:(UITouch*)touch {
hue += 0.005;
if(hue > 1.0) hue = 0.0;
UIColor *color = [UIColor colorWithHue:hue saturation:0.7 brightness:1.0 alpha:1.0];
CGContextSetStrokeColorWithColor(self.cacheContext, [color CGColor]);
CGContextSetLineCap(self.cacheContext, kCGLineCapRound);
CGContextSetLineWidth(self.cacheContext, 6);
CGPoint lastPoint = [touch previousLocationInView:self];
CGPoint newPoint = [touch locationInView:self];
CGContextMoveToPoint(self.cacheContext, lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(self.cacheContext, newPoint.x, newPoint.y);
CGContextStrokePath(self.cacheContext);
CGRect dirtyPoint1 = CGRectMake(lastPoint.x-10, lastPoint.y-10, 20, 20);
CGRect dirtyPoint2 = CGRectMake(newPoint.x-10, newPoint.y-10, 20, 20);
[self setNeedsDisplayInRect:CGRectUnion(dirtyPoint1, dirtyPoint2)];
}
-(void)clear{
// this doesn't work.
CGContextClearRect(self.context, self.bounds);
}
- (void) drawRect:(CGRect)rect {
self.context = UIGraphicsGetCurrentContext();
CGImageRef cacheImage = CGBitmapContextCreateImage(self.cacheContext);
CGContextDrawImage(self.context, self.bounds, cacheImage);
CGImageRelease(cacheImage);
CGContextRetain(self.context);
}
#end
the button should call the view's setNeedsDisplay method which forces drawrect to be called or essentially forces a repaint.
Related
Before Colouring the image:
After Colouring the Image:
This is my code :
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
touchPoint = [touch locationInView:self.imgColor];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(touchPoint.x,touchPoint.y)];
[path addLineToPoint:CGPointMake(startingPoint.x,startingPoint.y)];
startingPoint=touchPoint;
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [path CGPath];
shapeLayer.strokeColor = [[UIColor blueColor] CGColor];
shapeLayer.lineWidth = 3.0;
shapeLayer.fillColor = [[UIColor redColor] CGColor];
[self.imgColor.layer addSublayer:shapeLayer];
[arrLayer addObject:shapeLayer];
NSLog(#"Touch moving point =x : %f Touch moving point =y : %f", touchPoint.x, touchPoint.y);
}
Depend upon the user touch, it's drawing a line inside UIImageView.
What i need is :
If image size is to small, then I don't like to allow draw outside the image.
Within the image user have to draw ,is there any way? Please suggest me.
Add this code to your touch handing view.
override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
return CGRectContainsPoint(doggyImage.frame, point)
}
Code provided in the first comment if (!CGRectContainsPoint(doggyImage.frame, touchPoint)) is some condition that you can use to check whether the touch point is in one specific view, where I think you should use to compare with your dog image (I don't know which variable it is, so I used doggyImage. Maybe self.imgColor is the correct one or not).
And for simplest solution, you need to do at least two things
If touchesBegan outside the doggyImage, don't do anything, just return.
Prevent lines been draw from inside to outside, so use the same condition, and apply the same code you used in touchesEnd to handle something like stopDrawLine.
More advanced method would include computing the nearest point from the touch to the image if it's outside, and draw it on the edge.
BTW, currently in your touchesMoved, you created one new UIBezierPath every single touch point, resulting in hundreds of discrete small pieces of line. You may want to try to new a path when touchesBegan and addLineToPoint in touchesMoved. So that the whole line is in one path, and you can therefore offer freatures like "undo".
take one CGRect object Globally assign its value as follow
CGRect imagerect = CGRectMake(imgColor.center.x - (imgColor.image.size.width/2 + imgColor.frame.origin.x), imgColor.center.y - (imgColor.image.size.height/2+ imgColor.frame.origin.y), imgColor.image.size.width, imgColor.image.size.height);
now in touchesMoved method put one condition before drawing if( CGRectContainsPoint(imagerect, touchPoint))
so the method will be like
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
touchPoint = [touch locationInView:self.imgColor];
if( CGRectContainsPoint(imagerect, touchPoint))
{
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(touchPoint.x,touchPoint.y)];
[path addLineToPoint:CGPointMake(startingPoint.x,startingPoint.y)];
startingPoint=touchPoint;
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [path CGPath];
shapeLayer.strokeColor = [[UIColor blueColor] CGColor];
shapeLayer.lineWidth = 3.0;
shapeLayer.fillColor = [[UIColor redColor] CGColor];
[self.imgColor.layer addSublayer:shapeLayer];
[arrLayer addObject:shapeLayer];
NSLog(#"Touch moving point =x : %f Touch moving point =y : %f", touchPoint.x, touchPoint.y);
}
}
Might be this is not perfect solution but i try to make same as your requirements. please try with this
#import "ViewController.h"
#interface ViewController ()
{
CGPoint touchPoint,startingPoint;
CALayer *layer;
float x1;
float y1;
IBOutlet UIImageView *imgColor;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
layer = [CALayer layer];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
-(void)viewDidAppear:(BOOL)animated
{
CGSize scaledImageSize = imgColor.image.size;
CGRect imageFrame = CGRectMake(roundf(0.5f*(CGRectGetWidth(imgColor.bounds)-scaledImageSize.width)), roundf(0.5f*(CGRectGetHeight(imgColor.bounds)-scaledImageSize.height)), roundf(scaledImageSize.width), roundf(scaledImageSize.height));
x1 = imageFrame.origin.x ;
y1 = imageFrame.origin.y ;
}
- (UIColor*)getRGBAsFromImage:(UIImage*)image atX:(int)x andY:(int)y
{
if (x < 0 || y<0 || x> image.size.width || y > image.size.height) {
return nil;
}
// First get the image into your data buffer
CGImageRef imageRef = [image CGImage];
NSUInteger width = CGImageGetWidth(imageRef);
NSUInteger height = CGImageGetHeight(imageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char *rawData = (unsigned char*) calloc(height * width * 4, sizeof(unsigned char));
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(rawData, width, height,
bitsPerComponent, bytesPerRow, colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGContextRelease(context);
// Now your rawData contains the image data in the RGBA8888 pixel format.
NSUInteger byteIndex = (bytesPerRow * y) + x * bytesPerPixel;
CGFloat alpha = ((CGFloat) rawData[byteIndex + 3] ) / 255.0f;
CGFloat red = ((CGFloat) rawData[byteIndex] ) / alpha;
CGFloat green = ((CGFloat) rawData[byteIndex + 1] ) / alpha;
CGFloat blue = ((CGFloat) rawData[byteIndex + 2] ) / alpha;
byteIndex += bytesPerPixel;
if (red >= 0 || green >= 0 || blue >= 0) {
return [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
}
return nil;
}
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
touchPoint = [touch locationInView:imgColor];
;
if ([self getRGBAsFromImage:imgColor.image atX:touchPoint.x - x1 andY:touchPoint.y - y1] && !(touchPoint.x == 0 && touchPoint.y == 0)) {
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(touchPoint.x,touchPoint.y)];
[path addLineToPoint:CGPointMake(startingPoint.x,startingPoint.y)];
startingPoint=touchPoint;
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [path CGPath];
shapeLayer.strokeColor = [[UIColor blueColor] CGColor];
shapeLayer.lineWidth = 1.0;
shapeLayer.fillColor = [[UIColor redColor] CGColor];
[imgColor.layer addSublayer:shapeLayer];
}
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
{
touchPoint = CGPointMake(0, 0);
}
#end
I have a very simple app where a user can draw. A user taps 3 times and an angle will be drawn at those 3 taps. At the location of each tap, there is a circle. Below is a picture of what it looks like:
On the first tap, only a circle is created because there are no other points to connect to (as it should). On the second tap it connects a line (as it should) from the first to second tap. On the third tap, it gets weird and connects from edge of the second circle (should be in the center?) to the third tap. Any ideas why on earth this is happening??
After 3 taps:
Another example of 3 taps:
This is how i'm drawing:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
[self setOpacity:o];
if (CGPointEqualToPoint(first, CGPointZero)) {
first = [touch locationInView:self];
}
else if (!CGPointEqualToPoint(first, CGPointZero) && CGPointEqualToPoint(second, CGPointZero)) {
second = [touch locationInView:self];
}
else if (!CGPointEqualToPoint(first, CGPointZero) && !(CGPointEqualToPoint(second, CGPointZero)) && CGPointEqualToPoint(third, CGPointZero)) {
third = [touch locationInView:self];
}
else {
first = [touch locationInView:self];
second = CGPointZero;
third = CGPointZero;
}
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
if (!CGPointEqualToPoint(first, CGPointZero)) {
CGRect rectangle = CGRectMake(first.x - 10, first.y - 10, 20, 20);
CGContextAddEllipseInRect(context, rectangle);
CGContextMoveToPoint(context, first.x, first.y);
if (!CGPointEqualToPoint(second, CGPointZero)) {
CGContextAddLineToPoint(context, second.x, second.y);
CGRect rectangle2 = CGRectMake(second.x - 10, second.y - 10, 20, 20);
CGContextAddEllipseInRect(context, rectangle2);
}
if (!CGPointEqualToPoint(third, CGPointZero)) {
CGContextAddLineToPoint(context, third.x, third.y);
CGRect rectangle3 = CGRectMake(third.x - 10, third.y - 10, 20, 20);
CGContextAddEllipseInRect(context, rectangle3);
}
CGContextStrokePath(context);
}
}
Because your current position is changed after you draw second ellipse CGContextAddEllipseInRect(context, rectangle2);
Try this:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
UIColor * w = [UIColor whiteColor] ;
UIColor * r = [UIColor redColor] ;
CGContextSetStrokeColorWithColor(context, w.CGColor) ;
CGContextSetFillColorWithColor(context, r.CGColor) ;
if (!CGPointEqualToPoint(first, CGPointZero)) {
CGRect rectangle = CGRectMake(first.x - 10, first.y - 10, 20, 20);
CGContextAddEllipseInRect(context, rectangle);
CGContextMoveToPoint(context, first.x, first.y);
if (!CGPointEqualToPoint(second, CGPointZero)) {
CGContextAddLineToPoint(context, second.x, second.y);
CGRect rectangle2 = CGRectMake(second.x - 10, second.y - 10, 20, 20);
CGContextAddEllipseInRect(context, rectangle2);
CGContextMoveToPoint(context, second.x, second.y);
}
if (!CGPointEqualToPoint(third, CGPointZero)) {
CGContextAddLineToPoint(context, third.x, third.y);
CGRect rectangle3 = CGRectMake(third.x - 10, third.y - 10, 20, 20);
CGContextAddEllipseInRect(context, rectangle3);
}
CGContextStrokePath(context);
}
}
I'm working through a challenge in Big Nerd Ranch's iOS Programming guide in a chapter on UIView subclassing.
I have some code below that draws concentric circles in random colors. And, upon a shake, it should change them all to red. It doesn't because after red is set, drawRect is called again and the random color loop is redone.
Would the correct way to implement this be to move the random color loop somewhere out of drawRect? Do I set a semaphore (messy) to make sure the loop only runs once?
Thanks for your advice.
- (void)setCircleColor:(UIColor *)clr
{
circleColor = clr;
[self setNeedsDisplay];
}
- (BOOL) canBecomeFirstResponder
{
return YES;
}
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake) {
[self setCircleColor:[UIColor redColor]];
}
}
-(void)drawRect:(CGRect)dirtyRect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect bounds = [self bounds];
// Figure out the center of the bounds rectangle
CGPoint center;
center.x = bounds.origin.x + bounds.size.width / 2.0;
center.y = bounds.origin.y + bounds.size.height / 2.0;
float maxRadius = hypot(bounds.size.width, bounds.size.height) / 2.0;
CGContextSetLineWidth(ctx, 10);
// Set up an array of colors
UIColor *red = [UIColor redColor];
UIColor *green = [UIColor greenColor];
UIColor *yellow = [UIColor yellowColor];
NSArray *colors = [[NSArray alloc]initWithObjects:red, green, yellow, nil];
// Draw concentric circles from the outside in
for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) {
CGContextAddArc(ctx, center.x, center.y, currentRadius, 0.0, M_PI * 2, YES);
// Random index for colors array
NSUInteger randomIndex = arc4random() % [colors count];
[self setCircleColor:[colors objectAtIndex:randomIndex]];
[[self circleColor] setStroke];
// Perform drawing; remove path
CGContextStrokePath(ctx);
}
NSString *text = #"You are getting sleepy.";
UIFont *font = [UIFont boldSystemFontOfSize:28];
CGRect textRect;
textRect.size = [text sizeWithFont:font];
// Let's put that string in the center of the view
textRect.origin.x = center.x - textRect.size.width / 2.0;
textRect.origin.y = center.y - textRect.size.height / 2.0;
[[UIColor blackColor] setFill];
// Draw a shadow
CGSize offset = CGSizeMake(4, 3);
CGColorRef color = [[UIColor darkGrayColor] CGColor];
CGContextSetShadowWithColor(ctx, offset, 2.0, color);
[text drawInRect:textRect withFont:font];
}
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setBackgroundColor:[UIColor clearColor]];
[self setCircleColor:[UIColor lightGrayColor]];
}
return self;
}
#end
Make a BOOL isSHake and cancel the random colors after you shake the device, the circles will remain red and you can reset that upon another action.
I'am trying to render waveform (high resolution bitmap) into UIView with different frames. I want to scale it and decided to use CATiledLayer for better performance. But in a result I got what I didn't expected.
source bitmap (5000 x 80 pixels):
result ( ? x 80 pixels):
Tracks are not the same; and when I continue scaling/scrolling - they differ.
Code itself:
+ (Class)layerClass {
return [CATiledLayer class];
}
- (id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
self.loading = YES;
self.recording = NO;
CATiledLayer *tiledLayer = (CATiledLayer *)[self layer];
tiledLayer.levelsOfDetail = 7;
tiledLayer.levelsOfDetailBias = 3;
tiledLayer.tileSize = CGSizeMake(128.0, 128.0);
[self addSubview:activityView];+ (Class)layerClass {
return [CATiledLayer class];
}
-(void)drawRect:(CGRect)rect {
UIImage* image = [UIImage imageWithContentsOfFile:self.imagePath];
CGFloat scale = parent.frame.size.width/image.size.width;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, rect);
CGContextSaveGState(context);
CGContextScaleCTM(context, scale, 1.0);
CGContextDrawImage(context, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage);
CGContextRestoreGState(context);
}
-What I am doing wrong here?
-Could CATiledLayer be scaled only by X-Axis?
Thanks in advance!
I am drawing some custom lines using custom MKAnnotationView on MapView control. The drawing is between two defined points. The problem I am facing is that, when I zoom in or zoom out in an iPhone, the lines seems to zoom in and out too. This creates a weird effect as lines appear to be over different buildings and places once the user zoom in or out. How can I solve this problem?
Here is the code for custom AnnotationView:
#implementation JogAnnotationView
#synthesize points;
- (id)initWithAnnotation:(id <MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier pts:(NSMutableArray *)p
{
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
if (self != nil)
{
CGRect cgRect =[[UIScreen mainScreen] bounds];
CGSize cgSize = cgRect.size;
CGRect frame = self.frame;
frame.size = CGSizeMake(cgSize.width/2, cgSize.height/2);
self.frame = frame;
self.backgroundColor = [UIColor clearColor];
self.centerOffset = CGPointMake(-5, -5);
self.points = p;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
NSLog(#"%d",self.points.count);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextBeginPath (context);
CGContextSetLineWidth(context, 2.0);
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGFloat components[] = {0.0, 0.0, 1.0, 1.0};
CGColorRef color = CGColorCreate(colorspace, components);
CGContextSetStrokeColorWithColor(context, color);
CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0);
for (int i = 0; i < ([self.points count] - 1); i++)
{
JogPoint *startPoint = (JogPoint *) [self.points objectAtIndex:i];
JogPoint *endPoint = (JogPoint *) [self.points objectAtIndex:(i+1)];
CGContextMoveToPoint(context, startPoint.x ,startPoint.y);
CGContextAddLineToPoint(context,endPoint.x,endPoint.y);
}
}