Infinitely scrollable UIView for a graph - ios

I'm relatively new to graphics. This block of code simply draws graph nodes and edges in a UIView. This code works fine for a small number of nodes, but takes a 10+ seconds to load when the number of nodes and edges exceeds 200 in a UIView that is equaly to the frame of the iPad. Am I drawing inefficiently? My goal is to create an 'infinite' scrollable/zoomable graph view where nodes and edges are loaded only when they become visible. I believe that UIView dimensions are limited, so inserting an extremely large UIView into a UIScrollView will not work.
Currently, I am creating a UIView that is slightly larger than the dimensions of the iPad and placing that within a UIScrollView. All nodes and edges are being rendered, even if they are not visible. This is most likely inefficient.
How can I minimize time drawing? How can I create an infinite scrollable/zoomable view?
Thanks
A sample of the drawing:
UIView Code:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
//Set background
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextFillRect(context, self.bounds);
//Translate the coordinate system relative to the minimum and maximum points in the graph
CGContextTranslateCTM(context, -graph.minX + MAP_BORDER_WIDTH, -graph.minY + MAP_BORDER_HEIGHT);
if (graph) {
//Draw all edges in graph
for (FNETEdge *edge in graph.edges) {
[self drawEdge:edge
inRect:rect];
}
//Draw all nodes in graph
for (FNETNode *node in graph.nodes) {
[self drawNode:node
inRect:rect];
}
}
CGContextRestoreGState(context);
}
- (void)drawNode:(FNETNode*)node inRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
//Get the size of the text
NSMutableAttributedString *text = node.getAttributedText;
CGSize maxTextSize = [text boundingRectWithSize:CGSizeMake(0, 0) options:NSStringDrawingUsesLineFragmentOrigin context:nil].size;
//Find the width and height of the node
CGFloat width = NODE_PADDING + node.icon.size.width + NODE_SPACING + maxTextSize.width + NODE_PADDING;
CGFloat height = node.icon.size.height > maxTextSize.height ? (NODE_PADDING + node.icon.size.height + NODE_PADDING) : (NODE_PADDING + maxTextSize.height + NODE_PADDING);
//Top left corner of the node
CGPoint topLeftPoint = CGPointMake(node.center.x - width/2, node.center.y - height/2);
//Inner and outer rectangles for node
CGRect outerNodeRect = CGRectMake(topLeftPoint.x, topLeftPoint.y, width, height);
CGRect innerNodeRect = CGRectMake(topLeftPoint.x + NODE_PADDING, topLeftPoint.y + NODE_PADDING, width - (2 * NODE_PADDING), height - (2 * NODE_PADDING));
//Find where we will draw the icon and the text
CGRect iconRect = CGRectMake(innerNodeRect.origin.x, topLeftPoint.y + (height - node.icon.size.height) / 2, node.icon.size.width, node.icon.size.height);
CGRect textRect = CGRectMake(innerNodeRect.origin.x + node.icon.size.width + NODE_SPACING, innerNodeRect.origin.y, maxTextSize.width, maxTextSize.height);
//Fill background
[[node getDeviceColor] setFill];
UIBezierPath *roundedRect = [UIBezierPath bezierPathWithRoundedRect:outerNodeRect cornerRadius:8];
[roundedRect fillWithBlendMode: kCGBlendModeNormal alpha:1.0f];
//Draw icon
[node.icon drawInRect:iconRect];
//Draw text
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
[text drawWithRect:textRect options:NSStringDrawingUsesLineFragmentOrigin context:nil];
CGContextRestoreGState(context);
}
- (void)drawEdge:(FNETEdge*)edge inRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetLineCap(context, kCGLineCapSquare);
CGContextSetStrokeColorWithColor(context, [edge getSpeedColor].CGColor);
CGContextSetLineWidth(context, EDGE_LINE_WIDTH);
//Iterate through all points and connect the dots!
for (int i = 0; i < edge.points.count -1; i++) {
CGPoint startPoint = ((NSValue*)[edge.points objectAtIndex:i]).CGPointValue;
CGPoint endPoint = ((NSValue*)[edge.points objectAtIndex:i+1]).CGPointValue;
CGContextMoveToPoint(context, startPoint.x, startPoint.y);
CGContextAddLineToPoint(context, endPoint.x, endPoint.y);
}
CGContextStrokePath(context);
CGContextRestoreGState(context);
}

