How to round one corner of a resizable UIView in iOS?

I use this code to round one corner of my UIView :

 UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect: self.view.bounds byRoundingCorners:(UIRectCornerTopLeft) cornerRadii: CGSizeMake(10.0, 10.0)]; CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init]; maskLayer.frame = self.view.bounds; maskLayer.path = maskPath.CGPath; self.view.layer.mask = maskLayer; self.view.layer.masksToBounds = NO; 

This code works if I never resize the view. If I make the view larger, a new area will not appear because it extends beyond the mask layer (this mask layer does not automatically resize with the view). I could just make the mask as big as it will ever be, but it can be full-screen on the iPad, so I'm worried about performance with a big mask (I will have more than one of them in my UI). In addition, the ultra-small mask will not work for a situation where I need the upper right corner (one) to be rounded.

Is there a simpler and easier way to achieve this?

Update : this is what I'm trying to achieve: http://i.imgur.com/W2AfRBd.png (the rounded corner I want is surrounded by green).

I got a working version of this using a subclass of UINavigationController and overriding viewDidLayoutSubviews as follows:

 - (void)viewDidLayoutSubviews { CGRect rect = self.view.bounds; UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerTopLeft cornerRadii:CGSizeMake(8.0, 8.0)]; self.maskLayer = [CAShapeLayer layer]; self.maskLayer.frame = rect; self.maskLayer.path = maskPath.CGPath; self.view.layer.mask = self.maskLayer; } 

Then I create an instance of the UINavigationController subclass with my view controller, and then I shift the view frame of the controller controller by 20px (y) to open the status panel and leave the panel with a high resolution of 44 pixels, as shown in the image.

The code works, except that it does not handle rotation at all. When the application rotates, viewDidLayoutSubviews is called before the rotation, and my code creates a mask that matches the view after the rotation; this creates an unwanted rotation lock when the bit to be hidden is displayed during rotation. In addition, while the rotation of the application is completely smooth without this mask, when you create the mask, the rotation becomes noticeably jerky and slow.

The Evomail iPad app also has rounded corners like this, and their app suffers from the same issue.

+6
source share
9 answers

I know this is a pretty hacky way to do this, but could you just add png over the corner?

I know terribly, but this will not affect performance, the rotation will be great if it is previewed and users do not notice it.

+2
source

The problem is that CoreAnimation properties are not expected in UIKit animation blocks. You need to create a separate animation that will have the same curve and duration as the UIKit animation.

I created a mask layer in viewDidLoad . When the view will schedule, I only change the path property of the mask layer.

You don’t know the duration of the rotation inside the layout callback methods, but you know this right before the rotation (and before the layout is launched) so that you can save it there.

The following code works well.

 - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { //Keep duration for next layout. _duration = duration; } -(void)viewWillLayoutSubviews { [super viewWillLayoutSubviews]; UIBezierPath* maskPath = [UIBezierPath bezierPathWithRoundedRect:self.view.bounds byRoundingCorners:UIRectCornerTopLeft cornerRadii:CGSizeMake(10, 10)]; CABasicAnimation* animation; if(_duration > 0) { animation = [CABasicAnimation animationWithKeyPath:@"path"]; animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; [animation setDuration:_duration]; //Set old value [animation setFromValue:(id)((CAShapeLayer*)self.view.layer.mask).path]; //Set new value [animation setToValue:(id)maskPath.CGPath]; } ((CAShapeLayer*)self.view.layer.mask).path = maskPath.CGPath; if(_duration > 0) { [self.view.layer.mask addAnimation:animation forKey:@"path"]; } //Zero duration for next layout. _duration = 0; } 
+5
source

Two ideas:

  • Resize the mask when resizing the view. You do not get auto-resizing of sublayers the same way you automatically resize subzones, but you still get an event, so you can manually resize sublayers.

  • Or ... If this is the view whose drawing and display you are responding to, make the rounding of the corner part of how you draw the view in the first place (by clipping). In fact, this is the most effective approach.

+1
source

You can subclass the view you are using and override the layoutSubviews method. This call is called every time you resize your view.

Even if "self.view" (the link in your code) is your viewcontroller view, you can still customize this view to a custom class in your storyboard. Here's the modified code for the subclass:

 - (void)layoutSubviews { [super layoutSubviews]; UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect: self.bounds byRoundingCorners:(UIRectCornerTopLeft) cornerRadii: CGSizeMake(10.0, 10.0)]; CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init]; maskLayer.frame = self.bounds; maskLayer.path = maskPath.CGPath; self.layer.mask = maskLayer; self.layer.masksToBounds = NO; } 
