Draw border and keep above all subviews in view - ios

I've been thinking about this one for a while. Basically, the code below draws a border on a UIView, but, if I add another UIView as a subview to that UIView - it'll appear above the border.
Like this:
How do I (as cleanly as possible), keep the border above all subviews?
Like this:
This is what I have so far. But, like stated above, it doesn't keep the border above all its subviews.
CGPathRef CGPathRect = CGPathCreateWithRect(rect, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGPathRef border = CGPathCreateCopyByStrokingPath(rect.CGPath, NULL, 5.0f, kCGLineCapButt, kCGLineJoinMiter, 0);
CGContextAddPath(context, border);
CGContextSetFillColorWithColor(context, someCGColor);
CGContextDrawPath(context, kCGPathFill);
CGPathRelease(border);
I could create a separate UIView for the border itself, and just insert subviews below that UIView, but that feels rather hackish. If there's a better way - I'd love to hear about it.

I would say use:
view.clipToBounds = YES;
view.layer.borderColor = [UIColor redColor];
view.layer.borderWidth = 2;
view.layer.cornerRadius = 4;
All subviews are clipped and u can keep ur border :).

Related

iOS Draw a line above label

I have a label in my view. Now I want to draw a line above the label, which means you can see the label is under the line.
So far I can draw a line using quartz2D but always under the label. Is there any way to solve my problems?
You can create a CAShapeLayer like this:
CAShapeLayer *lineLayer = [CAShapeLayer layer];
lineLayer.frame = self.label.bounds;
lineLayer.strokeColor = [UIColor redColor].CGColor;
CGRect rect = CGRectMake(0, CGRectGetMidY(lineLayer.bounds), lineLayer.bounds.size.width, 2);
lineLayer.path = [UIBezierPath bezierPathWithRect:rect].CGPath;
And then add it to the UILabel like this:
[self.label.layer addSublayer:lineLayer];
To be honest, the easiest thing to do is to create a 2x2 pixel image called line#2x.png, and have the bottom 2 pixels black, the top 2 transparent, then use it as the background image for an image view. Stretch the image view to whatever width you need by using it as a pattern image. The 1x image should be a 1x1px, all black.
UIView *lineView = [[UIView alloc] initWithFrame:frame]; // Whatever frame the line needs
// Add the line image as a pattern
UIColor *patternColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"line.png"]];
lineView.backgroundColor = patternColor;
[self.view addSubview:lineView];
If this is a label you will be using a lot, you could make a sub-class of UILabel and override the drawRect function.
- (void) drawRect:(CGRect)r
{
[super drawRect:r];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetLineWidth(context, 1.0);
CGContextMoveToPoint(context, 1.0, 1.0);
CGContextAddLineToPoint(context, self.bounds.size.width - 1.0, 1.0);
CGContextStrokePath(context);
CGContextRestoreGState(context);
}
The advantage here is that the line will be "baked" into the view and only be draw once. Other methods such as CAShapeLayer or UIView will be re-rendered every frame.
For bonus points, you can make the color and line width properties :)

Cannot animate CALayer strokeColor

