Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to round off one corner of a resizable UIView in IOS?

Tags:

ios

calayer

I'm using this code to round off 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, as long as I don't ever resize the view. If I make the view larger, the new area does not appear because it's outside the bounds of the mask layer (this mask layer does not automatically resize itself with the view). I could just make the mask as large as it will ever need to be, but it could be full-screen on the iPad so I'm worried about performance with a mask that big (I'll have more than one of these in my UI). Also, a super-sized mask wouldn't work for the situation where I need the upper right corner (alone) to be rounded off.

Is there a simpler, easier way to achieve this?

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

I have achieved a working version of this, using a subclass of UINavigationController and overriding viewDidLayoutSubviews like so:

- (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;
}

I then instantiate my UINavigationController subclass with my view controller, and then I offset the frame of the nav controller's view by 20px (y) to expose the status bar and leave a 44-px high navigation bar, as shown in the picture.

The code is working, except that it doesn't handle rotation very well at all. When the app rotates, viewDidLayoutSubviews gets called before the rotation and my code creates a mask that fits the view after rotation; this creates an undesirable blockiness to the rotation, where bits that should be hidden are exposed during the rotation. Also, whereas the app's rotation is perfectly smooth without this mask, with the mask being created the rotation becomes noticeably jerky and slow.

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

like image 882
MusiGenesis Avatar asked Jan 23 '14 02:01

MusiGenesis


2 Answers

The problem is, CoreAnimation properties do not animate in UIKit animation blocks. You need to create a separate animation which will have the same curve and duration as the UIKit animation.

I created the mask layer in viewDidLoad. When the view is about to be layout, I only modify the path property of the mask layer.

You do not know the rotation duration inside the layout callback methods, but you do know it right before rotation (and before layout is triggered), so you can keep 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;
}
like image 50
Léo Natan Avatar answered Oct 17 '22 00:10

Léo Natan


I know this is a pretty hacky way of doing it but couldn't you just add a png over the top of the corner?

Ugly I know, but it won't affect performance, rotation will be fine if its a subview and users won't notice.

like image 44
Martin Avatar answered Oct 17 '22 00:10

Martin