+1
source

I think you need to create a custom view that updates itself anytime it is needed, which means that setNeedsDisplay is called at any time.

I suggest creating a custom subclass of UIView that will be implemented as follows:

 // OneRoundedCornerUIView.h #import <UIKit/UIKit.h> @interface OneRoundedCornerUIView : UIView //Subclass of UIView @end // OneRoundedCornerUIView.m #import "OneRoundedCornerUIView.h" @implementation OneRoundedCornerUIView - (void) setFrame:(CGRect)frame { [super setFrame:frame]; [self setNeedsDisplay]; } // Override drawRect as follows. - (void)drawRect:(CGRect)rect { UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect: self.bounds byRoundingCorners:(UIRectCornerTopLeft) cornerRadii: CGSizeMake(10.0, 10.0)]; CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init]; maskLayer.frame = self.bounds; maskLayer.path = maskPath.CGPath; self.layer.mask = maskLayer; self.layer.masksToBounds = NO; } @end 

Once you have done this, you just need to make your instance an instance of OneRoundedCornerUIView instead of UIView, and your view will smoothly update each time you resize or change its frame. I just did some testing and it seems to work great.

This solution can also be easily configured in order to have an idea for which you can easily set which angles should be included and which angles should not be in your view controller. Performance:

 // OneRoundedCornerUIView.h #import <UIKit/UIKit.h> @interface OneRoundedCornerUIView : UIView //Subclass of UIView // This properties are declared in the public API so that you can setup from your ViewController (it also works if you decide to add/remove corners at any time as the setter of each of these properties will call setNeedsDisplay - as shown in the implementation file) @property (nonatomic, getter = isTopLeftCornerOn) BOOL topLeftCornerOn; @property (nonatomic, getter = isTopRightCornerOn) BOOL topRightCornerOn; @property (nonatomic, getter = isBottomLeftCornerOn) BOOL bottomLeftCornerOn; @property (nonatomic, getter = isBottomRightCornerOn) BOOL bottomRightCornerOn; @end // OneRoundedCornerUIView.m #import "OneRoundedCornerUIView.h" @implementation OneRoundedCornerUIView - (void) setFrame:(CGRect)frame { [super setFrame:frame]; [self setNeedsDisplay]; } - (void) setTopLeftCornerOn:(BOOL)topLeftCornerOn { _topLeftCornerOn = topLeftCornerOn; [self setNeedsDisplay]; } - (void) setTopRightCornerOn:(BOOL)topRightCornerOn { _topRightCornerOn = topRightCornerOn; [self setNeedsDisplay]; } -(void) setBottomLeftCornerOn:(BOOL)bottomLeftCornerOn { _bottomLeftCornerOn = bottomLeftCornerOn; [self setNeedsDisplay]; } -(void) setBottomRightCornerOn:(BOOL)bottomRightCornerOn { _bottomRightCornerOn = bottomRightCornerOn; [self setNeedsDisplay]; } // Override drawRect as follows. - (void)drawRect:(CGRect)rect { UIRectCorner topLeftCorner = 0; UIRectCorner topRightCorner = 0; UIRectCorner bottomLeftCorner = 0; UIRectCorner bottomRightCorner = 0; if (self.isTopLeftCornerOn) topLeftCorner = UIRectCornerTopLeft; if (self.isTopRightCornerOn) topRightCorner = UIRectCornerTopRight; if (self.isBottomLeftCornerOn) bottomLeftCorner = UIRectCornerBottomLeft; if (self.isBottomRightCornerOn) bottomRightCorner = UIRectCornerBottomRight; UIRectCorner corners = topLeftCorner | topRightCorner | bottomLeftCorner | bottomRightCorner; UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect: self.bounds byRoundingCorners:(corners) cornerRadii: CGSizeMake(10.0, 10.0)]; CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init]; maskLayer.frame = self.bounds; maskLayer.path = maskPath.CGPath; self.layer.mask = maskLayer; self.layer.masksToBounds = NO; } @end 