Related

Draw Triangles inside Circle Objective-C [duplicate]

For an iPhone application I want to draw a circle, that is only for an x percentage filled.
Something like this:
I have no problems calculating the radius, the degrees or the radians, that is no problem. Also drawing the circle is already done. But how do I get the iPhone SDK to draw the part that is filled.
I can draw a rectangle that size, but not part of a circle.
I just want to draw that on a a normal context.
Hope someone can give me any pointers here.
A lot of people have showed you how this can be done in Core Graphics but it can also be done with Core Animation which gives the big addition of easily being able to animate the percentage of the pie shape.
The following code will create both the ring and the partly filled layers (even though you said that you already can draw the ring) since its nice to have both the ring and the pie shape to be drawn using the same method.
If you animate the strokeStart or strokeEnd properties of the pieShape layer you will have the percentage animate. As with all Core Animation code you will need to add QuartzCore.framework to your project and include <QuartzCore/QuartzCore.h> in your code.
// Create a white ring that fills the entire frame and is 2 points wide.
// Its frame is inset 1 point to fit for the 2 point stroke width
CGFloat radius = MIN(self.frame.size.width,self.frame.size.height)/2;
CGFloat inset = 1;
CAShapeLayer *ring = [CAShapeLayer layer];
ring.path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(self.bounds, inset, inset)
cornerRadius:radius-inset].CGPath;
ring.fillColor = [UIColor clearColor].CGColor;
ring.strokeColor = [UIColor whiteColor].CGColor;
ring.lineWidth = 2;
// Create a white pie-chart-like shape inside the white ring (above).
// The outside of the shape should be inside the ring, therefore the
// frame needs to be inset radius/2 (for its outside to be on
// the outside of the ring) + 2 (to be 2 points in).
CAShapeLayer *pieShape = [CAShapeLayer layer];
inset = radius/2 + 2; // The inset is updated here
pieShape.path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(self.bounds, inset, inset)
cornerRadius:radius-inset].CGPath;
pieShape.fillColor = [UIColor clearColor].CGColor;
pieShape.strokeColor = [UIColor whiteColor].CGColor;
pieShape.lineWidth = (radius-inset)*2;
// Add sublayers
// NOTE: the following code is used in a UIView subclass (thus self is a view)
// If you instead chose to use this code in a view controller you should instead
// use self.view.layer to access the view of your view controller.
[self.layer addSublayer:ring];
[self.layer addSublayer:pieShape];
Use CGContext's arc functions:
CGContextAddArc(context,
centerX,
centerY,
radius,
startAngleRadians,
endAngleRadians,
clockwise ? 1 : 0);
See the documentation for CGContextAddArc().
Try this:
CGContextMoveToPoint(the center point)
CGContextAddLineToPoint(the starting point of the fill path on the circumference)
CGContextAddArcToPoint(the ending point of the fill path on the circumference)
CGContextAddLineToPoint(the center point)
CGContextFillPath
I implemented a pie progress view that looks similar to what you are doing. It's open source. Hopefully the source code will help.
SSPieProgressView.h source
SSPieProgressView.m source
CircleViewController.h
#import <UIKit/UIKit.h>
#interface CircleViewController : UIViewController
#end
CircleViewController.m
#import "CircleViewController.h"
#import "GraphView.h"
#interface CircleViewController ()
#end
#implementation CircleViewController
- (void)viewDidLoad {
[super viewDidLoad];
GraphView *graphView = [[GraphView alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
graphView.backgroundColor = [UIColor whiteColor];
graphView.layer.borderColor = [UIColor redColor].CGColor;
graphView.layer.borderWidth = 1.0f;
[self.view addSubview:graphView];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
GraphView.h
#import <UIKit/UIKit.h>
#interface GraphView : UIView
#end
GraphView.m
#import "GraphView.h"
#implementation GraphView
- (void)drawRect:(CGRect)rect {
CGPoint circleCenter = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
[self drawCircleWithCircleCenter:(CGPoint) circleCenter radius:80 firstColor:[UIColor blueColor].CGColor secondeColor:[UIColor redColor].CGColor lineWidth:2 startDegree:0 currentDegree:90];
//[self drawCircleWithCircleCenter2:(CGPoint) circleCenter radius:80 firstColor:[UIColor blueColor].CGColor secondeColor:[UIColor redColor].CGColor lineWidth:2 startDegree:0 currentDegree:90];
}
- (void)drawCircleWithCircleCenter:(CGPoint) circleCenter
radius:(CGFloat)radius
firstColor:(CGColorRef)firstColor
secondeColor:(CGColorRef)secondeColor
lineWidth:(CGFloat)lineWidth
startDegree:(float)startDegree
currentDegree:(float)endDegree {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, lineWidth);
CGContextMoveToPoint(context, circleCenter.x, circleCenter.y);
CGContextAddArc(context, circleCenter.x , circleCenter.y, radius, [self radians:startDegree], [self radians:endDegree], 0);
CGContextSetFillColorWithColor(context, firstColor);
CGContextFillPath(context);
CGContextMoveToPoint(context, circleCenter.x, circleCenter.y);
CGContextAddArc(context, circleCenter.x, circleCenter.y, radius, [self radians:endDegree], [self radians:startDegree], 0);
CGContextSetFillColorWithColor(context, secondeColor);
CGContextFillPath(context);
}
- (void)drawCircleWithCircleCenter2:(CGPoint) circleCenter
radius:(CGFloat)radius
firstColor:(CGColorRef)firstColor
secondeColor:(CGColorRef)secondeColor
lineWidth:(CGFloat)lineWidth
startDegree:(float)startDegree
currentDegree:(float)endDegree {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, lineWidth);
CGContextMoveToPoint(context, circleCenter.x, circleCenter.y);
CGContextAddArc(context, circleCenter.x , circleCenter.y, radius, [self radians:startDegree], [self radians:endDegree], 0);
CGContextSetFillColorWithColor(context, firstColor);
CGContextFillPath(context);
CGContextMoveToPoint(context, circleCenter.x, circleCenter.y);
CGContextAddArc(context, circleCenter.x, circleCenter.y, radius, [self radians:endDegree], [self radians:startDegree], 0);
CGContextSetStrokeColorWithColor(context, secondeColor);
CGContextStrokePath(context);
}
-(float) radians:(double) degrees {
return degrees * M_PI / 180;
}
#end
note: you can use one of the 2 methods:
"drawCircleWithCircleCenter" or "drawCircleWithCircleCenter2"
this code if you want to split cell on 2 parts only
if you want to split cell on more than 2 parts you can check this : "Drawing a circle ,filled different parts with different color" and check the answer start with this Phrase "we have 6 class"
Well, since nobody used NSBezierPath so far, I figured I could provide the solution I recently used for the same problem:
-(void)drawRect:(NSRect)dirtyRect
{
double start = -10.0; //degrees
double end = 190.0; //degrees
NSPoint center = NSMakePoint(350, 200);
double radius = 50;
NSBezierPath *sector = [NSBezierPath bezierPath];
[sector moveToPoint:center];
[sector appendBezierPathWithArcWithCenter:center radius:radius startAngle:start endAngle:end];
[sector lineToPoint:center];
[sector fill];
}
Below is a full method I am using that does this with Core Graphics, adapting and expanding on mharper's comment above.
This code is for OSX Cocoa, but could easily be changed to iOS, by modifying how you get the context.
- (void)drawPieShapedCircleWithRadius:(CGFloat)radius
strokeColor:(CGColorRef)strokeColor
fillColor:(CGColorRef)fillColor
lineWidth:(CGFloat)lineWidth
currentDegrees:(float)currentDegrees
startDegrees:(float)startDegrees {
// get the context
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
// Set the color of the circle stroke and fill
CGContextSetStrokeColorWithColor(context, strokeColor);
CGContextSetFillColorWithColor(context, fillColor);
// Set the line width of the circle
CGContextSetLineWidth(context, 1);
// Calculate the middle of the circle
CGPoint circleCenter = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2);
// Move the bezier to the center of the circle
CGContextMoveToPoint(context, circleCenter.x, circleCenter.y); // move to the center point
// Draw the arc from the start point (hardcoded as the bottom of the circle) to the center
CGContextAddLineToPoint(context, circleCenter.x, circleCenter.y + radius);
// Draw the arc around the circle from the start degrees point to the current degrees point
CGContextAddArc(context, circleCenter.x , circleCenter.y, radius, [self radians:startDegrees], [self radians:startDegrees + currentDegrees], 0);
// Draw the line back into the center of the circle
CGContextAddLineToPoint(context, circleCenter.x, circleCenter.y);
// Fill the circle
CGContextFillPath(context);
// Draw the line around the circle
CGContextStrokePath(context);
}
Try this code in a UIView, Example "MyChartClass"...
- (void)drawRect:(CGRect)rect {
int c=(int)[itemArray count];
CGFloat angleArray[c];
CGFloat offset;
int sum=0;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetAllowsAntialiasing(context, false);
CGContextSetShouldAntialias(context, false);
for(int i=0;i<[itemArray count];i++) {
sum+=[[itemArray objectAtIndex:i] intValue];
}
for(int i=0;i<[itemArray count];i++) {
angleArray[i]=(float)(([[itemArray objectAtIndex:i] intValue])/(float)sum)*(2*3.14);
CGContextMoveToPoint(context, radius, radius);
if(i==0)
CGContextAddArc(context, radius, radius, radius, 0,angleArray[i], 0);
else
CGContextAddArc(context, radius, radius, radius,offset,offset+angleArray[i], 0);
offset+=angleArray[i];
CGContextSetFillColorWithColor(context, ((UIColor *)[myColorArray objectAtIndex:i]).CGColor);
CGContextClosePath(context);
CGContextFillPath(context);
}
}
Implementation in your UIViewController
MyChartClass *myChartClass=[[MyChartClass alloc]initWithFrame:CGRectMake(0, 0, 200, 200)];
myChartClass.backgroundColor = [UIColor clearColor];
myChartClass.itemArray=[[NSArray alloc]initWithObjects:#"75",#"25", nil];
myChartClass.myColorArray=[[NSArray alloc]initWithObjects:[UIColor blackColor],[UIColor whiteColor], nil];
myChartClass.radius=100;
[self.view addSubview:myChartClass];
Regards.

How to clear drawrect in Ios?

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);

