iOS CALayer convertPoint:fromLayer: message sent to deallocated instance - ios

I have a class with a square made of bezierPaths, you can change the size of the square by pressing near a side and drag it.
Right now i can drag and release one time, the next time i get the following error:
2015-01-20 14:20:36.356 BezierPaths[196:3437] *** -[CALayer convertPoint:fromLayer:]: message sent to deallocated instance 0x1742251c0
Why doesnt it work the 2nd time?
This is the class:
#implementation EditBezierView
{
UIBezierPath *path;
UIImage *incrementalImage;
CGPoint pts[9];
NSMutableArray *pathPoints;
NSMutableArray *controlPoints;
BOOL pressedPoint;
int index;
float xStart;
float yStart;
int closestIndex;
CGPoint closestComparePoint;
bool moveControl;
int counter;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder])
{
[self setMultipleTouchEnabled:NO];
[self setBackgroundColor:[UIColor clearColor]];
path = [UIBezierPath bezierPath];
[path setLineWidth:1.0];
pathPoints = [NSMutableArray array];
controlPoints = [NSMutableArray array];
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setMultipleTouchEnabled:NO];
path = [UIBezierPath bezierPath];
[path setLineWidth:2.0];
counter = 0;
}
return self;
}
- (void)initValues
{
[pathPoints addObject:[NSValue valueWithCGPoint:CGPointMake(47.0, 130.0)]];
[pathPoints addObject:[NSValue valueWithCGPoint:CGPointMake(14.0, 230.0)]];
[controlPoints addObject:[NSValue valueWithCGPoint:CGPointMake(26.0, 150.0)]];
[pathPoints addObject:[NSValue valueWithCGPoint:CGPointMake(22.0, 320.0)]];
[controlPoints addObject:[NSValue valueWithCGPoint:CGPointMake(9.0, 270.0)]];
[pathPoints addObject:[NSValue valueWithCGPoint:CGPointMake(90.0, 420.0)]];
[controlPoints addObject:[NSValue valueWithCGPoint:CGPointMake(24.0, 380.0)]];
[pathPoints addObject:[NSValue valueWithCGPoint:CGPointMake(230.0, 420.0)]];
[controlPoints addObject:[NSValue valueWithCGPoint:CGPointMake(170.0, 460.0)]];
[pathPoints addObject:[NSValue valueWithCGPoint:CGPointMake(300.0, 320.0)]];
[controlPoints addObject:[NSValue valueWithCGPoint:CGPointMake(280.0, 390)]];
[pathPoints addObject:[NSValue valueWithCGPoint:CGPointMake(307.0, 220.0)]];
[controlPoints addObject:[NSValue valueWithCGPoint:CGPointMake(315.0, 260.0)]];
[pathPoints addObject:[NSValue valueWithCGPoint:CGPointMake(270.0, 130.0)]];
[controlPoints addObject:[NSValue valueWithCGPoint:CGPointMake(300.0, 160.0)]];
[self drawPaths];
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
[[UIColor blueColor] setStroke];
[path stroke];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
counter++;
NSLog(#"%d", counter);
[self.layer.sublayers makeObjectsPerformSelector:#selector(removeFromSuperlayer)];
UITouch *currentTouch = [touches anyObject];
CGPoint startPt = [currentTouch locationInView:self];
if(moveControl)
{
float xOs = [(NSValue*)[controlPoints objectAtIndex:closestIndex] CGPointValue].x;
float yOs = [(NSValue*)[controlPoints objectAtIndex:closestIndex] CGPointValue].y;
[controlPoints replaceObjectAtIndex:closestIndex withObject:[NSValue valueWithCGPoint:CGPointMake(xOs - (xStart-startPt.x), yOs - (yStart - startPt.y))]];
}
else
{
float xOs = [(NSValue*)[pathPoints objectAtIndex:closestIndex] CGPointValue].x;
float yOs = [(NSValue*)[pathPoints objectAtIndex:closestIndex] CGPointValue].y;
[pathPoints replaceObjectAtIndex:closestIndex withObject:[NSValue valueWithCGPoint:CGPointMake(xOs - (xStart-startPt.x), yOs - (yStart - startPt.y))]];
}
[self drawPaths];
xStart = startPt.x;
yStart = startPt.y;
}
- (void)drawPaths
{
path = [UIBezierPath bezierPath];
for(int i = 1; i < [pathPoints count]; i++)
{
//Draw path
[path moveToPoint:((NSValue*)[pathPoints objectAtIndex:i-1]).CGPointValue];
[path addQuadCurveToPoint:((NSValue*)[pathPoints objectAtIndex:i]).CGPointValue controlPoint:((NSValue*)[controlPoints objectAtIndex:i-1]).CGPointValue];
//Draw points
if(i == 1)
{
[self addrectangle:((NSValue*)[pathPoints objectAtIndex:i-1]).CGPointValue];
[self addrectangle:((NSValue*)[pathPoints objectAtIndex:i]).CGPointValue];
[self addQuadrectangle:((NSValue*)[controlPoints objectAtIndex:i-1]).CGPointValue];
}
else
{
[self addrectangle:((NSValue*)[pathPoints objectAtIndex:i]).CGPointValue];
[self addQuadrectangle:((NSValue*)[controlPoints objectAtIndex:i-1]).CGPointValue];
}
}
[self setNeedsDisplay];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *currentTouch = [touches anyObject];
CGPoint startPt = [currentTouch locationInView:self];
closestComparePoint = startPt;
xStart = startPt.x;
yStart = startPt.y;
[self findNearestPoint];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
//[self drawPath];
}
-(void)findNearestPoint
{
int pathClosestIndex = 0;
CGFloat xDist;
CGFloat yDist;
CGFloat Controldistance = 12401240;
CGFloat Pathdistance = 12401240;
for(int i = 0; i < [controlPoints count]; i++)
{
xDist = ABS([(NSValue*)[controlPoints objectAtIndex:i] CGPointValue].x - closestComparePoint.x);
yDist = ABS([(NSValue*)[controlPoints objectAtIndex:i] CGPointValue].y - closestComparePoint.y);
if(sqrt((xDist * xDist) + (yDist * yDist)) < Controldistance)
{
Controldistance = sqrt((xDist * xDist) + (yDist * yDist));
closestIndex = i;
}
}
for(int i = 0; i < [pathPoints count]; i++)
{
xDist = ABS([(NSValue*)[pathPoints objectAtIndex:i] CGPointValue].x - closestComparePoint.x);
yDist = ABS([(NSValue*)[pathPoints objectAtIndex:i] CGPointValue].y - closestComparePoint.y);
if(sqrt((xDist * xDist) + (yDist * yDist)) < Pathdistance)
{
Pathdistance = sqrt((xDist * xDist) + (yDist * yDist));
pathClosestIndex = i;
}
}
if(Pathdistance < Controldistance)
{
moveControl = NO;
closestIndex = pathClosestIndex;
}
else
{
moveControl = YES;
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesEnded:touches withEvent:event];
pressedPoint = NO;
}
-(void)removeRectangles{
[self.layer.sublayers makeObjectsPerformSelector:#selector(removeFromSuperlayer)];
}
-(void)addrectangle:(CGPoint) center{
int offset = 8;
UIBezierPath *path2 = [UIBezierPath bezierPath];
[path2 moveToPoint:CGPointMake(center.x - offset, center.y - offset)];
// # Entry Point with 10px white frame
[path2 moveToPoint:CGPointMake(center.x - offset, center.y - offset)];
// # Keeping 10px frame with iPhone's 450 on y-axis
[path2 addLineToPoint:CGPointMake(center.x - offset, center.y + offset)];
// path2 Substracting 10px for frame on x-axis, and moving 450 in y-axis
[path2 addLineToPoint:CGPointMake(center.x + offset, center.y + offset)];
// # Moving up to 1st step 10px line, 310px on the x-axis
[path2 addLineToPoint:CGPointMake(center.x + offset, center.y - offset)];
// # Back to entry point
[path2 addLineToPoint:CGPointMake(center.x - offset, center.y - offset)];
// 4th Create a CAShapeLayer that uses that UIBezierPath:
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [path2 CGPath];
shapeLayer.strokeColor = [[UIColor blueColor] CGColor];
shapeLayer.lineWidth = 1.0;
shapeLayer.fillColor = [[UIColor clearColor] CGColor];
// 5th add shapeLayer as sublayer inside layer view
[self.layer addSublayer:shapeLayer];
}
-(void)addQuadrectangle:(CGPoint) center{
int offset = 4;
UIBezierPath *path2 = [UIBezierPath bezierPath];
[path2 moveToPoint:CGPointMake(center.x - offset, center.y - offset)];
// # Entry Point with 10px white frame
[path2 moveToPoint:CGPointMake(center.x - offset, center.y - offset)];
// # Keeping 10px frame with iPhone's 450 on y-axis
[path2 addLineToPoint:CGPointMake(center.x - offset, center.y + offset)];
// path2 Substracting 10px for frame on x-axis, and moving 450 in y-axis
[path2 addLineToPoint:CGPointMake(center.x + offset, center.y + offset)];
// # Moving up to 1st step 10px line, 310px on the x-axis
[path2 addLineToPoint:CGPointMake(center.x + offset, center.y - offset)];
// # Back to entry point
[path2 addLineToPoint:CGPointMake(center.x - offset, center.y - offset)];
// 4th Create a CAShapeLayer that uses that UIBezierPath:
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [path2 CGPath];
shapeLayer.strokeColor = [[UIColor redColor] CGColor];
shapeLayer.lineWidth = 1.0;
shapeLayer.fillColor = [[UIColor clearColor] CGColor];
// 5th add shapeLayer as sublayer inside layer view
[self.layer addSublayer:shapeLayer];
}
-(int)findOrigoInX
{
int closestToX = [(NSValue *)[pathPoints firstObject] CGPointValue].x;
for(int i = 1; i < [pathPoints count]; i++)
{
if([(NSValue *)[pathPoints objectAtIndex:i] CGPointValue].x < closestToX)
{
closestToX = [(NSValue *)[pathPoints objectAtIndex:i] CGPointValue].x;
}
}
return closestToX;
}
-(int)findOrigoInY
{
int closestToY = [(NSValue *)[pathPoints firstObject] CGPointValue].y;
for(int i = 1; i < [pathPoints count]; i++)
{
if([(NSValue *)[pathPoints objectAtIndex:i] CGPointValue].y > closestToY)
{
closestToY = [(NSValue *)[pathPoints objectAtIndex:i] CGPointValue].y;
}
}
return closestToY;
}
-(NSMutableArray*)generateKoordinateArray
{
NSLog(#"count in pathPoints : %lu", (unsigned long)[pathPoints count]);
NSMutableArray *storeArray = [[NSMutableArray alloc] init];
BezierCsvFormat *storeBezier = [[BezierCsvFormat alloc] init];
NSString *stringValueX;
NSString *stringValueY;
int xOffset = [self findOrigoInX];
NSLog(#"x offset : %d", xOffset);
int yOffset = [self findOrigoInY];
NSLog(#"y offset : %d", yOffset);
for(int i = 1; i < [pathPoints count]; i++)
{
storeBezier = [[BezierCsvFormat alloc] init];
stringValueX = [NSString stringWithFormat:#"%d", (int)[(NSValue *)[pathPoints objectAtIndex:i-1] CGPointValue].x - xOffset];
stringValueY = [NSString stringWithFormat:#"%d", ABS((int)[(NSValue *)[pathPoints objectAtIndex:i-1] CGPointValue].y - yOffset)];
[storeBezier setp0:[NSString stringWithFormat:#"%#,%#",stringValueX, stringValueY]];
stringValueX = [NSString stringWithFormat:#"%d", (int)[(NSValue *)[controlPoints objectAtIndex:i-1] CGPointValue].x - xOffset];
int multiplicator = 1;
if((int)[(NSValue *)[controlPoints objectAtIndex:i-1] CGPointValue].y > yOffset)
{
multiplicator = multiplicator * -1;
}
stringValueY = [NSString stringWithFormat:#"%d", multiplicator * ABS((int)[(NSValue *)[controlPoints objectAtIndex:i-1] CGPointValue].y - yOffset)];
[storeBezier setp1:[NSString stringWithFormat:#"%#,%#",stringValueX, stringValueY]];
stringValueX = [NSString stringWithFormat:#"%d", (int)[(NSValue *)[pathPoints objectAtIndex:i] CGPointValue].x - xOffset];
stringValueY = [NSString stringWithFormat:#"%d", ABS((int)[(NSValue *)[pathPoints objectAtIndex:i] CGPointValue].y - yOffset)];
[storeBezier setp2:[NSString stringWithFormat:#"%#,%#",stringValueX, stringValueY]];
[storeArray addObject:storeBezier];
}
NSLog(#"count in storeArray : %lu", (unsigned long)[storeArray count]);
return storeArray;
}
- (NSMutableArray*)printCoordinateSystem
{
NSMutableArray *tempArray = [self generateKoordinateArray];
return tempArray;
NSLog(#"count in tempArray : %lu", (unsigned long)[tempArray count]);
for(int i = 0; i < [tempArray count]; i++)
{
NSLog(#"p0 : %# p1 : %# p2 : %#", [(BezierCsvFormat*)[tempArray objectAtIndex:i] getp0], [(BezierCsvFormat*)[tempArray objectAtIndex:i] getp1], [(BezierCsvFormat*)[tempArray objectAtIndex:i] getp2]);
}
}
-(void)resetView{
#try {
[path removeAllPoints];
pathPoints = [NSMutableArray array];
[self.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
[self.layer.sublayers makeObjectsPerformSelector:#selector(removeFromSuperlayer)];
[self setNeedsDisplay];
}
#catch (NSException *exception) {
[self resetView];
}
#finally {
}
}

Related

CABasicAnimation for a CAShapeLayer disappears before it completes, and doesn't continue infinitely

I'm trying to create an infinitely tracing path that draws and the back of it disappears. However, the code I have makes the path disappear after a single iteration. In fact, the layer disappears before the path's disappear animation completes. It seems like, instead, it disappears when the drawing animation completes.
Furthermore, once it disappears, it doesn't show up again for another few seconds (perhaps however long it would take for the animation to repeat) before starting again.
Two questions:
Why is the layer disappearing before the disappearing animation completes?
How can I make the animation play continuously without disappearing?
Thank you in advance! Here's my code:
CGRect boundingFrame = CGRectMake(CGRectGetMidX(self.bounds) - 48.0, CGRectGetMidY(self.bounds) - 48.0, 96.0, 96.0);
_pathPoints = #[
[NSValue valueWithCGPoint:CGPointMake(CGRectGetMidX(boundingFrame), CGRectGetMinY(boundingFrame) + squareSideWidth/2.0)],
[NSValue valueWithCGPoint:CGPointMake(CGRectGetMinX(boundingFrame) + squareSideWidth/2.0, CGRectGetMidY(boundingFrame))],
[NSValue valueWithCGPoint:CGPointMake(CGRectGetMidX(boundingFrame), CGRectGetMaxY(boundingFrame) - squareSideWidth/2.0)],
[NSValue valueWithCGPoint:CGPointMake(CGRectGetMaxX(boundingFrame) - squareSideWidth/2.0, CGRectGetMidY(boundingFrame))]
];
CAShapeLayer *line = [CAShapeLayer layer];
line.path = pathForPoints(_pathPoints).CGPath;
line.lineCap = kCALineCapRound;
line.strokeColor = color.CGColor;
line.fillColor = [UIColor clearColor].CGColor;
line.strokeEnd = 0.0;
[self.layer addSublayer:line];
NSMutableArray<CABasicAnimation *> *animations = [NSMutableArray new];
for (int i = 0; i < [_pathPoints count]; i++) {
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.beginTime = index;
drawAnimation.duration = 1.0;
drawAnimation.fromValue = #(strokeEndForIndex(points, index - 1));
drawAnimation.toValue = #(strokeEndForIndex(points, index));
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
drawAnimation.additive = YES;
drawAnimation.removedOnCompletion = NO;
CABasicAnimation *eraseAnimation = [CABasicAnimation animationWithKeyPath:#"strokeStart"];
eraseAnimation.beginTime = index + 0.75;
eraseAnimation.duration = 1.0;
eraseAnimation.fromValue = #(strokeEndForIndex(points, index - 1));
eraseAnimation.toValue = #(strokeEndForIndex(points, index));
eraseAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
eraseAnimation.additive = YES;
eraseAnimation.removedOnCompletion = NO;
CAAnimationGroup *group = [CAAnimationGroup animation];
group.animations = #[drawAnimation, eraseAnimation];
group.duration = 2.0 * [animations count];
group.repeatCount = INFINITY;
group.removedOnCompletion = NO;
[line addAnimation:group forKey:#"line"];
}
Helper functions, for reference:
static CGFloat lengthOfPathWithPoints(NSArray<NSValue *> *points)
{
CGFloat totalDist = 0;
for (int i = 0; i < [points count]; i++) {
CGFloat xDist = [points[i % [points count]] CGPointValue].x - [points[(i + 1) % [points count]] CGPointValue].x;
CGFloat yDist = [points[i % [points count]] CGPointValue].y - [points[(i + 1) % [points count]] CGPointValue].y;
totalDist += sqrt(pow(xDist, 2) + pow(yDist, 2));
}
return totalDist;
}
static CGFloat strokeEndForIndex(NSArray<NSValue *> *points, int index)
{
// If it's the last index, just return 1 early
if (index == [points count] - 1) {
return 1.0;
}
// In the case where index = -1 (as it does for the initial erase animation fromValue), just return 0
if (index < 0) {
return 0.0;
}
CGFloat totalDist = 0;
for (int i = 0; i < index + 1; i++) {
CGFloat xDist = [points[i % [points count]] CGPointValue].x - [points[(i + 1) % [points count]] CGPointValue].x;
CGFloat yDist = [points[i % [points count]] CGPointValue].y - [points[(i + 1) % [points count]] CGPointValue].y;
totalDist += sqrt(pow(xDist, 2) + pow(yDist, 2));
}
return totalDist/lengthOfPathWithPoints(points);
}
static UIBezierPath *pathForPoints(NSArray<NSValue *> *points)
{
UIBezierPath *path = [UIBezierPath new];
[path moveToPoint:[[points firstObject] CGPointValue]];
for (int i = 0; i < [points count]; i++) {
[path addLineToPoint:[points[(i + 1) % 4] CGPointValue]];
}
return path;
}
Increase your duration property value and change your repeatCount = Float(Int.max) to something like this.

SHGraphLineView remove a plot

I'm using this class called SHLineGraphView and it's working good, but I have a problem that they didn't implemented a methods for removing a plot. Now I have to do it, but I can't quite figure the right way to do.
This is how they drawing the plot:
- (void)addPlot:(SHPlot *)newPlot;
{
if(nil == newPlot) {
return;
}
if(_plots == nil){
_plots = [NSMutableArray array];
}
[_plots addObject:newPlot];
}
- (void)drawPlot:(SHPlot *)plot {
NSDictionary *theme = plot.plotThemeAttributes;
//
CAShapeLayer *backgroundLayer = [CAShapeLayer layer];
backgroundLayer.frame = self.bounds;
backgroundLayer.fillColor = ((UIColor *)theme[kPlotFillColorKey]).CGColor;
backgroundLayer.backgroundColor = [UIColor clearColor].CGColor;
[backgroundLayer setStrokeColor:[UIColor clearColor].CGColor];
[backgroundLayer setLineWidth:((NSNumber *)theme[kPlotStrokeWidthKey]).intValue];
CGMutablePathRef backgroundPath = CGPathCreateMutable();
//
CAShapeLayer *circleLayer = [CAShapeLayer layer];
circleLayer.frame = self.bounds;
circleLayer.fillColor = ((UIColor *)theme[kPlotPointFillColorKey]).CGColor;
circleLayer.backgroundColor = [UIColor clearColor].CGColor;
[circleLayer setStrokeColor:((UIColor *)theme[kPlotPointFillColorKey]).CGColor];
[circleLayer setLineWidth:((NSNumber *)theme[kPlotStrokeWidthKey]).intValue];
CGMutablePathRef circlePath = CGPathCreateMutable();
//
CAShapeLayer *graphLayer = [CAShapeLayer layer];
graphLayer.frame = self.bounds;
graphLayer.fillColor = [UIColor clearColor].CGColor;
graphLayer.backgroundColor = [UIColor clearColor].CGColor;
[graphLayer setStrokeColor:((UIColor *)theme[kPlotStrokeColorKey]).CGColor];
[graphLayer setLineWidth:((NSNumber *)theme[kPlotStrokeWidthKey]).intValue];
CGMutablePathRef graphPath = CGPathCreateMutable();
double yRange = [_yAxisRange doubleValue]; // this value will be in dollars
double yIntervalValue = yRange / INTERVAL_COUNT;
//logic to fill the graph path, ciricle path, background path.
[plot.plottingValues enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSDictionary *dic = (NSDictionary *)obj;
__block NSNumber *_key = nil;
__block NSNumber *_value = nil;
[dic enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
_key = (NSNumber *)key;
_value = (NSNumber *)obj;
}];
int xIndex = [self getIndexForValue:_key forPlot:plot];
//x value
double height = self.bounds.size.height - BOTTOM_MARGIN_TO_LEAVE;
double y = height - ((height / ([_yAxisRange doubleValue] + yIntervalValue)) * [_value doubleValue]);
(plot.xPoints[xIndex]).x = ceil((plot.xPoints[xIndex]).x);
(plot.xPoints[xIndex]).y = ceil(y);
}];
//move to initial point for path and background.
CGPathMoveToPoint(graphPath, NULL, _leftMarginToLeave, plot.xPoints[0].y);
CGPathMoveToPoint(backgroundPath, NULL, _leftMarginToLeave, plot.xPoints[0].y);
int count = _xAxisValues.count;
for(int i=0; i< count; i++){
CGPoint point = plot.xPoints[i];
CGPathAddLineToPoint(graphPath, NULL, point.x, point.y);
CGPathAddLineToPoint(backgroundPath, NULL, point.x, point.y);
CGPathAddEllipseInRect(circlePath, NULL, CGRectMake(point.x - 2.5, point.y - 2.5, 5, 5));
}
//move to initial point for path and background.
CGPathAddLineToPoint(graphPath, NULL, _leftMarginToLeave + PLOT_WIDTH, plot.xPoints[count -1].y);
CGPathAddLineToPoint(backgroundPath, NULL, _leftMarginToLeave + PLOT_WIDTH, plot.xPoints[count - 1].y);
//additional points for background.
CGPathAddLineToPoint(backgroundPath, NULL, _leftMarginToLeave + PLOT_WIDTH, self.bounds.size.height - BOTTOM_MARGIN_TO_LEAVE);
CGPathAddLineToPoint(backgroundPath, NULL, _leftMarginToLeave, self.bounds.size.height - BOTTOM_MARGIN_TO_LEAVE);
CGPathCloseSubpath(backgroundPath);
backgroundLayer.path = backgroundPath;
graphLayer.path = graphPath;
circleLayer.path = circlePath;
//animation
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
animation.duration = 1;
animation.fromValue = #(0.0);
animation.toValue = #(1.0);
[graphLayer addAnimation:animation forKey:#"strokeEnd"];
backgroundLayer.zPosition = 0;
graphLayer.zPosition = 1;
circleLayer.zPosition = 2;
[self.layer addSublayer:graphLayer];
[self.layer addSublayer:circleLayer];
[self.layer addSublayer:backgroundLayer];
NSUInteger count2 = _xAxisValues.count;
for(int i=0; i< count2; i++){
CGPoint point = plot.xPoints[i];
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
btn.backgroundColor = [UIColor clearColor];
btn.tag = i;
btn.frame = CGRectMake(point.x - 20, point.y - 20, 40, 40);
[btn addTarget:self action:#selector(clicked:) forControlEvents:UIControlEventTouchUpInside];
objc_setAssociatedObject(btn, kAssociatedPlotObject, plot, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self addSubview:btn];
}
}
And this is what I've been added, but just can't figure out how to actually remove the plot:
- (void)removePlot:(SHPlot *)plot
{
if (nil == plot) {
return;
}
if (_plots == nil) {
return;
}
[_plots removeObject:plot];
[self removePlotFromView:plot];
}
This is what you need to add in your SHLineGraphView.m file.
This method will remove all the plots added on your graphview.
- (void)removeAllPlots {
for (UIView *view in self.subviews) {
view.layer.sublayers = nil;
[view removeFromSuperview];
}
self.layer.sublayers = nil;
}
For removing a specific plot, first you need to give unique tag to your graphLayer, circleLayer and backgroundLayer.
You can do that by adding following code in your drawPlot: method.
[graphLayer setValue:[NSNumber numberWithInteger:[_plots indexOfObject:plot]] forKey:#"PLTag"];
[circleLayer.frame setValue:[NSNumber numberWithInteger:[_plots indexOfObject:plot]] forKey:#"PLTag"];
[backgroundLayer setValue:[NSNumber numberWithInteger:[_plots indexOfObject:plot]] forKey:#"PLTag"];
Then, add this method to remove specific plot:
- (void)removePlot:(SHPlot *)plot
{
if (plot == nil)
return;
else if (_plots == nil)
return;
for (CALayer *layer in self.layer.sublayers) {
if ([[layer valueForKey:#"PLTag"] isEqualToNumber:[NSNumber numberWithInteger:[_plots indexOfObject:plot]]]) {
[layer removeFromSuperlayer];
}
}
}
Hope it helps you.
So, by #Tejvansh Singh Chhabra's answer I managed to find a solution:
First of all, I've added these 2 new methods to SHGraphLineView.h:
/**
* this methods will remove a plot from the graph.
*
* #param plot the Plot that you want to remove from the Graph.
*/
- (void)removePlot:(SHPlot *)plot;
/**
* this methods will draw a new plot on the graph.
*
* #param plot the Plot that you want to draw on the Graph.
*/
- (void)drawNewPlot:(SHPlot*)plot;
And implement them on the main file:
- (void)removePlot:(SHPlot *)plot
{
if (plot == nil) {
return;
}
else if (_plots == nil) {
return;
}
for (CALayer *layer in [self.layer.sublayers copy])
{
if ([layer valueForKey:#"PLTag"] != nil)
{
NSString *tag = [layer valueForKey:#"PLTag"];
NSString *plotIndex = [NSString stringWithFormat:#"%ld", (long)plot.plotIndex];
if ([tag isEqualToString:plotIndex])
{
[layer removeFromSuperlayer];
}
}
}
[_plots removeObject:plot];
}
- (void)drawNewPlot:(SHPlot*)plot
{
if (nil == plot) {
return;
}
if (_plots == nil) {
_plots = [NSMutableArray array];
}
[_plots addObject:plot];
[self xPointWithoutDrawingForPlot:plot];
[self drawPlot:plot];
}
Now there is another method that create the x point for the plot, but without the labels and all the other this that are drawing with it when you first build the graph:
- (void)xPointWithoutDrawingForPlot:(SHPlot*)plot
{
int xIntervalCount = _xAxisValues.count;
double xIntervalInPx = PLOT_WIDTH / _xAxisValues.count;
//initialize actual x points values where the circle will be
plot.xPoints = calloc(sizeof(CGPoint), xIntervalCount);
for(int i=0; i < xIntervalCount; i++)
{
CGPoint currentLabelPoint = CGPointMake((xIntervalInPx * i) + _leftMarginToLeave, self.bounds.size.height - BOTTOM_MARGIN_TO_LEAVE);
CGRect xLabelFrame = CGRectMake(currentLabelPoint.x , currentLabelPoint.y, xIntervalInPx, BOTTOM_MARGIN_TO_LEAVE);
plot.xPoints[i] = CGPointMake((int) xLabelFrame.origin.x + (xLabelFrame.size.width /2) , (int) 0);
}
}
Another important thing is to add a valueForKey to the CAShapeLayer objects of the plot at first draw, we need to add to this method:
- (void)drawPlot:(SHPlot *)plot;
These 3 lines:
[graphLayer setValue:[NSString stringWithFormat:#"%ld", (long)plot.plotIndex] forKey:#"PLTag"];
[circleLayer setValue:[NSString stringWithFormat:#"%ld", (long)plot.plotIndex] forKey:#"PLTag"];
[backgroundLayer setValue:[NSString stringWithFormat:#"%ld", (long)plot.plotIndex] forKey:#"PLTag"];
One last thing, to SHPlot class we need to add an NSInteger that will indicate the index of this specific plot, and you need to set it when you first build the plot
SHPlot.h
/**
* index for plot for identification
*/
#property (assign, nonatomic) NSInteger plotIndex;
Enjoy! :)

Set Time Clicking on Dial

I am working on an alarm app in which I have to show an analogue clock. The functionality is to set time by clicking on the circumference of the circle displayed as the clock dial.
I have succesfully done this too. But the issue comes at 6 O'Clock or 30 min. The angle comes out to be nan for that which makes my smooth clock totally abrupt.
Here is the code.
myPath1 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(5, 5, 244, 244)];
[myPath1 closePath];
[myPath1 setLineWidth:20];
// Draw a shape Layer with Triangle Path
CAShapeLayer *shapeLayer1 = [CAShapeLayer layer];
shapeLayer1.fillColor = [UIColor clearColor].CGColor;
shapeLayer1.strokeColor = [UIColor clearColor].CGColor;
shapeLayer1.lineWidth = 20;
shapeLayer1.path = myPath1.CGPath;
// Add it to your view
[circleView.layer addSublayer:shapeLayer1];
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:circleView];
if([self containsPointOnStroke:currentPoint onPath:myPath1])
{
[self calculateAngle:currentPoint];
}
}
- (BOOL)containsPointOnStroke:(CGPoint)point onPath:(UIBezierPath *)path
{
CGPathRef cgPath = path.CGPath;
CGPathRef strokedPath = CGPathCreateCopyByStrokingPath(cgPath, NULL, 10,
kCGLineCapRound, kCGLineJoinRound, 1);
BOOL pointIsNearPath = CGPathContainsPoint(strokedPath, NULL, point, NO);
CGPathRelease(strokedPath);
return pointIsNearPath;
}
//---- calculate angle from the point of touch taking center as the reference ----//
- (void) calculateAngle : (CGPoint)currentPoint
{
double distance = sqrt(pow((currentPoint.x - 127), 2.0) + pow((currentPoint.y - 5), 2.0));
float val = ((2 * 14884) - powf((distance), 2)) / (2 * 14884);
float angleVal = acosf(val) * 180/M_PI;
float timeval = angleVal;
//NSLog(#"angleVal : %f timeVal : %.2f", angleVal, timeval);
if (isnan(angleVal))
{
// if the value of the angle val is infinite
if(check == 0)
[hourBtn setTitle:#"06" forState:UIControlStateNormal];
else
[minBtn setTitle:#"30" forState:UIControlStateNormal];
}
else
{
if(check == 0)
{
if (currentPoint.x >= 127)
[hourBtn setTitle:[self getHour:timeval reverseTIme:NO] forState:UIControlStateNormal];
else
[hourBtn setTitle:[self getHour:timeval reverseTIme:YES] forState:UIControlStateNormal];
}
else
{
if (currentPoint.x >= 127)
[minBtn setTitle:[self getMin:timeval reverseTIme:NO] forState:UIControlStateNormal];
else
[minBtn setTitle:[self getMin:timeval reverseTIme:YES] forState:UIControlStateNormal];
}
}
[timeLbl setText:[NSString stringWithFormat:#"%02d:%02d",[hourBtn.titleLabel.text intValue], [minBtn.titleLabel.text intValue]]];
}
//---- get the hour time from the angle ----//
- (NSString *) getHour :(float) angle reverseTIme:(BOOL) reverseCheck
{
int minuteCount = angle * 2;
int quo = minuteCount / 60;
int temp = quo;
if(quo == 0)
quo = 12;
NSString *timeStr = [NSString stringWithFormat:#"%02d", quo];
quo = temp;
if (reverseCheck)
{
int revQuo = 11 - quo;
if(revQuo == 0)
revQuo = 12;
timeStr = [NSString stringWithFormat:#"%02d", revQuo];
}
return timeStr;
}
// get the min time from the angle
- (NSString *) getMin :(float) angle reverseTIme:(BOOL) reverseCheck
{
int minuteCount = angle;
int rem = minuteCount / 6;
NSString *timeStr = [NSString stringWithFormat:#"%02d", rem];
if (reverseCheck)
{
int revRem = 60 - rem;
if(revRem == 60)
revRem = 0;
timeStr = [NSString stringWithFormat:#"%02d", revRem];
}
//NSLog(#"timeStr : %#", timeStr);
return timeStr;
}

Multiple paths are not kept on screen

I have an custom UIView. I keep on the lines and points when the finger touches moves. Then I make points to CGPath with this method:
- (void)makeCGPath
{
CGMutablePathRef path = CGPathCreateMutable();
if (lines&& lines.count>0)
{
for (int i = 0; i < lines.count; i++)
{
CGMutablePathRef linePath = CGPathCreateMutable();
NSArray *tempArray = [lines objectAtIndex:i];
CGPoint p = [[tempArray objectAtIndex:0]CGPointValue];
CGPathMoveToPoint(linePath, NULL, p.x, p.y);
for (int j = 1; j < tempArray.count; j++)
{
p = [[tempArray objectAtIndex:j]CGPointValue];
CGPathAddLineToPoint(linePath, NULL, p.x, p.y);
}
CGPathAddPath(path, NULL, linePath);
CGPathRelease(linePath);
}
}
if (points&& points.count > 0)
{
CGMutablePathRef pointPath = CGPathCreateMutable();
CGPoint p = [[points objectAtIndex:0]CGPointValue];
CGPathMoveToPoint(pointPath, NULL, p.x, p.y);
for (int i = 1; i < points.count;i++ )
{
p = [[points objectAtIndex:i]CGPointValue];
CGPathAddLineToPoint(pointPath, NULL, p.x, p.y);
}
CGPathAddPath(path, NULL, pointPath);
CGPathRelease(pointPath);
}
drawPath = CGPathCreateCopy(path);
[self setNeedsDisplay];
[lines removeAllObjects];
[points removeAllObjects];
}
And my drawRect is look like this follow.
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(ctx, 3);
[[UIColor blackColor]set];
CGContextAddPath(ctx, drawPath);
CGContextStrokePath(ctx);
}
It seems so strange that it displays only the last line on the screen. What's wrong with it?
How can I keep all of these paths on screen?
I have the exact same problem. It's not working on iOS 5.0 but it does work on iOS 5.1 and more. I'm still looking for the fix.
UPADTE:
I fixed the issue using UIBezierPath. You can draw all your paths using Besier and then convert it to CGPath this way:
UIBezierPath *path = [[UIBezierPath alloc] init];
// Add all the lines you need
[path addLineToPoint:p];
// Or points
[path moveToPoint:p];
// Append paths
[path appendPath:linePath];
// Make a CGPath from UIBezierPath
CGPath myCGPath = path.CGPath;
Try this code, it should work but I haven't tested it:
- (void)makeCGPath
{
UIBezierPath *path = [[UIBezierPath alloc] init];
if (lines && lines.count>0)
{
for (int i = 0; i < lines.count; i++)
{
UIBezierPath *linePath = [[UIBezierPath alloc] init];
NSArray *tempArray = [lines objectAtIndex:i];
CGPoint p = [[tempArray objectAtIndex:0]CGPointValue];
[linePath addLineToPoint:p];
for (int j = 1; j < tempArray.count; j++)
{
p = [[tempArray objectAtIndex:j]CGPointValue];
[linePath addLineToPoint:p];
}
[path appendPath:linePath];
}
}
if (points && points.count > 0)
{
UIBezierPath *pointPath = [[UIBezierPath alloc] init];
CGPoint p = [[points objectAtIndex:0]CGPointValue];
[pointPath moveToPoint:p];
for (int i = 1; i < points.count;i++ )
{
p = [[points objectAtIndex:i]CGPointValue];
[pointPath moveToPoint:p];
}
[path appendPath:pointPath];
}
drawPath = path.CGPath;
[self setNeedsDisplay];
[lines removeAllObjects];
[points removeAllObjects];
}

Issue with Drawing on UIImage

I am using the following code to Draw over an image. All the touch delegates are working, but the drawing is not rendered over the UIImage. Here is the code. I am not able to identify the issue. Thanks in advance.
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
[self setupVariables];
}
return self;
}
- (void)drawPic:(UIImage *)thisPic {
myPic = thisPic;
[myPic retain];
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
float newHeight;
float newWidth;
float xPos;
float yPos;
float ratio;
if (myPic != NULL) {
ratio = myPic.size.height/460;
if (myPic.size.width/320 > ratio) {
ratio = myPic.size.width/320;
}
newHeight = myPic.size.height/ratio;
newWidth = myPic.size.width/ratio;
xPos = (320 - newWidth) / 2;
yPos = (460 - newHeight) / 2;
[myPic drawInRect:CGRectMake(xPos, yPos, newWidth, newHeight)];
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *allTouches = [[event allTouches] allObjects];
if ([allTouches count] > 1) {
return;
}
else {
[self drawPoint:[allTouches objectAtIndex:0]];
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *allTouches = [[event allTouches] allObjects];
if ([allTouches count] > 1) {
return;
}
else {
[self drawPoint:[allTouches objectAtIndex:0]];
self.previousPoint = nil;
self.point = nil;
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *allTouches = [[event allTouches] allObjects];
if ([allTouches count] > 1) {
return;
}
else {
[self drawPoint:[allTouches objectAtIndex:0]];
self.previousPoint = nil;
self.point = nil;
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSArray *allTouches = [[event allTouches] allObjects];
if ([allTouches count] > 1) {
return;
}
else {
[self drawPoint:[allTouches objectAtIndex:0]];
}
}
- (void)dealloc {
CGContextRelease(offScreenBuffer);
[point release];
[previousPoint release];
[super dealloc];
}
- (void)setupVariables {
self.point = nil;
self.previousPoint = nil;
offScreenBuffer = [self setupBuffer];
}
- (CGContextRef)setupBuffer {
CGSize size = self.bounds.size;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL,size.width,size.height,8,size.width*4, colorSpace,kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colorSpace);
CGContextTranslateCTM(context, 0, size.height);
CGContextScaleCTM(context, 1.0, -1.0);
return context;
}
- (void)drawToBuffer {
// Red Gr Blu Alpha
CGFloat color[4] = {0.0, 1.0, 0.0, 1.0};
if (self.previousPoint != nil) {
CGContextSetRGBStrokeColor(offScreenBuffer, color[0], color[1], color[2], color[3]);
CGContextBeginPath(offScreenBuffer);
CGContextSetLineWidth(offScreenBuffer, 10.0);
CGContextSetLineCap(offScreenBuffer, kCGLineCapRound);
CGContextMoveToPoint(offScreenBuffer, previousPoint.location.x, previousPoint.location.y);
CGContextAddLineToPoint(offScreenBuffer, point.location.x, point.location.y);
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeColor);
CGContextDrawPath(offScreenBuffer, kCGPathStroke);
}
}
- (void)drawPoint:(UITouch *)touch {
PointLocation *currentLoc = [[PointLocation alloc] init];
currentLoc.location = [touch locationInView:self];
self.previousPoint = self.point;
self.point = currentLoc;
[self drawToBuffer];
[self setNeedsDisplay];
[currentLoc release];
}
It's very simple. You need to do all your drawing in drawRect: and you're only drawing the image in drawRect:, so that's why you only see the image.
-(void)drawPic:(UIImage *)thisPic {
//code for UIViewContentModeScaleAspectFit if needed
self.backgroundColor = [UIColor colorWithPatternImage:thisPic];
[self setNeedsDisplay];
}
Do Not use this in drawRect Method:
[myPic drawInRect:CGRectMake(xPos, yPos, newWidth, newHeight)];
Edit
- (void)drawRect:(CGRect)rect {
if (!myDrawing) {
// CGFloat scale = 0.5;
// CATransform3D transform = CATransform3DMakeScale(scale, scale, scale);
// [self layer].transform = transform;
myDrawing = [[NSMutableArray alloc] initWithCapacity:0];
self.lineOnOffArray = [[NSMutableArray alloc] initWithCapacity:0];
//arrPenThickness = [[NSMutableArray alloc] initWithCapacity:0];
}
CGContextRef ctx = UIGraphicsGetCurrentContext();
if ([myDrawing count] > 0) {
for (int i = 0 ; i < [myDrawing count] ; i++) {
NSArray *thisArray = [myDrawing objectAtIndex:i];
NSString *onOffFlag = [self.lineOnOffArray objectAtIndex:i];
if (([thisArray count] > 2) && (onOffFlag == #"1")){
float penT = [[arrPenThickness objectAtIndex:i] floatValue];
NSString* penC = [arrPenColors objectAtIndex:i];
NSArray *arr = [penC componentsSeparatedByString:#","];
float r = [[arr objectAtIndex:0]floatValue];
float g = [[arr objectAtIndex:1]floatValue];
float b = [[arr objectAtIndex:2]floatValue];
float a = [[arr objectAtIndex:3]floatValue];
CGContextSetRGBStrokeColor(ctx,
r/255.0,
g/255.0,
b/255.0,
a);
CGContextSetLineCap(ctx, kCGLineCapRound);
CGContextSetLineWidth(ctx, penT);
float thisX = [[thisArray objectAtIndex:0] floatValue];
float thisY = [[thisArray objectAtIndex:1] floatValue];
CGContextBeginPath(ctx);
for (int j = 2; j < [thisArray count] ; j+=2) {
CGContextMoveToPoint(ctx, thisX, thisY);
thisX = [[thisArray objectAtIndex:j] floatValue];
thisY = [[thisArray objectAtIndex:j+1] floatValue];
CGContextAddLineToPoint(ctx, thisX,thisY);
}
CGContextStrokePath(ctx);
}
}
}
}

Resources