+1
source

I am a fan of what @ Martin offers. As long as there is no animated content around the rounded corner, you can turn it off - even with a bitmap displayed behind the front view requiring a rounded corner.

I created a sample project to emulate your screenshot. The magic happens in a subclass of UIView called TSRoundedCornerView . You can place this view anywhere - above the view on which you want to display a rounded corner, set the property to say which corner to round (adjust the radius by adjusting the size of the view) and set the property, which is the "background view" that you want to see in the corner.

Here's the repo for the sample: https://github.com/TomSwift/testRoundedCorner

And here is the drawing magic for TSRoundedCornerView . Basically we create an inverted clip with our rounded corner, then draw a background.

 - (void) drawRect:(CGRect)rect { CGContextRef gc = UIGraphicsGetCurrentContext(); CGContextSaveGState(gc); { // create an inverted clip path // (thanks rob mayoff: http://stackoverflow.com/questions/9042725/drawrect-how-do-i-do-an-inverted-clip) UIBezierPath* bp = [UIBezierPath bezierPathWithRoundedRect: self.bounds byRoundingCorners: self.corner // eg UIRectCornerTopLeft cornerRadii: self.bounds.size]; CGContextAddPath(gc, bp.CGPath); CGContextAddRect(gc, CGRectInfinite); CGContextEOClip(gc); // self.backgroundView is the view we want to show peering out behind the rounded corner // this works well enough if there only one layer to render and not a view hierarchy! [self.backgroundView.layer renderInContext: gc]; //$ the iOS7 way of rendering the contents of a view. It works, but only if the UIImageView has already painted... I think. //$ if you try this, be sure to setNeedsDisplay on this view from your view controller viewDidAppear: method. // CGRect r = self.backgroundView.bounds; // r.origin = [self.backgroundView convertPoint: CGPointZero toView: self]; // [self.backgroundView drawViewHierarchyInRect: r // afterScreenUpdates: YES]; } CGContextRestoreGState(gc); } 
+1
source

I thought about it again, and I think there is a simpler solution. I updated my sample to demonstrate both solutions.

The new solution is to simply create a container view with 4 rounded corners (via CALayer cornerRadius ). You can customize this view, so only the angle that interests you is displayed on the screen. This solution works inefficiently if you need to round three corners or two opposite (diagonal) corners are rounded. I think it works in most other cases, including the one you described in your question and a screenshot.

Here's the repo for the sample: https://github.com/TomSwift/testRoundedCorner

0
source

Try it. Hope this helps you.

 UIView* parent = [[UIView alloc] initWithFrame:CGRectMake(10,10,100,100)]; parent.clipsToBounds = YES; UIView* child = [[UIView alloc] new]; child.clipsToBounds = YES; child.layer.cornerRadius = 3.0f; child.backgroundColor = [UIColor redColor]; child.frame = CGRectOffset(parent.bounds, +4, -4); [parent addSubView:child]; 
0
source

If you want to do this in Swift, I would advise you to use the UIView extension. Thus, all subclasses will be able to use the following method:

 import QuartzCore extension UIView { func roundCorner(corners: UIRectCorner, radius: CGFloat) { let maskPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSizeMake(radius, radius)) var maskLayer = CAShapeLayer() maskLayer.frame = self.bounds; maskLayer.path = maskPath.CGPath; self.layer.mask = maskLayer; } } self.anImageView.roundCorner(UIRectCorner.TopRight, radius: 10) 
0
source

All Articles