Memory Management of multiple simple UIViews

Background:
I'm making an app that is grid-based using ARC. Basically there is a 4x4-8x8 grid in the center of the screen (that takes up most of the screen). This grid is constructed using a single UIView that is tinted some color and lines drawn with drawRect: (I'll be posting all of the relevant code below for reference).
Each of the cells is contained inside an NSMutableArray for each row that is contained inside another NSMutableArray of the rows:
Array (Rows)
Array (Cols)
Cell Contents
In each of these cells, I either have an actor object or a placeholder object. The placeholder object is essentially just a blank NSObject while the actor object has 8 primitive properties and 1 object property.
For instance, one of the actors is a source, which essentially recursively draws a plain UIView from the source across the grid until it hits another actor or a wall of the grid.
The blue and red lines show different UIViews as they are currently running. With a grid this small, memory doesn't seem to be an issue often; however, when the full game runs with an 8x8 grid, there can feasibly be 50+ drawn UIViews on the screen in addition to the UIImageViews that function as the sources, movables, etc. as well as the other UILabels and buttons that are not included in the grid. There can easily be over 100 UIViews on the screen at once, which, even on the latest devices with the best hardware, causes some pretty bad lag.
I have a feeling that this has to do with the fact that I am rendering 100+ views to the screen at once.
Question:
Can I incorporate all of these dynamically drawn lines into one view, or is there a better solution entirely?
drawRect:
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorRef color = [[self backgroundColor] CGColor];
int numComponents = CGColorGetNumberOfComponents(color);
CGFloat red = 0, green = 0, blue = 0;
if (numComponents == 4)
{
const CGFloat *components = CGColorGetComponents(color);
red = components[0];
green = components[1];
blue = components[2];
}
CGContextSetRGBFillColor(context, red, green, blue, 0.5);
float top;
float cell = [self cellWidth];
float grid = [self gridWidth];
//Draw
for(int i = 0; i < [self size]+1; i++)
{
top = i*(cell+lineWidth);
CGContextFillRect(context, CGRectMake(0, top, grid, lineWidth));
CGContextFillRect(context, CGRectMake(top, 0, lineWidth, grid));
}
}
addSources:
- (void)addSources:(NSArray*)sources
{
for(int i = 0; i < [sources count]; i++)
{
NSArray* src = [sources objectAtIndex:i];
int row = [[src objectAtIndex:1] intValue];
int column = [[src objectAtIndex:0] intValue];
int direction = [[src objectAtIndex:2] intValue];
int color = [UIColor colorKeyForString:[src objectAtIndex:3]];
float width = [self cellWidth]*scaleActors;
float x = lineWidth + (([self cellWidth]+lineWidth) * (column-1)) + (([self cellWidth]-width)/2.0);
float y = lineWidth + (([self cellWidth]+lineWidth) * (row-1)) + (([self cellWidth]-width)/2.0);
ActorView* actor = [[ActorView alloc] initWithFrame:CGRectMake(x, y, width, width)];
[actor setType:4];
[actor setDirection:direction];
[actor setColorKey:color];
[actor setIsGlowing:YES];
[actor setPicture];
if([self isCreatingLevel])
[actor setCanRotate:YES];
[self addSubview:actor];
[[[self rows] objectAtIndex:(row-1)] replaceObjectAtIndex:(column-1) withObject:actor];
}
}
Edit: Time Profiler Results
By this point, I have roughly 48 drawn views on the screen, (about 70 views total).
I'd suggest WWDC 2012 video iOS App Performance: Responsiveness as a good primer in using Instruments to track down these sorts of issues. Lots of good techniques and tips in that video.
But I agree that this number of views doesn't seem outlandish (though I might be tempted to render this all in CoreGraphics). I'm not using your same model, but here is a pure Core Graphics rendering of that graphic with a single UIView subclass:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
// configure the gridlines
CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
CGContextSetLineWidth(context, 8.0);
CGContextSetLineCap(context, kCGLineCapSquare);
// add the horizontal gridlines
for (NSInteger row = 0; row <= self.rows; row++)
{
CGPoint from = [self coordinateForX:0 Y:row];
CGPoint to = [self coordinateForX:_cols Y:row];
CGContextMoveToPoint(context, from.x, from.y);
CGContextAddLineToPoint(context, to.x, to.y);
}
// add the vertical gridlines
for (NSInteger col = 0; col <= self.cols; col++)
{
CGPoint from = [self coordinateForX:col Y:0 ];
CGPoint to = [self coordinateForX:col Y:_rows];
CGContextMoveToPoint(context, from.x, from.y);
CGContextAddLineToPoint(context, to.x, to.y);
}
// stroke the gridlines
CGContextStrokePath(context);
// now configure the red/blue line segments
CGContextSetLineWidth(context, self.bounds.size.width / _cols / 2.0);
CGContextSetLineCap(context, kCGLineCapRound);
// iterate through our array of points
CGPoint lastPoint = [self.points[0] CGPointValue];
for (NSInteger i = 1; i < [self.points count]; i++)
{
// set the color
if (i % 2)
CGContextSetStrokeColorWithColor(context, [[UIColor redColor] CGColor]);
else
CGContextSetStrokeColorWithColor(context, [[UIColor blueColor] CGColor]);
CGPoint nextPoint = [self.points[i] CGPointValue];
// create path
CGPoint from = [self coordinateForCenterX:lastPoint.x Y:lastPoint.y];
CGPoint to = [self coordinateForCenterX:nextPoint.x Y:nextPoint.y];
CGContextMoveToPoint(context, from.x, from.y);
CGContextAddLineToPoint(context, to.x, to.y);
// stroke it
CGContextStrokePath(context);
// save the last point
lastPoint = nextPoint;
}
}
Now, maybe you're doing something else that requires more sophisticated treatment, in which case that WWDC video (or, perhaps iOS App Performance: Graphics and Animations) should point you in the right direction.

