I'm currently trying to create a Grid/Cinematic Overlay with a UIView.
I created a few methods; drawVerticalLine and Horizontal Lines and stuff...
I have a UIViewController that inits the UIGridView. I can put all my methods in the draw rect and draw them all at once.
However, I want to be able to call them individually from the ViewController. When I try to doenter code here that. I get an ": CGContextDrawPath: invalid context 0x0" Code Below.
From my ViewController I want to be able to call "drawGrid :withColor :andLines;" Or something
-
(void)drawRect:(CGRect)rect
{
if (self.verticalLinesON == YES) {
[self drawVerticalLinesForGrid:100 :[UIColor redColor] :[UIColor greenColor]];
}
[self show16NineOverLay:[UIColor orangeColor]];
[self show4ThreeOverLay:[UIColor orangeColor]];
[self drawHorizontalLinesForGrid:100 :[UIColor blueColor] :[UIColor yellowColor]];
}
-(void)drawVerticalLinesForGrid:(float)sectionsVertically :(UIColor *)lineColor1 :(UIColor *)lineColor2
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 2);
int i = 0;
float amountOfSectionsVertically = sectionsVertically;
for (i = 1; i < amountOfSectionsVertically; i++)
{//Horizontal Lines first.
float xCoord = self.frame.size.width * ((i+0.0f)/amountOfSectionsVertically);
CGContextMoveToPoint(context, xCoord, 0);
CGContextAddLineToPoint(context, xCoord, self.frame.size.height);
if (i%2 == 1)
{//if Odd
CGContextSetStrokeColorWithColor(context, lineColor1.CGColor);
}
else if(i%2 == 0)
{//if Even
CGContextSetStrokeColorWithColor(context, lineColor2.CGColor);
}
CGContextStrokePath(context);
}
}
-(void)drawHorizontalLinesForGrid :(float)sectionsHorizontally :(UIColor *)lineColor1 :(UIColor *)lineColor2
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 2);
int i = 0;
float amountOfSectionsHorizontally = sectionsHorizontally;
for (i = 1; i < amountOfSectionsHorizontally; i++)
{//Vertical Lines first.
float yCoord = self.frame.size.height * ((i+0.0f)/amountOfSectionsHorizontally);
CGContextMoveToPoint(context, 0, yCoord);
CGContextAddLineToPoint(context, self.frame.size.width, yCoord);
if (i%2 == 1)
{//if Odd
CGContextSetStrokeColorWithColor(context, lineColor1.CGColor);
}
else if(i%2 == 0)
{//if Even
CGContextSetStrokeColorWithColor(context, lineColor2.CGColor);
}
CGContextStrokePath(context);
}
}
-(void)show16NineOverLay:(UIColor *)lineColor
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 10);
//x/y
float yCoord = (0.5) * (self.frame.size.height * (1.778)
What you should do is set some state on your grid view class that specifies what should be drawn (just vertical, just horizontal, both, etc) and then call setNeedsDisplay on the view.
This will trigger a call to drawRect:. Then your drawRect: method should look at its current state and call just the appropriate methods to draw the desired parts.
You must never directly call drawRect: on a view.
Related
I have a UISlider that I use to play a video, and I would like to put red "break indication" lines in my UISlider to visually show the user when the break is coming up, as visualized below. My slider has a set .duration property, and I have an array full of timestamps that contain the times that the video will need to pause for a break. I'm still getting the hang of iOS, so I don't know how to go about drawing the lines over UISlider's. I would like it to appear similar to this:
In my research I've read in the Apple Docs that UISlider luckily provides a method to the sliders coordinates based upon a float value. This is perfect because now I can determine where in the slider I can draw the yellow line based upon the timestamps in the array, right?
So, I created a for loop to call the method and am (attempting to) draw a line.
The drawing the line part is what I'm having issues with. I've been reading through Apple's docs and other questions on this site, but I cannot seem to figure out the drawing logic. It draws just fine, the issue is that it's coordinates are all wrong. It draws and overlaps the majority of its lines in one specific location, the top left of my view. This is what I'm trying to do:
Updated Code In correlation with #Bannings answer (Many thanks to you).
- (void)breakStarted:(NSNotification *)notification {
self.lines = [NSMutableArray new];
for (int i = 0; i < myArray.count; i++) {
long long nanoSeconds = [[myArray.adMarkerTimes objectAtIndex:i] floatValue];
float minutes = nanoSeconds / 60000000000;
[self sliderThumbCenter:self.scrubberSliderView forValue:minutes];
NSLog(#"Minute Readings: %f", minutes);
}
}
- (float)sliderThumbCenter:(UISlider *)slider forValue:(float)value {
CGRect trackRect = [slider trackRectForBounds:slider.bounds];
CGRect thumbRect = [slider thumbRectForBounds:slider.bounds trackRect:trackRect value:value];
CGFloat centerThumb = CGRectGetMidX(thumbRect);
NSLog(#"Center Thumb / Line Placements on slider are: %f", centerThumb);
[self.lines addObject:#(centerThumb)]; // Added the rect values to an array which we will loop through to draw the lines over the slider
[self setNeedsDisplay];
return centerThumb;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetLineWidth(context, 2);
for (NSNumber *x in self.lines) {
CGContextMoveToPoint(context, x.floatValue, 0);
CGContextAddLineToPoint(context, x.floatValue, rect.size.height);
CGContextStrokePath(context);
}
}
I forgot to add: I'm manually converting CMTimeValue to seconds in the loop. That's what myArray.adMarkerTimes. Maybe I did that wrong...
You can override the drawRect of UISlider.
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetLineWidth(context, 2);
CGContextMoveToPoint(context, 20, 0);
CGContextAddLineToPoint(context, 20, rect.size.height);
CGContextStrokePath(context);
}
or insert UIView to UISlider.
- (UIView *)lineViewForRect:(CGRect)rect {
UIView *lineView = [[UIView alloc] initWithFrame:rect];
lineView.backgroundColor = [UIColor redColor];
return lineView;
}
- (void)viewDidLoad {
[super viewDidLoad];
[slider addSubview:[self lineViewForRect:CGRectMake(20, 0, 2, slider.bounds.size.height)]];
}
You also can set lineView.layer.zPosition = 1:
before:
after:
EDIT:
You can store lines in an array, and draw each in context.
It seems like this:
// YourSlider.m
- (void)addLineToX:(CGFloat)x {
[self.lines addObject:#(x)];
// This will cause drawRect to be called
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetLineWidth(context, 2);
for (NSNumber *x in self.lines) {
CGContextMoveToPoint(context, x.floatValue, 0);
CGContextAddLineToPoint(context, x.floatValue, rect.size.height);
}
CGContextStrokePath(context);
}
I have drawn text labels in front of a circular arc of dots using iOS Core Graphics. I am trying to understand why CGContextSetStrokeColorWithColor does not set text colour to black in the two examples below.
Text labels are black if the dots are outlined. But when images are filled the text changes to the dot colour.
EDIT: *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The suggestion of Ben Zotto helped to resolved this issue. In the original code below the solution was to replace
CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
with
CGContextSetFillColorWithColor(context, [[UIColor blackColor] CGColor]);
I also removed
CGContextSetShadowWithColor(context, CGSizeMake(-3 , 2), 4.0, [UIColor clearColor].CGColor);
resulting in a much cleaner label.
Thanks Ben.
*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here is the original code
- (void)rosette {
context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 0.5);
uberX = 160;
uberY = 240;
uberRadius = 52;
sectors = 16;
uberAngle = (2.0 * PI) / sectors;
dotAngle = PI * -0.5; // start drawing 0.5 PI radians before 3 o'clock
endAngle = PI * 1.5; // stop drawing 1.5 PI radians after 3 o'clock
NSLog(#"%f %f %f %f %f", uberX, uberY, uberRadius, uberAngle, dotAngle);
dotRadius = 20;
dotsFilled = FALSE;
alternateDots = TRUE; // alternately filled and outlined
textOffset = 4; // offset added to centre text
for (dotCount = 1; dotCount <= sectors; dotCount++)
{
// Create a new iOSCircle Object
iOSCircle *newCircle = [[iOSCircle alloc] init];
newCircle.circleRadius = dotRadius;
[self newPoint]; // find point coordinates for next dot
dotPosition = CGPointMake(x,y); // create dot centre
NSLog(#"Circle%i: %#", dotCount, NSStringFromCGPoint(dotPosition));
newCircle.circleCentre = dotPosition; // place each dot on the frame
[totalCircles addObject:newCircle];
[self setNeedsDisplay];
}
CGContextSetShadowWithColor(context, CGSizeMake(-3 , 2), 4.0, [UIColor clearColor].CGColor);
dotCount = 1;
for (iOSCircle *circle in totalCircles) {
CGContextEOFillPath (context);
CGContextAddArc(context, circle.circleCentre.x, circle.circleCentre.y, circle.circleRadius, 0.0, M_PI * 2.0, YES);
// draw the circles
int paintThisDot = dotsFilled * alternateDots * !(dotCount % 2); // paint if dotCount is even
NSLog(#"Dot %i Filled %i ", dotCount, dotsFilled);
switch (paintThisDot) {
case 1:
CGContextSetFillColorWithColor(context, [[UIColor cyanColor] CGColor]);
CGContextDrawPath(context, kCGPathFillStroke);
break;
default: // draw dot outline
CGContextStrokePath(context);
break;
}
CGContextClosePath(context);
[self newPoint]; // find point coordinates for next dot
dotCount++;
}
CGContextSetShadowWithColor(context, CGSizeMake(-3, 2), 4.0, [UIColor grayColor].CGColor);
CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
CGContextBeginPath(context);
// draw labels
for (dotCount = 1; dotCount <= sectors; dotCount++)
{
// Create a new iOSCircle Object
iOSCircle *newCircle = [[iOSCircle alloc] init];
newCircle.circleRadius = dotRadius;
[self newPoint]; // find point coordinates for next dot
dotPosition = CGPointMake(x,y); // use point coordinates for label
[self autoLabel]; // prints labels
}
}
And here is the method for newPoint
- (void)newPoint {
dotAngle = dotAngle + uberAngle;
x = uberX + (uberRadius * 2 * cos(dotAngle));
y = uberY + (uberRadius * 2 * sin(dotAngle));
NSLog(#"%i %f %f %f", dotCount, dotAngle, endAngle, uberAngle);
}
And the method for autoLabel
- (void)autoLabel {
boxBoundary = CGRectMake(x-dotRadius, y-dotRadius+textOffset, dotRadius*2, dotRadius*2); // set box boundaries relative to dot centre
[[NSString stringWithFormat:#"%i",dotCount] drawInRect:boxBoundary withFont:[UIFont systemFontOfSize:24] lineBreakMode:NSLineBreakByCharWrapping alignment:NSTextAlignmentCenter];
}
Use CGContextSetFillColorWithColor (Fill) instead of CGContextSetStrokeColorWithColor (Stroke) before you draw the text.
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);
Pardon me, but my knowledge of CGContext is fairly limited.
I am using the code from the accepted answer HERE to draw stars in a UIView. What I want to achieve is show the stars in 2 different colors (like a rating view). The problem is, I cannot seem to use 2 different colors for CGContextSetFillColorWithColor().
Relevant code:
if (i < 3) {
NSLog(#"__BLACK__");
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
} else {
NSLog(#"__RED__");
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
}
Full code:
- (void) drawRect:(CGRect)rect {
int aSize = 20;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, aSize);
CGFloat xCenter = 15.0;
CGFloat yCenter = 12.5;
float w = 25.0;
double r = w / 2.0;
float flip = -1.0;
for (NSUInteger i = 0; i < 5; i++) {
if (i < 3) {
NSLog(#"__BLACK__");
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
} else {
NSLog(#"__RED__");
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
}
double theta = 2.0 * M_PI * (2.0 / 5.0); // 144 degrees
CGContextMoveToPoint(context, xCenter, r * flip + yCenter);
for (NSUInteger k = 1; k < 5; k++) {
float x = r * sin(k * theta);
float y = r * cos(k * theta);
CGContextAddLineToPoint(context, x + xCenter, y * flip + yCenter);
}
xCenter += 37.5;
}
CGContextClosePath(context);
CGContextFillPath(context);
}
On compilation and execution i get the log :
__BLACK__
__BLACK__
__BLACK__
__RED__
__RED__
However, the fill colors don't seem to take effect, and the result is:
I don't understand what I'm doing wrong here.
When you set the fill color for the context, it's used on every object in that context. In your case, all 5 stars are drawn inside the same context, so only the last color setting (red) persists.
You can probably achieve what you want to achieve with 5 distinct CGPaths drawn in one context.
I'm trying to draw a bar that has 4 color segments (not a gradient - 4 distinct colors). Those 4 UIColors are stored in an array (I have debugged and checked that those colors are properly set). For some reason, This loop only draws the first color (at the correct width). This class inherits from UIView and is being invoked via
TopBar* bar = [[TopBar alloc]initWithFrame:CGRectMake(0, 200, self.view.frame.size.width, 20)];
[self.view addSubview:bar];
The drawRect is as follows:
-(void)drawRect:(CGRect)rect{
NSLog(#"draw rect");
[super drawRect:rect];
CGContextRef ctx = UIGraphicsGetCurrentContext();
NSInteger fullBarWidth = self.frame.size.width;
NSInteger individualBarWidth = fullBarWidth/self.barColors.count;
NSInteger currentColorIndex = 0;
CGContextSetLineWidth(ctx, self.frame.size.height);
CGContextSetLineCap(ctx, kCGLineCapButt);
for (UIColor *color in self.barColors)
{
NSInteger currentDrawLoc = currentColorIndex * individualBarWidth;
currentColorIndex++;
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, currentDrawLoc, 0);
CGContextAddLineToPoint(ctx, individualBarWidth, 0);
CGContextSetStrokeColorWithColor(ctx,color.CGColor);
CGContextStrokePath(ctx);
}
}
Any help would be appreciated
In this line:
CGContextAddLineToPoint(ctx, individualBarWidth, 0);
You meant to say:
CGContextAddLineToPoint(ctx, currentDrawLoc + individualBarWidth, 0);