I've been trying to draw a simple rectangle and animate the edge color. I tried using the following link and it still doesn't help. I finally fixed it by changing the layer's border color and animating the same. That worked fine.
Is there a KVO for strokeColor because I'm getting it working for the backgroundColor of layer.
I'm on XCode 4.6 running iOS SDK 6.1
This is what I've been doing:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 2.0);
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CABasicAnimation *strokeAnim = [CABasicAnimation animationWithKeyPath:#"strokeColor"];
strokeAnim.fromValue = (id) [UIColor redColor].CGColor;
strokeAnim.toValue = (id) [UIColor greenColor].CGColor;
strokeAnim.duration = 3.0;
strokeAnim.repeatCount = 0;
strokeAnim.autoreverses = YES;
[self.layer addAnimation:strokeAnim forKey:#"animateStrokeColor"];
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, squareLength, 0);
CGContextAddLineToPoint(context, squareLength, squareLength);
CGContextAddLineToPoint(context, 0, squareLength);
CGContextAddLineToPoint(context, 0, 0);
CGContextStrokePath(context);
tl;dr; You probably meant #"borderColor"
Unless you did override + (Class)layerClass in your view (which I doubt that you did) your view's layer is going to be a CALayer instance which doesn't have a strokeColor property.
You could use a CAShapeLayer but you shouldn't do that just for the stroke since CALayer has borderColor property which is probably what you were looking for.
So my advice is to animate the borderColor instead.
Not really related to your question:
From the code that you've posted it looks like you are adding the animation inside of your views drawRect: implementation. This method should only be used for actual drawing and I strongly suggest that you move the animation code somewhere else. This method will be called every time that your view redraws itself and that is likely too often for adding a new animation.
After researching this same question, I found out that strokeColor is not an animatable property on a CALayer. I think this has something to do with CALayer getting rendered to a cached bitmap.
The solution is to use a CAShapeLayer instead where strokeColor is animatable.

text is disappearing when filling the gradient for uilabel, custom uilabel

My goal is to add gradients for my uilabel by doing the following (CustomLabelBackGround is subclass of UILabel)
#implementation CustomLabelBackGround
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
// Initialization code
}
return self;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorRef whiteColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0].CGColor;
CGColorRef lightGrayColor = [UIColor colorWithRed:230.0/255.0 green:230.0/255.0 blue:230.0/255.0 alpha:1.0].CGColor;
CGColorRef separatorColor = [UIColor colorWithRed:208.0/255.0 green:208.0/255.0 blue:208.0/255.0 alpha:1.0].CGColor;
CGRect paperRect = self.bounds;
// Fill with gradient
drawLinearGradient(context, paperRect, whiteColor, lightGrayColor);
// Add white 1 px stroke
CGRect strokeRect = paperRect;
strokeRect.size.height -= 1;
strokeRect = rectFor1PxStroke(strokeRect);
CGContextSetStrokeColorWithColor(context, whiteColor);
CGContextSetLineWidth(context, 1.0);
CGContextStrokeRect(context, strokeRect);
// Add separator
CGPoint startPoint = CGPointMake(paperRect.origin.x, paperRect.origin.y + paperRect.size.height - 1);
CGPoint endPoint = CGPointMake(paperRect.origin.x + paperRect.size.width - 1, paperRect.origin.y + paperRect.size.height - 1);
draw1PxStroke(context, startPoint, endPoint, separatorColor);
}
However, the text of CustomLabelBackGround is disappearing when I am trying to display onto the screen. please look at the picture below as reference:
What I am missing here. Please help if you have any ideas about this. thanks
You need to call :
[super drawRect:rect];
in order to super draw your text before drawing your anything else. The text with then be drawn normaly
Since you are implementing drawRect for UILabel you are owning that Graphics Context, therefore you are basically "overriding" the text, you can draw that yourself as well, or an easier approach would just be to have a container view with the gradient and a transparent label with the text.
The best approach
Create a custom UIView
make this drawrect implement there
In this UILabel class add that class as background to the label
Otherwise you have to draw the label contents also since you are overriding its drawrect method
Why not just pre-render the gradient in photoshop and then on your label just set:
myLabel.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"myImage"]];
I would:
Create a custom UIView,and use your drawRect code there.
Make sure that the UILabel background is set to [UIColor
clearColor].
Add the Custom UILabel Class as a
subview of the Custom UIView class.
Or just do like Imram said, and create that gradient as an image in Photoshop or some other graphic editor program, and set the label background there. Personally, I think this may be the simplest and most maintainable solution.

How do you darken UIScrollView's overflow?

