UIView that is shaped like a UIBezierPath? - ios

Is it possible on iOS to make a regular UIView in some CGRect and add subviews to it and then tell that container UIView something like this:
containerView.layer.path = someClosedUIBezierPath
?
And will then all of the subviews also be curved according to it's parent container view?
I know that every UIView has it's own CALayer and that would be the starting point for me.
I saw examples with animations but I don't see nothing like above (maybe because it isn't there :))

This is a little late, but maybe it will help someone:
You can clip a view to a bezier path by using a CAShapeLayer and the view's layer's mask property:
CAShapeLayer shapeMask = [[CAShapeLayer alloc] initWithFrame:containerView.bounds];
shapeMask.path = someClosedUIBezierPath.CGPath;
containerView.layer.mask = shapeMask;
[shapeMask release];

Related

Draw a plus (+) sign using CALayer

I am not good with CALayer but I need to draw a plus (+) sign and I don't want to use an image as I want to animate the drawing. Any help?
After all the down votes, I was able to do it myself. Here's how for others who might need this
CGFloat height = 2.f;
CGFloat width = 3.f;
CGFloat cornerRadius = 1.f;
CALayer *hLayer = [CALayer layer];//this is the left - right stroke
hLayer.frame = CGRectMake(0, CGRectGetMidY(self.bounds)-(height/2), width, height);
hLayer.cornerRadius = cornerRadius;
CALayer *vLayer = [CALayer layer];// this is the top - bottom stroke
vLayer.frame = CGRectMake(CGRectGetMidX(self.bounds) - (height/2), -3,height, width);
vLayer.cornerRadius = cornerRadius;
[self.layer addSublayer:hLayer];
[self.layer addSublayer:vLayer];
As luk2302 says, use a CAShapeLayer.
You can install a CGPath into a CAShapeLayer. You can get a CGPath from any UIBezierPath. (It has a CGPath property that lets you get to the underlying CGPath object for any UIBezierPath.)
I suggest reading up on UIBezierPath. It has methods moveToPoint and addLineToPoint.
You'd move to the top of your plus, add a line down, then move to the left of your plus and add a line across.
Note that you can also animate images, depending on the type of animation you are after. What kind of animation do you need to do?
Another simple way would be to use two UIView with the same background color and add them as a subview to another UIView. Then you can use the UIView animateWithDuration:... method to do your simple animations.

Creating a view with rounding 2 corners [duplicate]

I did a bit of research, and I see, there is no easy way to
make only top right and top left corners of the UIView round, right?
ps. This code makes all 4 corners round which is smth. I don't want.
#import <QuartzCore/QuartzCore.h>
self.layer.cornerRadius = 5;
self.layer.masksToBounds = YES;
You can do it with this snippet:
// Set top right & left corners
[self setMaskTo:yourView byRoundingCorners:UIRectCornerTopLeft|UIRectCornerTopRight];
EDIT
Sorry, I forgot this
- (void)setMaskTo:(UIView*)view byRoundingCorners:(UIRectCorner)corners
{
UIBezierPath *rounded = [UIBezierPath bezierPathWithRoundedRect:view.bounds
byRoundingCorners:corners
cornerRadii:CGSizeMake(8.0, 8.0)];
CAShapeLayer *shape = [[CAShapeLayer alloc] init];
[shape setPath:rounded.CGPath];
view.layer.mask = shape;
}
PS: as I said in the comment, if you use things like this when displaying UITableViewCell cells, I've found that it's always a better choice to use background images because this kind of drawing stuff always affects performance.
I tried extensions for the UIView once and it did not work for me because it have caused problems when drawing a cell
I got a better and easier solution now in 2 steps that worked perfectly good :)
1) One line of code (Swift 2.0) - it wounds all 4 corners of a view:
YourView.layer.cornerRadius = 10
2) and one addition in storyboard you add 1 view with regular corners where you need no round corners and adjust constraints as you need :
note that small regular corner View should be behind the main view. This is how it looks in storyboard
Here you can see constraints needed for the small view

Rounding all corners with cornerRadius works, but if I just do the bottom two it breaks scrolling on my UITableView

