anyone can 'tell me how you draw a horizontal line on the UIView by code or by other methods? I need this line to create a separator
Thank you all
The easiest way to do this is probably use to create a UIView with the height, width, and color you want your separator to have and make it a subview of the view in question. In other words, make a 1 or 2 point tall UIView and use it as your separator. Position it using constraints or manually setting its frame.
Alternatively, add an UILabel and set the text to ---------- or ______ or ======== or whatever looks good. You can adjust the thickness and style by setting the font or the color by setting the text color. Make sure you set the line breaks to "clip" (NSLineBreakByClipping) or you may end up with ellipsis (...) if you enter more characters than the label can show.
You can override the UIView's drawRect: method to draw a 1 pixel stroke at the bottom of the view to act as a separator.
- (void)drawRect:(CGRrect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGPoint startPoint = CGPointMake(rect.origin.x, rect.origin.y + rect.size.height - 1);
CGPoint endPoint = CGPointMake(rect.origin.x + rect.size.width - 1, rect.origin.y + rect.size.height - 1);
CGContextSaveState(context);
CGContextSetLineCap(context, kCGLineCapSquare);
CGContextSetStrokeColorWithColor(context, UIColor.CGColor);
CGContextSetLineWidth(context, 1.0); // Set the line width here
CGContextMoveToPoint(context, startPoint.x + 0.5, startPoint.y + 0.5);
CGContextAddLineToPoint(context, endPoint.x + 0.5, startPoint.y + 0.5);
CGContextStrokePath(context);
CGContextRestoreState(context);
}
Related
I am developing an app in which user can draw lines. The desktop version of the same app is able to draw lines on negative coordinates and I would like to have same capability in iOS app too.
What I've tried so far:-
I have a UIViewController inside which I have overridden - (void)drawRect:(CGRect)rect and drawn the line. Here is the code I am using:-
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, self.bounds);
CGPoint startPoint = CGPointMake(self.startHandle.frame.origin.x + self.startHandle.frame.size.width/2, self.startHandle.frame.origin.y + self.startHandle.frame.size.height/2);
CGPoint endPoint = CGPointMake(self.endHandle.frame.origin.x + self.endHandle.frame.size.width/2, self.endHandle.frame.origin.y + self.endHandle.frame.size.height/2);
CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
CGContextSetLineWidth(context, 2.0f);
CGContextMoveToPoint(context, startPoint.x, startPoint.y ); //start at this point
CGContextAddLineToPoint(context, endPoint.x, endPoint.y); //draw to this point
It works very well until I have startPoint.x as a negative value. With negative value, I don't see the portion of line that is behind 0,0 coordinates. Any help or information is appreciated.
Following on from what #matt said (he has since deleted his answer, shame as it was still useful), you simply want to apply a transform to the context in order to draw in a standard cartesian coordinate system, where (0,0) is the center of the screen.
The context provided to you from within drawRect has its origin at the top left of the screen, with the positive y-direction going down the screen.
So first, we'll want to first flip the y-axis so that the positive y-direction goes up the screen.
We can do this by using CGContextScaleCTM(context, 1, -1). This will reflect the y-axis of the context. Therefore, this reflected context will have a y-axis where the positive direction goes up the screen.
Next, we want to translate the context to the correct position, so that (0,0) corresponds to the center of the screen. We should therefore shift the y-axis down by half of the height (so that 0 on the y-axis is now at the center), and the x-axis to the right by half of the width.
We can do this by using CGContextTranslateCTM(context, width*0.5, -height*0.5), where width and height are the width and height of your view respectively.
Now your context coordinates will look like this:
You could implement this into your drawRect like so:
-(void) drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFloat width = self.bounds.size.width;
CGFloat height = self.bounds.size.height;
// scale and translate to the standard cartesian coordinate system where the (0,0) is the center of the screen.
CGContextScaleCTM(ctx, 1, -1);
CGContextTranslateCTM(ctx, width*0.5, -height*0.5);
CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextSetLineWidth(ctx, 2.0);
// add y-axis
CGContextMoveToPoint(ctx, 0, -height*0.5);
CGContextAddLineToPoint(ctx, 0, height*0.5);
// add x-axis
CGContextMoveToPoint(ctx, -width*0.5, 0);
CGContextAddLineToPoint(ctx, width*0.5, 0);
// stroke axis
CGContextStrokePath(ctx);
// define start and end points
CGPoint startPoint = CGPointMake(-100, 100);
CGPoint endPoint = CGPointMake(100, -200);
CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor);
CGContextSetLineWidth(ctx, 5.0);
CGContextMoveToPoint(ctx, startPoint.x, startPoint.y );
CGContextAddLineToPoint(ctx, endPoint.x, endPoint.y);
CGContextStrokePath(ctx);
}
This will give the following output:
I added the red lines to represent the y and x axis, in order to clarify what we've done here.
CGContextClearRect(context, self.bounds);
You can draw out of this bounds,please make a larger rect.
My question is how to animate the drawing process when drawing with CGContextRef. Is it possible? Assuming it is, how?
I have two code snippets that I would like to animate. First one draws a progress bar and the second one draws a simple line chart. The drawing is done inside a subclass of UIView.
Progress bar is nice and easy. But I want it to sort of draw itself out from the left. I am pretty sure that this will require using something other than UIRectFill but I dont know how to accomplish it.
- (void)drawProgressLine
{
[bgColor set];
UIRectFill(self.bounds);
[graphColor set];
UIRectFill(CGRectMake(0, 0, self.frame.size.width / 100 * [[items objectAtIndex:0] floatValue], self.frame.size.height));
}
The line chart is a bit more complex. I would really really like it to start drawing itself from the left line by line slowly completing itself towards the right but if that is too much how can I just slowly fade it in? The code:
- (void)drawLineChart
{
[bgColor set];
UIRectFill(self.bounds);
[graphColor set];
if (items.count < 2) return;
CGRect bounds = CGRectMake(0, 50, self.bounds.size.width, self.bounds.size.height - 100);
float max = -1;
for (GraphItem *item in items)
if (item.value > max)
max = item.value;
float xStep = (self.frame.size.width) / (items.count - 1);
for (int i = 0; i < items.count; i++)
{
if (i == items.count - 1) break;
float itemHeight = bounds.origin.y + bounds.size.height - ((GraphItem*)[items objectAtIndex:i]).value / max * bounds.size.height;
float nextItemHeight = bounds.origin.y + bounds.size.height - ((GraphItem*)[items objectAtIndex:i + 1]).value / max * bounds.size.height;
CGPoint start = CGPointMake(xStep * i, itemHeight);
CGPoint stop = CGPointMake(xStep * (i + 1), nextItemHeight);
[self drawLineFromPoint:start toPoint:stop lineWidth:1 color:graphColor shadow:YES];
}
}
Pretty simple I guess. If important the drawLineFromPoint..... is implemented like:
- (void)drawLineFromPoint:(CGPoint)startPoint toPoint:(CGPoint)endPoint lineWidth:(CGFloat)width color:(UIColor *)color shadow:(BOOL)shadow
{
if (shadow)
{
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat components[4] = {0.0, 0.0, 0.0, 1.0};
CGColorRef shadowColor = CGColorCreate(colorSpace, components);
CGContextSetShadowWithColor(UIGraphicsGetCurrentContext(), CGSizeMake(1,1), 2.0, shadowColor);
}
CGContextBeginPath(context);
CGContextSetLineWidth(context, width);
CGContextMoveToPoint(context, startPoint.x, startPoint.y);
CGContextAddLineToPoint(context, endPoint.x, endPoint.y);
CGContextClosePath(context);
[color setStroke];
CGContextStrokePath(context);
CGContextSetShadowWithColor(context, CGSizeZero, 0, NULL);
}
I hope I made myself clear cause its 1 am in my country and this post is the last thing that stands between me and my bed. Cheers, Jan.
It sounds like you don't understand the UIKit view drawing cycle. Do you understand that each time you want to change the appearance of your custom-drawn view, you need to send it setNeedsDisplay? And then you need to redraw it entirely in your drawRect: method? Your drawing doesn't appear on screen until drawRect: returns, and after that you cannot draw more in that view until it receives another drawRect: message. If you want the contents of the view to be animated, you will need to send setNeedsDisplay to the view periodically (say, every 1/30th or 1/60th of a second, using either an NSTimer or a CADisplayLink).
It seems like you got the progress bar handled, so here is what I suggest for the graph drawing. Just create and debug your code once to draw the entire graph. Then, use a clip rect that you animate the width of, so that the clip rect starts out skinny and then extends in width until the whole graph becomes visible (from left to right). That will give the user the idea that whatever lines you have are "drawing" from the left to the right, but the actual code is very simple as the animation steps just modify the clip rect to make it wider for each "step". See this question for more info on the CoreGraphics calls: How to set up a clipping rectangle or area
my aim is to drow an empty rounded box, that should appear as a hole, onto my UIView to show what's under.
I made this by overriding drawRect method as shown below.
This gives me the ability to create a rounded rect of the size of my holeRect property (which is a CGRect), drop an inner shadow into it and place it on my view, clearing (using clearColor and CGContextSetBlendMode(context, kCGBlendModeSourceOut) )the area covered by this box and revealing what's behind my view (i took some code from this question and modified it) .
Now my problem is that I have to move and resize this rect with an animation, and I can't find a way to do so, maybe I choose the wrong way to do this but I'm not so expert in drawing so any hint would be very appreciated.
- (void)drawRect:(CGRect)rect
{
//I set the frame of my "holey" rect
CGRect bounds = self.holeRect;
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat radius = 20;
//fill my whole view with gray
CGContextSetFillColorWithColor( context, [UIColor colorWithRed:.5 green:.5 blue:.5 alpha:.8].CGColor );
CGContextFillRect( context, rect );
// Create the "visible" path, which will be the shape that gets the inner shadow
// In this case it's just a rounded rect, but could be as complex as your want
CGMutablePathRef visiblePath = CGPathCreateMutable();
CGRect innerRect = CGRectInset(bounds, radius, radius);
CGPathMoveToPoint(visiblePath, NULL, innerRect.origin.x, bounds.origin.y);
CGPathAddLineToPoint(visiblePath, NULL, innerRect.origin.x + innerRect.size.width, bounds.origin.y);
CGPathAddArcToPoint(visiblePath, NULL, bounds.origin.x + bounds.size.width, bounds.origin.y, bounds.origin.x + bounds.size.width, innerRect.origin.y, radius);
CGPathAddLineToPoint(visiblePath, NULL, bounds.origin.x + bounds.size.width, innerRect.origin.y + innerRect.size.height);
CGPathAddArcToPoint(visiblePath, NULL, bounds.origin.x + bounds.size.width, bounds.origin.y + bounds.size.height, innerRect.origin.x + innerRect.size.width, bounds.origin.y + bounds.size.height, radius);
CGPathAddLineToPoint(visiblePath, NULL, innerRect.origin.x, bounds.origin.y + bounds.size.height);
CGPathAddArcToPoint(visiblePath, NULL, bounds.origin.x, bounds.origin.y + bounds.size.height, bounds.origin.x, innerRect.origin.y + innerRect.size.height, radius);
CGPathAddLineToPoint(visiblePath, NULL, bounds.origin.x, innerRect.origin.y);
CGPathAddArcToPoint(visiblePath, NULL, bounds.origin.x, bounds.origin.y, innerRect.origin.x, bounds.origin.y, radius);
CGPathCloseSubpath(visiblePath);
// Fill this path
UIColor *aColor = [UIColor clearColor];
[aColor setFill];
CGContextAddPath(context, visiblePath);
CGContextSetBlendMode(context, kCGBlendModeSourceOut);
CGContextFillPath(context);
// Now create a larger rectangle, which we're going to subtract the visible path from
// and apply a shadow
CGMutablePathRef path = CGPathCreateMutable();
//(when drawing the shadow for a path whichs bounding box is not known pass "CGPathGetPathBoundingBox(visiblePath)" instead of "bounds" in the following line:)
//-42 cuould just be any offset > 0
CGPathAddRect(path, NULL, CGRectInset(bounds, -42, -42));
// Add the visible path (so that it gets subtracted for the shadow)
CGPathAddPath(path, NULL, visiblePath);
CGPathCloseSubpath(path);
// Add the visible paths as the clipping path to the context
CGContextAddPath(context, visiblePath);
CGContextClip(context);
// Now setup the shadow properties on the context
aColor = [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f];
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, CGSizeMake(0.0f, 1.0f), 5.0f, [aColor CGColor]);
// Now fill the rectangle, so the shadow gets drawn
[aColor setFill];
CGContextSaveGState(context);
CGContextAddPath(context, path);
CGContextEOFillPath(context);
// Release the paths
//CGPathRelease(path);
CGPathRelease(visiblePath);
}
Take a look at this answer I gave to a question about punching a hole in a UIImageView:
https://stackoverflow.com/a/8632731/341994
Notice that the way it works is that UIImageView's superview's layer has a sublayer that does the masking and thus punches the hole. This means that to animate the hole, all you have to do is animate the movement of that sublayer. That's simple with Core Animation, as described in my book:
http://www.apeth.com/iOSBook/ch17.html#_using_a_cabasicanimation
In other words, if you can encapsulate everything you're doing as a layer, then animating it by moving the layer is trivial.
Following the setup you have already, what you want to do is store the position of the cutout in an instance variable of your view class (the class implementing this drawRect method) and use an NSTimer to update the position periodically. For example, in your view's code you could make a scheduled timer like this:
[NSTimer scheduledTimerWithTimeInterval:1.0/30.0 target:self selector:#selector(updateCutoutPosition) userInfo:nil repeats:YES];
and then implement the update method:
- (void)updateCutoutPosition {
// Do whatever you need to do to update the position of the cutout.
[self setNeedsDisplay]; // Will cause drawRect to be called again soon.
}
I want to draw a very thin hairline width of a line in my UIView's drawRect method. The line I see that has a value 0.5 for CGContextSetLineWidth doesn't match the same 1.0 width value that is used to draw a border CALayer.
You can see the difference between the two - the red line (width = 1) is a lot thinner than the purple/blue line (width = 0.5).
Here's how I am drawing my pseudo 1.0 width horizontal line:
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(ctx, [UIColor blueColor].CGColor);
CGContextSetLineWidth(ctx, 0.5); // I expected a very thin line
CGContextMoveToPoint(ctx, 0, y);
CGContextAddLineToPoint(ctx, self.bounds.size.width, y);
CGContextStrokePath(ctx);
Here's a border for the same view, this time using 1.0 border width:
UIView *myView = (UIView *)self;
CALayer *layer = myView.layer;
layer.borderColor = [UIColor redColor].CGColor;
layer.borderWidth = 1.0;
What do I need to do differently to draw my own custom line that's the same width as the CALayer version?
When you stroke a path, the stroke straddles the path. To say it another way, the path lies along the center of the stroke.
If the path runs along the edge between two pixels, then the stroke will (partially) cover the pixels on both sides of that edge. With a line width of 0.5, a horizontal stroke will extend 0.25 points into the pixel above the path, and 0.25 points into the pixel below the path.
You need to move your path so it doesn't run along the edge of the pixels:
CGFloat lineWidth = 0.5f;
CGContextSetLineWidth(ctx, lineWidth);
// Move the path down by half of the line width so it doesn't straddle pixels.
CGContextMoveToPoint(ctx, 0, y + lineWidth * 0.5f);
CGContextAddLineToPoint(ctx, self.bounds.size.width, y + lineWidth * 0.5f);
But since you're just drawing a horizontal line, it's simpler to use CGContextFillRect:
CGContextSetFillColorWithColor(ctx, [UIColor blueColor].CGColor);
CGContextFillRect(ctx, CGRectMake(0, y, self.bounds.size.width, 0.5f));
You need to turn off antialiasing to get a thin line when not drawn on an integral.
CGContextSetShouldAntialias(ctx, NO)
I have a UIView subclass on which the user can add a random CGPath. The CGPath is added by processing UIPanGestures.
I would like to resize the UIView to the minimal rect possible that contains the CGPath. In my UIView subclass, I have overridden sizeThatFits to return the minimal size as such:
- (CGSize) sizeThatFits:(CGSize)size {
CGRect box = CGPathGetBoundingBox(sigPath);
return box.size;
}
This works as expected and the UIView is resized to the value returned, but the CGPath is also "resized" proportionally resulting in a different path that what the user had originally drawn. As an example, this is the view with a path as drawn by the user:
And this is the view with the path after resizing:
How can I resize my UIView and not "resize" the path?
Use the CGPathGetBoundingBox. From Apple documentation:
Returns the bounding box containing all points in a graphics path. The
bounding box is the smallest rectangle completely enclosing all points
in the path, including control points for Bézier and quadratic curves.
Here a small proof-of-concept drawRect methods. Hope it helps you!
- (void)drawRect:(CGRect)rect {
//Get the CGContext from this view
CGContextRef context = UIGraphicsGetCurrentContext();
//Clear context rect
CGContextClearRect(context, rect);
//Set the stroke (pen) color
CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
//Set the width of the pen mark
CGContextSetLineWidth(context, 1.0);
CGPoint startPoint = CGPointMake(50, 50);
CGPoint arrowPoint = CGPointMake(60, 110);
//Start at this point
CGContextMoveToPoint(context, startPoint.x, startPoint.y);
CGContextAddLineToPoint(context, startPoint.x+100, startPoint.y);
CGContextAddLineToPoint(context, startPoint.x+100, startPoint.y+90);
CGContextAddLineToPoint(context, startPoint.x+50, startPoint.y+90);
CGContextAddLineToPoint(context, arrowPoint.x, arrowPoint.y);
CGContextAddLineToPoint(context, startPoint.x+40, startPoint.y+90);
CGContextAddLineToPoint(context, startPoint.x, startPoint.y+90);
CGContextAddLineToPoint(context, startPoint.x, startPoint.y);
//Draw it
//CGContextStrokePath(context);
CGPathRef aPathRef = CGContextCopyPath(context);
// Close the path
CGContextClosePath(context);
CGRect boundingBox = CGPathGetBoundingBox(aPathRef);
NSLog(#"your minimal enclosing rect: %.2f %.2f %.2f %.2f", boundingBox.origin.x, boundingBox.origin.y, boundingBox.size.width, boundingBox.size.height);
}