Suppose you wanna implement the same functionality iOS's camera 'Zoom & Crop' has... in which you can scroll and crop an image.
Any section of the picture that exceeds the size of the crop area gets grayed out.
I'm trying to replicate exactly that. Provided that the flag 'clipToBounds' is set to NO, you can get the whole subview to get displayed.
However, i'm finding it a bit hard to gray out the UIScrollView's subviews overflow.
How would you implement that?.
Thanks in advance!
You can do this by creating a subclass of UIView that is semi-transparent in the overflow region and transparent in the "crop" region and placing it over your UIScrollView and extending it out to cover the overflow.
The main methods you need to implement are initWithFrame:
#define kIDZAlphaOverlayDefaultAlpha 0.75
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
mAlpha = kIDZAlphaOverlayDefaultAlpha;
self.backgroundColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:mAlpha];
self.userInteractionEnabled = NO;
}
return self;
}
Don't miss out the userInteractionEnabled = NO otherwise the scroll view will not sees events.
and drawRect
- (void)drawRect:(CGRect)rect
{
CGRect apertureRect = /* your crop rect */;
CGContextRef context = UIGraphicsGetCurrentContext();
/* draw the transparent rect */
CGContextSetRGBFillColor(context, 0.0, 0.0, 0.0, 0.0);
CGContextSetBlendMode(context, kCGBlendModeCopy);
CGContextFillRect(context, apertureRect);
/* draw a white border */
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
CGContextStrokeRect(context, apertureRect);
}
The important point here is the kCGBlendModeCopy this allows us to draw (or cut) a transparent rectangle in a semi-transparent background.
If you want to can make the transparent rectangle a rounded rectangle, and include a preview of the cropped image and end up with something like the screen below:
Sorry I can't share all the code for the screen shot. It's from a client project :-(
I've solved this issue by...:
Adding a new helper class, 'ApertureOverlay' (subclass of UIView), with the following code:
(void)drawRect:(CGRect)rect
{
if(CGRectEqualToRect(_apertureRect, CGRectZero) == NO)
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetShouldAntialias(context, false);
CGContextSetRGBFillColor(context, 1.0f, 1.0f, 1.0f, 1.0f);
CGContextFillRect(context, _apertureRect);
CGContextSetShouldAntialias(context, true);
}
}
ApertureOverlay has a background with the alpha byte set to 50%.
[self setBackgroundColor:[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.5f]];
So far... al we've got is a view with transparent background, and a white rectangle (drawn with _apertureRect position + size).
After implementing this class, i've set up the 'mask' attribute of the ScrollView, which contains the image inside.
[[self layer] setMask:[_apertureOverlayView layer]];
That's it!. If you update the ApertureOverlay's '_apertureRect' attribute, you'll need to call 'setNeedsDisplay', so it gets redrawn.
One more thing. By setting the antialias to false (ApertureOverlay)... things work pretty smooth.

UILabel : How can I change all the borders except the bottom one?

I know I can change a border's object with
item.layer.cornerRadius = floatValue;
item.layer.borderWidth = intValue;
item.layer.borderColor = colorValue;
But how can I only change top, left and right borders ?
Thank you for your advices.
I don't think you can do that directly.
There are a couple of responses to this question that might help, including one that links to some open source code that solves the problem.
You could use another layer to mask away the corners that you don't want to see. This has the downside that you:
can't have a shadow
can't have another mask (if you don't do them together)
will loose half the border width since the border is stroked on the center of your border
If that is okay with you, here is a sample code that should get you started
CGFloat borderWidth = 4.0;
[[myView layer] setBorderWidth:borderWidth];
CALayer *mask = [CALayer layer];
// The mask needs to be filled to mask
[mask setBackgroundColor:[[UIColor blackColor] CGColor]];
// Make the masks frame smaller in height
CGRect maskFrame = CGRectInset([myView bounds], 0, borderWidth);
// Move the maskFrame to the top
maskFrame.origin.y = 0;
[mask setFrame:maskFrame];
[[myView layer] setMask:mask];

Resources