iOS drawRect with custom height

I have placed a view on a viewController and I want to draw a rectangle in this view, but with a custom height. I determine the height based on a parameter in my viewController.
So for example. If my parameter is 50, I want the rectangle to have the height of 50% of the UIView.
2 questions:
how can I pass the height to the custom drawRect?
how do I make the rectangle be placed on the bottom of the UIView?
I have placed the view using Interface Builder and I have implemented the drawRect in a subclass of UIView and used this as Custom Class for my UIView.
So in the custom class I have:
- (void)drawRect:(CGRect)rect
{
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
UIColor * lightGrayColor = [UIColor colorWithRed:230.0/255.0 green:230.0/255.0 blue:230.0/255.0 alpha:1.0];
CGRect paperRect = self.bounds;
drawLinearGradient(context, paperRect, lightGrayColor.CGColor, [UIColor clearColor].CGColor);
}
void drawLinearGradient(CGContextRef context, CGRect rect, CGColorRef startColor, CGColorRef endColor)
{
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat locations[] = { 0.0, 1.0 };
NSArray *colors = #[(__bridge id) startColor, (__bridge id) endColor];
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef) colors, locations);
CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
CGContextSaveGState(context);
CGContextAddRect(context, rect);
CGContextClip(context);
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
CGContextRestoreGState(context);
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
}
This draws a nice gradient rectangle in my view but it fills the complete UIView.
This is because of the self.bounds.
I have added a property *height to my custom class, but I don't know how to fill this from my viewController. So I want it to start at the bottom off the UIView, and make it as high as I have determined (a % of the real height).
Anybody knows how I can achieve this?
have you set your custom view class in identity inspector in interface builder?
you can set the height property from your viewController:
((MyViewClass *) self.myView).height = myCalculatedValue;
then implement drawRect:
- (void)drawRect:(CGRect)rect
{
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
UIColor * lightGrayColor = [UIColor colorWithRed:230.0/255.0 green:230.0/255.0 blue:230.0/255.0 alpha:1.0];
CGRect paperRect = self.bounds;
paperRect.origin.y = paperRect.size.height - self.height;
paperRect.size.height = self.height;
//assuming your height property is of CGFloat type
drawLinearGradient(context, paperRect, lightGrayColor.CGColor, [UIColor clearColor].CGColor);
}
this will draw your gradient from (height) points above the bottom to the bottom
I think you can get calculate 50% of screen height and then substract new view height from the full screen Height
youParameter = 50; // Lets assume this is your parametere
int heightOfView = ([UIScreen mainScreen].bounds.size.height * yourParameter) / 100;
// For placing the view to the bottom;
CGRect newFrame;
frame.origin.y = [UIScreen mainScreen].bounds.size.height - heightOfView;
frame.origin.x = 0;
frame.size.width = [UIScreen mainScreen].bounds.size.width; // assuming it will take full width
frame.size.height = heightOfView;
youViewToChange.frame = newFrame; // normally do this or
To pass the value to drawRect you can do:
[self drawRect:newFrame];