Example project: http://cl.ly/1l1x1A0J3o2X
I have a child view controller that has a UITableView in it, and this view controller sits on top of another view controller. I want to give the bottom of it rounded corners (but only the bottom).
In my UITableView subclass I have this code to round the bottom corners.
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
UIBezierPath *maskPath;
maskPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:(UIRectCornerBottomLeft | UIRectCornerBottomRight) cornerRadii:CGSizeMake(5.0, 5.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
self.layer.mask = maskLayer;
self.backgroundColor = [UIColor colorWithRed:0.169 green:0.169 blue:0.169 alpha:1];
}
return self;
}
Which does indeed round them, but as soon as I scroll the table view it moves it up further and decreases in height (seemingly at least).
However, if I simply use self.layer.cornerRadius = 5.0 it completely operates fine.
Does anyone know why this behaviour might be the case?
UIScrollView, and thus the subclass UITableView, actually renders its content by changing the bounds of what it's rendering. My guess is that adding a mask to the UITableView's layer is actually adding the mask to the content within the view, not the container like you might imagine.
Why 'self.layer.cornerRadius = 5.0' works, and your approach doesn't, I am not sure.
An approach that would work better was already given for a similar request, so I'll direct you over to:
Adding Rounded Corners to only top of UITableView?
Basically, the recommendation was to put the UITableView into a container and add the mask to the container. I believe that would work just fine.
This might not be an exact answer. But I am gonna give you an workaround.
Look at this question and answer.
How to get an UITableView working with CAShapeLayer mask?
https://stackoverflow.com/a/11538923/1083859
This will give you a clue what I tried.
As in that question explained, adding tableView mask will not going to work for you, because it will move with your tableView as you explained.
So I tried adding a another superview for the tableView in your DropDownViewController and add a outlet for it.
Then I changed your code inside the viewDidLoad in DropDownViewController to add the mask for current superview of tableView like below.
UIBezierPath *maskPath;
maskPath = [UIBezierPath bezierPathWithRoundedRect:self.tableSuperView.bounds byRoundingCorners:(UIRectCornerBottomLeft | UIRectCornerBottomRight) cornerRadii:CGSizeMake(5.0, 5.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.view.bounds;
maskLayer.path = maskPath.CGPath;
self.tableSuperView.layer.mask = maskLayer;
and that's it. It works. And I found an unwanted behavior when scroll the tableView to the end. It happens because of tableView bounce animation. If you remove that animation, then you are good to go.
Glad to know your opinion and result.

Use Bezier Path as Clipping Mask

I am wondering if it is possible to clip a view to a Bezier Path. What I mean is that I want to be able to see the view only in the region within the closed Bezier Path. The reason for this is that I have the outline of an irregular shape, and I want to fill in the shape gradually with a solid color from top to bottom. If I could make it so that a certain view is only visible within the path then I could simply create a UIView of the color I want and then change the y coordinate of its frame as I please, effectively filling in the shape. If anyone has any better ideas for how to implement this that would be greatly appreciated. For the record the filling of the shape will match the y value of the users finger, so it can't be a continuous animation. Thanks.
Update (a very long time later):
I tried your answer, Rob, and it works great except for one thing. My intention was to move the view being masked while the mask remains in the same place on screen. This is so that I can give the impression of the mask being "filled up" by the view. The problem is that with the code I have written based on your answer, when I move the view the mask moves with it. I understand that that is to be expected because all I did was add it as the mask of the view so it stands to reason that it will move if the thing it's tied to moves. I tried adding the mask as a sublayer of the superview so that it stays put, but that had very weird results. Here is my code:
self.test = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
self.test.backgroundColor = [UIColor greenColor];
[self.view addSubview:self.test];
UIBezierPath *myClippingPath = [UIBezierPath bezierPath];
[myClippingPath moveToPoint:CGPointMake(100, 100)];
[myClippingPath addCurveToPoint:CGPointMake(200, 200) controlPoint1:CGPointMake(self.screenWidth, 0) controlPoint2:CGPointMake(self.screenWidth, 50)];
[myClippingPath closePath];
CAShapeLayer *mask = [CAShapeLayer layer];
mask.path = myClippingPath.CGPath;
self.test.layer.mask = mask;
CGRect firstFrame = self.test.frame;
firstFrame.origin.x += 100;
[UIView animateWithDuration:3 animations:^{
self.test.frame = firstFrame;
}];
Thanks for the help already.
You can do this easily by setting your view's layer mask to a CAShapeLayer.
UIBezierPath *myClippingPath = ...
CAShapeLayer *mask = [CAShapeLayer layer];
mask.path = myClippingPath.CGPath;
myView.layer.mask = mask;
You will need to add the QuartzCore framework to your target if you haven't already.
In Swift ...
let yourCarefullyDrawnPath = UIBezierPath( .. blah blah
let maskForYourPath = CAShapeLayer()
maskForYourPath.path = carefullyRoundedBox.CGPath
layer.mask = maskForYourPath
Just an example of Rob's solution, there's a UIWebView sitting as a subview of a UIView called smoothView. smoothView uses bezierPathWithRoundedRect to make a rounded gray background (notice on right). That works fine.
But if smoothView has only normal clip-subviews, you get this:
If you do what Rob says, you get the rounded corners in smoothView and all subviews ...
Great stuff.

Drawing inside a subview of a UIView subclass

I have more of a generic question. I have a custom class that subclasses UIView. I override '-drawRect' to draw points in a line which I successfully did. Now I declare another UIView object inside my class that I want to animate. It should be a white circle outline which I'd like to move with an animation and this is why I want it to be of UIView type. But how can I draw inside it? What if I have 3 other similar cases for example? So my point is, I want to keep all of the functionality inside the component's class and have custom drawing for one or more UIView subviews. I think I am missing something on conceptional level.
In ActionScript 3 I would do it so:
var first:MovieClip = new MovieClip();
first.graphics...
......
var nTh:MovieClip = new MovieClip();
nTh.graphics...
This is what I want to achieve (as I said I've already made the drawing of the dots inside -drawRect, now I want to make the bigger circle which has to be animated later):
What I did is creating an UIView instance and setting its bounds and position in -initWithFrame:
//circleFrameSideSize will serve as the diameter of the circle
UIView *circle = [[UIView alloc] initWithFrame:CGRectMake(0, circleYPos, circleFrameSideSize, circleFrameSideSize)];
circle.layer.borderColor = [UIColor whiteColor].CGColor;
circle.layer.borderWidth = 1;
//here we do the trick for making the circle with using layer's corner radius
circle.layer.cornerRadius = circleFrameSideSize/2;
[self addSubview:circle];

Resources