I'm drawing a path in my SKScene to show the track of my object.
Everything works fine, I can draw the path for all points and add it to scene.
The problem happens when I try to printscreen my scene, everything appears but the path.
Where am i doing it wrong?
- (void)drawPathTrackBall
{
// length of my nsmutablearray with the object track
NSUInteger len = [_trackBallPath count];
// start image context
CGRect bounds = self.scene.view.bounds;
UIGraphicsBeginImageContextWithOptions(bounds.size, YES, [UIScreen mainScreen].scale);
[self.view drawViewHierarchyInRect:bounds afterScreenUpdates:YES];
// draw my path
SKShapeNode *line = [[SKShapeNode alloc] init];
CGMutablePathRef linePath = CGPathCreateMutable();
CGPathMoveToPoint(linePath, NULL, _ballStartX, _ballStartY);
for(NSUInteger i=0; i<len; ++i)
{
NSValue *nsPoint = [_trackBallPath objectAtIndex:i];
CGPoint p = nsPoint.CGPointValue;
CGPathAddLineToPoint(linePath, NULL, p.x, p.y);
}
line.path = linePath;
line.lineWidth = 0.5f;
line.strokeColor = [UIColor redColor];
[_container addChild:line];
_screenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
Thanx
First of all - you're creating simple path, its not connected or drawed to somewhere in your code to context. I didn't learn SpriteKit yet, but I know CoreGraphics well and I think you just can't simply use there SpriteKit API to create a screenshot. Instead try to use native CoreGraphics methods.
To make your code work you need:
Obtain context to which you will draw.
As you've already created a path - just add it to context.
Then stroke/fill it.
To obtain current context to draw call: CGContextRef ctx = UIGraphicsGetCurrentContext();
To add path use : CGContextAddPath(ctx, path);
To stroke/fill use CGContextStrokePath(ctx); / CGContextFillPath(ctx);
So your updated code should look like(notice I've removed code related to SpriteKit stuff):
- (void)drawPathTrackBall
{
// length of my nsmutablearray with the object track
NSUInteger len = [_trackBallPath count];
// start image context
CGRect bounds = self.scene.view.bounds;
UIGraphicsBeginImageContextWithOptions(bounds.size, YES, [UIScreen mainScreen].scale);
[self.view drawViewHierarchyInRect:bounds afterScreenUpdates:YES];
CGContextRef ctx = UIGraphicsGetCurrentContext(); // Obtain context to draw.
CGMutablePathRef linePath = CGPathCreateMutable();
CGPathMoveToPoint(linePath, NULL, _ballStartX, _ballStartY);
for(NSUInteger i=0; i<len; ++i)
{
NSValue *nsPoint = [_trackBallPath objectAtIndex:i];
CGPoint p = nsPoint.CGPointValue;
CGPathAddLineToPoint(linePath, NULL, p.x, p.y);
}
// I've exchanged SpriteKit node API calls with CoreGraphics equivalents.
CGContextSetLineWidth(ctx, 0.5f);
CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextAddPath(ctx, linePath);
CGContextStrokePath(ctx);
_screenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
Try it.
Related
Our app contains several MKPolyline boundaries that all create a closed in polygon. These are primarily to display as an MKOverlay on a MKMapView but I'm looking for a solution to display these polygons as small thumbnails to be visible not on the MKMapView but instead as a standard UIImage or UIImageView.
Just to be clear, I'm wanting these small thumbnails would just be displayed as small shapes that have a stroke color and a fill color but without any map background.
Could anyone help me with this?
Here you go.
+ (UIImage *)imageNamed:(NSString *)name withColor:(UIColor *)color{
// load the image
UIImage *img = [UIImage imageNamed:name];
// begin a new image context, to draw our colored image onto
UIGraphicsBeginImageContext(img.size);
// get a reference to that context we created
CGContextRef context = UIGraphicsGetCurrentContext();
// set the fill color
[color setFill];
// translate/flip the graphics context (for transforming from CG* coords to UI* coords
CGContextTranslateCTM(context, 0, img.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// set the blend mode to color burn, and the original image
CGContextSetBlendMode(context, kCGBlendModeColorBurn);
CGRect rect = CGRectMake(0, 0, img.size.width, img.size.height);
CGContextDrawImage(context, rect, img.CGImage);
// set a mask that matches the shape of the image, then draw (color burn) a colored rectangle
CGContextClipToMask(context, rect, img.CGImage);
CGContextAddRect(context, rect);
CGContextDrawPath(context,kCGPathFill);
// generate a new UIImage from the graphics context we drew onto
UIImage *coloredImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//return the color-burned image
return coloredImg;
}
Please check this original post for detail description.
I had to do exactly the same in my own app. Here is my solution : I generate a UIView which represents path's shape. In your case the path is a MKPolyline.
Here is my code :
+ (UIView *)createShapeForGPX:(GPX *)gpx
withFrameSize:(CGSize)frameSize
lineColor:(UIColor *)lineColor {
// Array of coordinates (Adapt this code with your coordinates)
// Note : in my case I have a double loops because points are in paths
// and I can have many paths for one route. So I concact all points
// into one array to simplify the code for your case. If you also have
// many paths, you have to change a little bit next code.
NSMutableArray<NSValue *> *dataPoints = [NSMutableArray new];
for (NSArray *path in gpx.paths) {
for (NSDictionary *point in path) {
double latitude = [point[#"latitude"] doubleValue];
double longitude = [point[#"longitude"] doubleValue];
[dataPoints addObject:[NSValue valueWithCGPoint:CGPointMake(longitude, latitude)]];
}
}
// Graph bounds (You need to calculate topRightCoordinate and bottomleftCoordinate. You can do it in previous for loop)
double lngBorder = gpx.topRightCoordinate.longitude - gpx.bottomLeftCoordinate.longitude;
double latBorder = gpx.topRightCoordinate.latitude - gpx.bottomLeftCoordinate.latitude;
double middleLng = gpx.bottomLeftCoordinate.longitude + (lngBorder / 2.f);
double middleLat = gpx.bottomLeftCoordinate.latitude + (latBorder / 2.f);
double boundLength = MAX(lngBorder, latBorder);
// *** Drawing ***
CGFloat margin = 4.f;
UIView *graph = [UIView new];
graph.frame = CGRectMake(0, 0, frameSize.width - margin, frameSize.height - margin);
CAShapeLayer *line = [CAShapeLayer layer];
UIBezierPath *linePath = [UIBezierPath bezierPath];
float xAxisMin = middleLng - (boundLength / 2.f);
float xAxisMax = middleLng + (boundLength / 2.f);
float yAxisMin = middleLat - (boundLength / 2.f);
float yAxisMax = middleLat + (boundLength / 2.f);
int i = 0;
while (i < dataPoints.count) {
CGPoint point = [dataPoints[i] CGPointValue];
float xRatio = 1.0-((xAxisMax-point.x)/(xAxisMax-xAxisMin));
float yRatio = 1.0-((yAxisMax-point.y)/(yAxisMax-yAxisMin));
float x = xRatio*(frameSize.width - margin / 2);
float y = (1.0-yRatio)*(frameSize.height - margin);
if (i == 0) {
[linePath moveToPoint:CGPointMake(x, y)];
} else {
[linePath addLineToPoint:CGPointMake(x, y)];
}
i++;
}
// Line
line.lineWidth = 0.8;
line.path = linePath.CGPath;
line.fillColor = [[UIColor clearColor] CGColor];
line.strokeColor = [lineColor CGColor];
[graph.layer addSublayer:line];
graph.backgroundColor = [UIColor clearColor];
// Final view (add margins)
UIView *finalView = [UIView new];
finalView.backgroundColor = [UIColor clearColor];
finalView.frame = CGRectMake(0, 0, frameSize.width, frameSize.height);
graph.center = CGPointMake(CGRectGetMidX(finalView.bounds), CGRectGetMidY(finalView.bounds));
[finalView addSubview:graph];
return finalView;
}
In my case GPX class contains few values :
- NSArray<NSArray<NSDictionary *> *> *paths; : contains all points of all paths. In your case I think it is your MKPolyline.
- topRightCoordinate and bottomLeftCoordinate : Two CLLocationCoordinate2D that represent top right and bottom left virtual coordinates of my path (you have to calcul them also).
You call this method like that :
UIView *shape = [YOURCLASS createShapeForGPX:gpx withFrameSize:CGSizeMake(32, 32) lineColor:[UIColor blackColor]];
This solution is based on this question how to draw a line graph in ios? Any control which will help me show graph data in ios which gives a solution to draw a graph from points.
Maybe all this code is not usefull for you (like margins) but it should help you to find your own solution.
Here is how it displays in my app (in a UITableView) :
I came up with a scenario like drawing a line graph using DrawRect method. Using the same code i should plot three more types of line graphs. How should i clear already plotted graph and draw another...Posting one of the clasess drawRect method. Please look at it and suggest me a solution for this
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, self.bounds);
CGContextSetAllowsAntialiasing(context, true);
CGRect currentBounds = self.bounds;
CGFloat dotsWidth = self.numberOfPages*kDotDiameter + MAX(0, self.numberOfPages-1)*kDotSpacer;
CGFloat x = CGRectGetMidX(currentBounds)-dotsWidth/2;
CGFloat y = CGRectGetMidY(currentBounds)-kDotDiameter/2;
for (int i=0; i<_numberOfPages; i++)
{
CGRect circleRect = CGRectMake(x, y, kDotDiameter, kDotDiameter);
if (i == _currentPage)
{
CGContextSetFillColorWithColor(context, [UIColor orangeColor].CGColor);
}
else
{
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
}
CGContextFillEllipseInRect(context, circleRect);
x += kDotDiameter + kDotSpacer;
}
}
You can try the following, rect should be containing the line graph bounds.
CGContextClearRect(UIGraphicsGetCurrentContext(), rect);
I have an UILabel subclass that draws some text in drawinRect method using a pattern image. This pattern image is a gradient image I am creating.
-(void)drawRect:(CGRect)rect
{
UIColor *color = [UIColor colorWithPatternImage:[self gradientImage]];
[color set];
[someText drawInRect:rec withFont:font lineBreakMode:UILineBreakModeWordWrap alignment:someAlignment];
}
The gradient image method is
-(UIImage *)gradientImage
{
NSData *colorData = [[NSUserDefaults standardUserDefaults] objectForKey:#"myColor"];
UIColor *co = [NSKeyedUnarchiver unarchiveObjectWithData:colorData];
colors = [NSArray arrayWithObjects:co,co,[UIColor whiteColor],co,co,co,co,nil];
NSArray *gradientColors=colors;
CGFloat width;
CGFloat height;
CGSize textSize = [someText sizeWithFont:font];
width=textSize.width; height=textSize.height;
UIGraphicsBeginImageContext(CGSizeMake(width, height));
// get context
CGContextRef context = UIGraphicsGetCurrentContext();
// push context to make it current (need to do this manually because we are not drawing in a UIView)
UIGraphicsPushContext(context);
//draw gradient
CGGradientRef gradient;
CGColorSpaceRef rgbColorspace;
//set uniform distribution of color locations
size_t num_locations = [gradientColors count];
CGFloat locations[num_locations];
for (int k=0; k<num_locations; k++)
{
locations[k] = k / (CGFloat)(num_locations - 1); //we need the locations to start at 0.0 and end at 1.0, equaly filling the domain
}
//create c array from color array
CGFloat components[num_locations * 4];
for (int i=0; i<num_locations; i++)
{
UIColor *color = [gradientColors objectAtIndex:i];
NSAssert(color.canProvideRGBComponents, #"Color components could not be extracted from StyleLabel gradient colors.");
components[4*i+0] = color.red;
components[4*i+1] = color.green;
components[4*i+2] = color.blue;
components[4*i+3] = color.alpha;
}
rgbColorspace = CGColorSpaceCreateDeviceRGB();
gradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, num_locations);
CGRect currentBounds = self.bounds;
CGPoint topCenter = CGPointMake(CGRectGetMidX(currentBounds), 0.0f);
CGPoint midCenter = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMidY(currentBounds));
CGContextDrawLinearGradient(context, gradient, topCenter, midCenter, 0);
CGGradientRelease(gradient);
CGColorSpaceRelease(rgbColorspace);
// pop context
UIGraphicsPopContext();
// get a UIImage from the image context
UIImage *gradientImage = UIGraphicsGetImageFromCurrentImageContext();
// clean up drawing environment
UIGraphicsEndImageContext();
return gradientImage;
}
Everything is working fine. The text is drawn with nice glossy effect. Now my problem is if I change the x or y position of the text inside the UILabel and then call the
[someText drawinRect:rec]... to redraw, the gloss effect is generated differently. It seems that the pattern image is changing with change of text position..
The following is the effect for the frame rec = CGRectMake(0, 10, self.frame.size.width, self.frame.size.height);
The following is the effect for the frame rec = CGRectMake(0, 40, self.frame.size.width, self.frame.size.height);
I hope I have explained my question well. Could not find any exact answer elsewhere. Thanks in advance.
You might want to look at using an existing 3rd party solution like FXLabel.
friends, I am working with UIBezierPaths, for free hand drawing , and everything works fine, for this I am storing my paths in a path array, everything works fine, while rendering, I am looping the whole array and rendering the paths,but soon as the array count increases, I see a lag while drawing, below is my drawRect code. please help me out in finding where I am going wrong
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
m_previousPoint2 = m_previousPoint1;
m_previousPoint1 = [mytouch previousLocationInView:self];
m_currentPoint = [mytouch locationInView:self];
CGPoint mid1 = midPoint(m_previousPoint1, m_previousPoint2);
CGPoint mid2 = midPoint(m_currentPoint, m_previousPoint1);
testpath = CGPathCreateMutable();
CGPathMoveToPoint(testpath, NULL, mid1.x, mid1.y);
CGPathAddQuadCurveToPoint(testpath, NULL, m_previousPoint1.x, m_previousPoint1.y, mid2.x, mid2.y);
CGRect bounds = CGPathGetBoundingBox(testpath);
CGPathRelease(testpath);
CGRect drawBox = bounds;
//Pad our values so the bounding box respects our line width
drawBox.origin.x -= self.lineWidth * 2;
drawBox.origin.y -= self.lineWidth * 2;
drawBox.size.width += self.lineWidth * 4;
drawBox.size.height += self.lineWidth * 4;
CGContextRef context = UIGraphicsGetCurrentContext();
context = CGLayerGetContext(myLayerRef);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
[self setNeedsDisplayInRect:drawBox];
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
if(myLayerRef == nil)
{
myLayerRef = CGLayerCreateWithContext(context, self.bounds.size, NULL);
}
[self.layer renderInContext:context];
CGPoint mid1 = midPoint(m_previousPoint1, m_previousPoint2);
CGPoint mid2 = midPoint(m_currentPoint, m_previousPoint1);
CGContextMoveToPoint(context, mid1.x, mid1.y);
CGContextAddQuadCurveToPoint(context, m_previousPoint1.x, m_previousPoint1.y, mid2.x, mid2.y);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, self.lineWidth);
CGContextSetStrokeColorWithColor(context, self.lineColor.CGColor);
CGContextSetFlatness(context, 0.1);
CGContextSetAllowsAntialiasing(context, true);
CGContextStrokePath(context);
[super drawRect:rect];
}
Code updated according to the below discussion by #borrrden
Regards
Ranjit
There is an excellent session in the WWDC 2012 sessions about this exact problem. I think it is called iOS performance: Graphics or something similar. You should definitely watch it.
The summary is, DON'T store it in a path. There is no need to keep this path information around. Store a separate context (CGLayer is best) and draw into that (just like you add points to the path, add points to the context intead). Then in your drawRect, simply draw that layer into the current graphics context. Also be sure to only call setNeedsDisplay on the rectangle that changed, or you will most likely run into problems on a high resolution device.
I'm working on the Stanford CS193p course and am trying to draw a graph. Drawing the axes works, but I can't draw the graph itself. I'm getting the message
CGContextAddLineToPoint: no current point.
when I try to draw. Here's the code.
- (void)drawRect:(CGRect)rect
{
NSLog(#"Drawing Graph View");
CGPoint origin = CGPointMake(20, 350);
CGRect rect2 = CGRectMake(origin.x, origin.y, 250, -250);
[AxesDrawer drawAxesInRect:rect2 originAtPoint:origin scale:[self scale]];
CGContextRef context = UIGraphicsGetCurrentContext();
UIGraphicsPushContext(context);
CGContextSetLineWidth(context, 2);
[[UIColor redColor] setStroke];
CGContextMoveToPoint(context, origin.x, origin.y);
for (CGFloat i=0; i<250; i++) {
CGFloat yChange = [self.dataSource deltaY:self];
CGContextAddLineToPoint(context, origin.x+i, origin.y-yChange);
CGContextStrokePath(context);
}
UIGraphicsPopContext();
}
You have to place CGContextStrokePath(context); outside the for loop. Otherwise it will create a fresh path on every run through the loop and that fails.
Do you not have a problem with:
CGRectMake(origin.x, origin.y, 250, -250)
You specify a negative height!