How to draw a triangle over a UIImage in a UIImageView

I'm having trouble drawing atop an image in a UIImageView, I've already looked at "Draw another image on a UIImage" But it was only so much help. Here's my scenario: I have a UIPopoverController with a UITableView in it and I want to display a triangle pointing up when the user is at the bottom of scrollable table view.
My code:
- (UIImage *) drawTriangleInViewForSize:(CGSize)sizeOfView
imageToDrawOn:(UIImage *)underImage
isAtTop:(BOOL)top{
CGPoint firstPoint;
CGPoint secondPoint;
CGPoint thirdPoint;
if(!top){
//I want to draw a equilateral triangle (side length 10) in the center of the image in
//the imageView, these are the coordinates I am using.
firstPoint = CGPointMake(underImage.size.width * 0.5 + 5, underImage.size.height * 0.5 - 5);
secondPoint = CGPointMake((firstPoint.x - 10), firstPoint.y);
thirdPoint = CGPointMake(underImage.size.width * 0.5,
underImage.size.width * 0.5 + 5);
}
*/
**DISREGARD**
else{
firstPoint = CGPointMake(sizeOfView.width * 0.5 + 5,
self.tableViewChoices.rowHeight * 0.5 - 5);
secondPoint = CGPointMake((firstPoint.x - 10), firstPoint.y);
thirdPoint = CGPointMake(sizeOfView.width * 0.5,
self.tableViewChoices.rowHeight * 0.5 + 5);
}*/
//get the size of the image for the drawInRect: method
CGFloat imageViewWidth = sizeOfView.width;
CGFloat imageViewHeight = self.tableViewChoices.rowHeight;
//set the graphics context to be the size of the image
UIGraphicsBeginImageContextWithOptions(underImage.size, YES, 0.0);
[underImage drawInRect:CGRectMake(0.0, 0.0, imageViewWidth, imageViewHeight)];
//set the line attributes
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor);
CGContextSetFillColorWithColor(ctx, [UIColor clearColor].CGColor);
CGContextSetLineWidth(ctx, 0.05);
UIGraphicsPushContext(ctx);
//draw the triangle
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, firstPoint.x, firstPoint.y);
CGContextAddLineToPoint(ctx, secondPoint.x, secondPoint.y);
CGContextAddLineToPoint(ctx, thirdPoint.x, thirdPoint.y);
CGContextAddLineToPoint(ctx, firstPoint.x, firstPoint.y);
CGContextClosePath(ctx);
UIGraphicsPopContext();
//get the image
UIImage *rslt = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return rslt;
}
I can't quite seem to draw to the triangle to the UIImage in the imageView, the end result is either a completely black ImageView (the UIImage is just a white.png), or one line at the top of the imageview (depending on whether I use drawInRect: or drawAtPoint: and what coordinates I use). All I need to do is draw a triangle pointing up, not a hard thing, and it really looks like I'm doing it "by the book."
Here is my completed code to draw a triangle onto a UIImage in a UIImageView.
//draws triangle up or down on a UIImage.
+(UIImage *) drawTriangleInViewForSize:(CGSize)sizeOfView
imageToDrawOn:(UIImage *)underImage
isAtTop:(BOOL)top
{
CGFloat rowHeight = underImage.size.height; //44; //self.tableViewChoices.rowHeight;
CGPoint firstPoint;
CGPoint secondPoint;
CGPoint thirdPoint;
CGFloat imageViewWidth = sizeOfView.width;
CGFloat imageViewHeight = rowHeight;
if(!top){
//draw a upward facing triangle in the center of the view.
firstPoint = CGPointMake(imageViewWidth * 0.5 + 15, imageViewHeight * 0.5 - 5);
secondPoint = CGPointMake((firstPoint.x - 30), firstPoint.y);
thirdPoint = CGPointMake(imageViewWidth * 0.5,
imageViewHeight * 0.5 + 5);
}else{
//disregard this 'else'
firstPoint = CGPointMake(sizeOfView.width * 0.5 + 15,
rowHeight * 0.5 - 5);
secondPoint = CGPointMake((firstPoint.x - 10), firstPoint.y);
thirdPoint = CGPointMake(sizeOfView.width * 0.5,
rowHeight * 0.5 + 5);
}
//get the image context with options(recommended funct to use)
//get the size of the imageView
UIGraphicsBeginImageContextWithOptions(CGSizeMake(imageViewWidth, imageViewHeight), YES, 0.0);
//use the the image that is going to be drawn on as the receiver
[underImage drawInRect:CGRectMake(0.0, 0.0, imageViewWidth, imageViewHeight)];
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(ctx, 0.5);
//UIGraphicsPushContext(ctx);
//uses path ref
CGMutablePathRef path = CGPathCreateMutable();
//draw the triangle
CGPathMoveToPoint(path, NULL, firstPoint.x, firstPoint.y);
CGPathAddLineToPoint(path, NULL, secondPoint.x, secondPoint.y);
CGPathAddLineToPoint(path, NULL, thirdPoint.x, thirdPoint.y);
CGPathAddLineToPoint(path, NULL, firstPoint.x, firstPoint.y);
//close the path
CGPathCloseSubpath(path);
//add the path to the context
CGContextAddPath(ctx, path);
CGContextSetFillColorWithColor(ctx, [UIColor whiteColor].CGColor);
CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor);
CGContextFillPath(ctx);
CGContextAddPath(ctx, path);
CGContextStrokePath(ctx);
CGPathRelease(path);
//UIGraphicsPopContext();
//get the new image with the triangle
UIImage *rslt = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return rslt;
}
Your code looks too complicated for that kind of task. Why don't you use simple UIImageView with any triangle image you ever want? Just show it when the table at the bottom and that's all.
Note, that when you're implementing delegate for UITableView you're also able to catch it's UIScrollView's delegate messages such as scrollView:didEndDecelerating: and so on.
Regarding your code you've missed an actual path draw:
CGContextAddPath(context, trianglePath);
CGContextFillPath(context);
To make it possible you should use path-related functions. Using CGContext-related functions seems to be improper since such approach may remove old path. So the better way is to create your own mutable path in the context, draw your triangle in it and then draw this path into context by functions mentioned above.

Resources