I'm trying to build a category method on UIView to allow to stroke the UIView, similarly to the Photoshop function: Stroke-Outside. While CALayer properties borderWidth and borderColor work flawlessly to stroke on the inside, I cannot find a good way to stroke the view on the outside.
This is the solution I came up at first:
Using sublayers I add another CALayer with frame larger than the superlayer's bounds. The layer is then inserted in the UIView's sublayers.
- (void)strokeViewWithColor:(UIColor *)color borderWidth:(CGFloat)borderWidth
{
CALayer *borderLayer = [CALayer layer];
CGRect borderFrame = CGRectMake(-borderWidth, -borderWidth, (self.frame.size.width) + (borderWidth * 2), (self.frame.size.height) + (borderWidth * 2));
[borderLayer setBackgroundColor:[[UIColor clearColor] CGColor]];
[borderLayer setFrame:borderFrame];
//
// Copy current border layer radius
//
[borderLayer setCornerRadius:self.layer.cornerRadius];
[borderLayer setBorderWidth:borderWidth];
[borderLayer setBorderColor:[color CGColor]];
[self.layer addSublayer:borderLayer];
}
This works for all the UIView's that have layer's property masksToBounds or view's property clipsToBounds set to NO. But this disables the functionality to use cornerRadius property, which I want to have.
So my second attempt was to use UIView's, since obviously I must go out of the view I want to stroke (the same logic could be applied to layer and superlayer relationship I assume).
I came up with this solution:
- (void)strokeViewWithColor:(UIColor *)color borderWidth:(CGFloat)borderWidth
{
UIView* superview = self.superview;
//
// Fix frame
//
CGRect frame = self.frame;
frame.origin.x -= borderWidth;
frame.origin.y -= borderWidth;
frame.size.width += (2 * borderWidth);
frame.size.height += (2 * borderWidth);
UIView* strokeView = [[UIView alloc] initWithFrame:frame];
strokeView.layer.borderWidth = borderWidth;
strokeView.layer.cornerRadius = self.layer.cornerRadius + borderWidth;
strokeView.layer.borderColor = [color CGColor];
[superview insertSubview:strokeView aboveSubview:self];
}
This works, but the problem would be with auto layout. Because the strokeView has no auto layout constraints, it probably would not work correctly when constraints are added.
That is why I am looking for any other solutions.
What would be the best way to solve this problem? Should I write some code to handle the auto layout issue on second part of the code? Or should I approach the problem from different perspective? If so, how?
I'm mostly looking for suggestions on how to approach the problem.
Thanks for help!
Related
I have setup a UIView using constraints in a storyboard, and I want to add a circle progress to this view using CAShapeLayer with a UIBezierPath.
I have added this correctly, but the circle isn't expanded to all the view. i call this in my viewDidLoad function. My view has constraints to asjust height top-x view and width.
- (void)viewDidLoad {
[super viewDidLoad];
CAShapeLayer *borderLayer = [CAShapeLayer layer];
borderLayer.fillColor = [UIColor whiteColor].CGColor;
CGFloat lineWidth = 4;
CGRect rect = CGRectMake(lineWidth+lineWidth/2, lineWidth+lineWidth/2, self.myView.bounds.size.width/2, self.myView.bounds.size.height/2);
borderLayer.path = [UIBezierPath bezierPathWithOvalInRect:rect].CGPath;
borderLayer.strokeColor = [[UIColor redColor] CGColor];
borderLayer.lineWidth = lineWidth;
[borderLayer setFillColor:[UIColor clearColor].CGColor];
[self.myView.layer addSublayer:borderLayer];
}
The circle progress isn't resized to the UIView, and it is shown like this:
How can I draw this occupying the whole UIView like this:
1 - Your view's frame will not have been set yet in viewDidLoad. You need to move this code into viewDidLayoutSubviews or later in the view controller's lifecycle.
Related Question:
Why am I having to manually set my view's frame in viewDidLoad?
Apple Docs on view controller lifecycle:
https://developer.apple.com/library/content/referencelibrary/GettingStarted/DevelopiOSAppsSwift/WorkWithViewControllers.html#//apple_ref/doc/uid/TP40015214-CH6-SW1
2 - Change this:
CGRect rect = CGRectMake(lineWidth+lineWidth/2, lineWidth+lineWidth/2, self.myView.bounds.size.width/2, self.myView.bounds.size.height/2);
to this:
CGRect rect = CGRectMake(lineWidth/2, lineWidth/2, self.myView.bounds.size.width - lineWidth, self.myView.bounds.size.height - lineWidth);
Result (I used slightly different lineWidth and view size for my testing):
I need to set the length size of a UIButton border. I tried using this
[[jb layer] setBorderLength:2.2f];
but got a error saying "setBorderLength is not a method".
Here's my code for my UIButton border:
[[jb layer] setBorderWidth:2.2f];
[[jb layer] setBorderColor:[UIColor blackColor].CGColor];
setting a layer border parameters :
jb.layer.borderColor = [UIColor blackColor].CGColor;
jb.layer.borderWidth = 1;
jb.layer.cornerRadius = jb.bounds.size.width * 0.1;
There is no border length. The border width determines how thick your border is.
The border is around your frame. If you mean your border to be appear wider around your button consider changing the frame.
You would have to create layers and add it to the button to handle this scenario
CALayer * layer = [CALayer layer];
layer.bounds = CGRectMake(0, 0, buttonWidth, 1.0);
layer.backgroundColor = [UIColor blackColor].CGColor;
layer.opacity = 0.1f;
[self.button.layer addSublayer:layer];
This will add border on only the top border of button.
Change the frame and add 3 more layers to cover left right and bottom
I use this code in my customized UIView initialization:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
...
UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRect:CGRectMake(frame.origin.x, frame.size.height, frame.size.width, 15)];
self.layer.masksToBounds = NO;
self.layer.shadowColor = [UIColor blackColor].CGColor;
self.layer.shadowOffset = CGSizeMake(0.0f, 4.0f);
self.layer.shadowOpacity = 0.5f;
self.layer.shadowPath = shadowPath.CGPath;
}
return self;
}
trying to make a drop shadow like this:
1)
But I got this effect:
2)
You can see this is an upside down version of what I want to achieve. How to make the shadow effect of the first image?
The issue is just with your shadowPath.
Using CGRectMake(frame.origin.x, frame.size.height, frame.size.width, 15) to create your UIBezierPath will set an incorrect origin.
First, origin.x should be 0.0f or the shadow will shift far away if your UIView's origin.x != 0.0f. Second, you need to line up the bottom of the shadowPath with the bottom of your UIView.
This is a screenshot of UIViews using your shadow code illustrating these issues. (In the lower UIView you cannot see the shadow because it is far off the right of the screen).
You will see what you intended if you change the rect to:
const CGFloat shadowRectHeight = 15.0f;
CGRect shadowRect = CGRectMake(0.0f, self.bounds.size.height - shadowRectHeight, self.bounds.size.width, shadowRectHeight)];
Do like this and set the UIView alpha
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = rect;
gradient.colors = [NSArray arrayWithObjects:(id)[UIColor blackColor].CGColor,
(id)[UIColor grayColor].CGColor,
(id)[UIColor blackColor].CGColor,nil];
[self.layer insertSublayer:gradient atIndex:0];
you can add or remove UIColor you want to display.
I have a UIViewController with a UITableView. The sits inside the view controller's view with some padding on all sides. I am trying to draw a drop shadow from the table view as well as rounded corners, but I am unable to achieve both at the same time. I have tried with the following code and turned masksToBounds on and off, but setting it to NO create a really weird effect with the shadow scrolling and the calls are not clipping inside the table.
[self.tableView.layer setShadowColor:[UIColor blackColor].CGColor];
[self.tableView.layer setShadowOffset:CGSizeMake(0, 3)];
[self.tableView.layer setShadowOpacity:0.4];
[self.tableView.layer setShadowRadius:3.0f];
[self.tableView.layer setCornerRadius:5.0f];
[self.tableView.layer setShadowPath:[[UIBezierPath bezierPathWithRoundedRect:self.tableView.bounds cornerRadius:5.0f] CGPath]];
I am just drawing a UITableView with plain style too. The effect I am trying to achieve can be seen in the free app Transit App and here's a movie where you can see the shadow stays and the table even has a mask that scrolls up and down.
I have searched and searched and haven't been able to put together a solution based on other SO answers or online articles. Help appreciated.
Try the below links, some of them are old but you can get some pointer.
Custom Table Effects
Dropshadow 2
Cool Table Views
DropShadow
Okay!! I attempted once again and got an effect, it is again a work around only (instead of using images), the below is what i tried
Create a CALayer, with the same frame as your tableview, give the same curve and all, add the table view on top of this layer, thats it.
CALayer *sublayer = [CALayer layer];
sublayer.backgroundColor = [UIColor blueColor].CGColor; // If you dont give this, shadow will not come, dont know why
sublayer.shadowOffset = CGSizeMake(0, 3);
sublayer.shadowRadius = 5.0;
sublayer.shadowColor = [UIColor blackColor].CGColor;
sublayer.shadowOpacity = 1.0;
sublayer.cornerRadius = 5.0;
sublayer.frame = CGRectMake(myTable.frame.origin.x, myTable.frame.origin.y, myTable.frame.size.width, myTable.frame.size.height);
[self.view.layer addSublayer:sublayer];
[self.view.layer addSublayer:myTable.layer];
Ah!! due to some reason I am unable to upload the screenshot from my machine, firewall ;-). I will try from my home.
-anoop
It is recommended to use images for drop shadows with large tableviews, all that drawing has the ability to slow down performance, which can result in bad user experience, just a word of advice
I know this is a rather old question, but I recently had the same problem and the solutions here were good examples, but not full enough. So here it is the method I'm using to create shadow of a table view. It can be used also for any other view.
-(void)makeShadowToView:(UIView*) view withHeight:(CGFloat)shadowHeight
{
CGRect bounds = view.frame;
bounds.size.height = shadowHeight;
view.clipsToBounds = NO; // Without this row it doesn't display any shadow, at least for a table view
view.layer.shadowColor = [UIColor blackColor].CGColor;
view.layer.shadowOpacity = 0.9f;
view.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);
view.layer.shadowRadius = 5.0f;
view.layer.shadowPath = [UIBezierPath bezierPathWithRect:bounds].CGPath;
}
Example usage: [self makeShadowToView:self.view withHeight:height];
where height is added in case you want to recalculate the shadow's height according to the table content's height.
(void)shadowForView{
CALayer *layer = self.viewActionMenu.layer;
layer.shadowOffset = CGSizeMake(-2, 0);
layer.shadowColor = [[UIColor blackColor] CGColor];
layer.shadowRadius = 2.0f;
layer.shadowOpacity = 0.80f;
// [self.view bringSubviewToFront:self.actionView];
}
And Call:
[self shadowForView];
Try this
UITableView * objTableview=[[UITableView alloc]initWithFrame:CGRectMake(x, y, w, h)];
UIView * objViewShadow=[[UIView alloc]initWithFrame:objTableview.bounds];
objTableview.layer.cornerRadius=5;
// add shadow
objViewShadow.layer.shadowOffset = CGSizeMake(0, 3);
objViewShadow.layer.shadowRadius = 5.0;
objViewShadow.layer.shadowColor = [UIColor blackColor].CGColor;
objViewShadow.layer.shadowOpacity = 0.8;
[objViewShadow addSubview:objTableview];
[self.view addSubview:objViewShadow];
You need to set this two properties
self.tableView.clipsToBounds = NO;
self.tableView.layer.masksToBounds = NO;
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];