Раунд два угла в UIView - PullRequest
       70

Раунд два угла в UIView

75 голосов
/ 31 января 2011

Некоторое время назад я опубликовал вопрос о округлении только двух углов представления , и получил отличный ответ, но у меня возникли проблемы с его реализацией. Вот мой drawRect: метод:

- (void)drawRect:(CGRect)rect {
    //[super drawRect:rect]; <------Should I uncomment this?
    int radius = 5;
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextBeginPath(context);
    CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + rect.size.height - radius, radius, M_PI, M_PI / 2, 1);
    CGContextAddArc(context, rect.origin.x + rect.size.width - radius, rect.origin.y + rect.size.height - radius, radius, M_PI / 2, 0.0f, 1);
    CGContextClosePath(context);
    CGContextClip(context);
}

Метод вызывается, но, похоже, не влияет на результат просмотра. Есть идеи почему?

Ответы [ 18 ]

72 голосов
/ 08 февраля 2011

Насколько я знаю, если вам также нужно замаскировать подпредставления, вы можете использовать CALayer маскирование.Есть 2 способа сделать это.Первый более элегантный, второй - обходной путь :-), но он также быстрый.Оба основаны на CALayer маскировании.Я использовал оба метода в нескольких проектах в прошлом году, и я надеюсь, что вы найдете что-то полезное.

Решение 1

Прежде всего, я создал эту функцию для генерации маски изображения намуха (UIImage) с закругленным углом, который мне нужен.Эта функция по существу нуждается в 5 параметрах: границы изображения и 4 угловых радиуса (верхний левый, верхний правый, нижний левый и нижний правый).


static inline UIImage* MTDContextCreateRoundedMask( CGRect rect, CGFloat radius_tl, CGFloat radius_tr, CGFloat radius_bl, CGFloat radius_br ) {  

    CGContextRef context;
    CGColorSpaceRef colorSpace;

    colorSpace = CGColorSpaceCreateDeviceRGB();

    // create a bitmap graphics context the size of the image
    context = CGBitmapContextCreate( NULL, rect.size.width, rect.size.height, 8, 0, colorSpace, kCGImageAlphaPremultipliedLast );

    // free the rgb colorspace
    CGColorSpaceRelease(colorSpace);    

    if ( context == NULL ) {
        return NULL;
    }

    // cerate mask

    CGFloat minx = CGRectGetMinX( rect ), midx = CGRectGetMidX( rect ), maxx = CGRectGetMaxX( rect );
    CGFloat miny = CGRectGetMinY( rect ), midy = CGRectGetMidY( rect ), maxy = CGRectGetMaxY( rect );

    CGContextBeginPath( context );
    CGContextSetGrayFillColor( context, 1.0, 0.0 );
    CGContextAddRect( context, rect );
    CGContextClosePath( context );
    CGContextDrawPath( context, kCGPathFill );

    CGContextSetGrayFillColor( context, 1.0, 1.0 );
    CGContextBeginPath( context );
    CGContextMoveToPoint( context, minx, midy );
    CGContextAddArcToPoint( context, minx, miny, midx, miny, radius_bl );
    CGContextAddArcToPoint( context, maxx, miny, maxx, midy, radius_br );
    CGContextAddArcToPoint( context, maxx, maxy, midx, maxy, radius_tr );
    CGContextAddArcToPoint( context, minx, maxy, minx, midy, radius_tl );
    CGContextClosePath( context );
    CGContextDrawPath( context, kCGPathFill );

    // Create CGImageRef of the main view bitmap content, and then
    // release that bitmap context
    CGImageRef bitmapContext = CGBitmapContextCreateImage( context );
    CGContextRelease( context );

    // convert the finished resized image to a UIImage 
    UIImage *theImage = [UIImage imageWithCGImage:bitmapContext];
    // image is retained by the property setting above, so we can 
    // release the original
    CGImageRelease(bitmapContext);

    // return the image
    return theImage;
}  

Теперь вам просто нужно несколько строккод.Я поместил материал в метод viewController viewDidLoad, потому что он быстрее, но вы можете использовать его и в своем собственном UIView с методом layoutSubviews, например.



- (void)viewDidLoad {

    // Create the mask image you need calling the previous function
    UIImage *mask = MTDContextCreateRoundedMask( self.view.bounds, 50.0, 50.0, 0.0, 0.0 );
    // Create a new layer that will work as a mask
    CALayer *layerMask = [CALayer layer];
    layerMask.frame = self.view.bounds;       
    // Put the mask image as content of the layer
    layerMask.contents = (id)mask.CGImage;       
    // set the mask layer as mask of the view layer
    self.view.layer.mask = layerMask;              

    // Add a backaground color just to check if it works
    self.view.backgroundColor = [UIColor redColor];
    // Add a test view to verify the correct mask clipping
    UIView *testView = [[UIView alloc] initWithFrame:CGRectMake( 0.0, 0.0, 50.0, 50.0 )];
    testView.backgroundColor = [UIColor blueColor];
    [self.view addSubview:testView];
    [testView release];

    [super viewDidLoad];
}

Решение 2

Это решение немного более «грязное».По сути, вы можете создать слой маски с закругленным углом, который вам нужен (все углы).Затем вы должны увеличить высоту маскирующего слоя на величину радиуса угла.Таким образом, нижние закругленные углы скрыты, и вы можете видеть только верхний закругленный угол.Я поместил код только в метод viewDidLoad, потому что он быстрее, но вы можете использовать его и в своем пользовательском UIView с методом layoutSubviews в примере.

  

- (void)viewDidLoad {

    // set the radius
    CGFloat radius = 50.0;
    // set the mask frame, and increase the height by the 
    // corner radius to hide bottom corners
    CGRect maskFrame = self.view.bounds;
    maskFrame.size.height += radius;
    // create the mask layer
    CALayer *maskLayer = [CALayer layer];
    maskLayer.cornerRadius = radius;
    maskLayer.backgroundColor = [UIColor blackColor].CGColor;
    maskLayer.frame = maskFrame;

    // set the mask
    self.view.layer.mask = maskLayer;

    // Add a backaground color just to check if it works
    self.view.backgroundColor = [UIColor redColor];
    // Add a test view to verify the correct mask clipping
    UIView *testView = [[UIView alloc] initWithFrame:CGRectMake( 0.0, 0.0, 50.0, 50.0 )];
    testView.backgroundColor = [UIColor blueColor];
    [self.view addSubview:testView];
    [testView release];

    [super viewDidLoad];
}

Надеюсь, это поможет.Ciao!

68 голосов
/ 29 июня 2012

Прочесав несколько ответов и комментариев, я обнаружил, что использование UIBezierPath bezierPathWithRoundedRect и CAShapeLayer - самый простой и прямой путь.Это может не подходить для очень сложных случаев, но для случайного скругления углов это работает быстро и плавно для меня.

Я создал упрощенный помощник, который устанавливает соответствующий угол в маске:

-(void) setMaskTo:(UIView*)view byRoundingCorners:(UIRectCorner)corners
{
    UIBezierPath* rounded = [UIBezierPath bezierPathWithRoundedRect:view.bounds byRoundingCorners:corners cornerRadii:CGSizeMake(10.0, 10.0)];

    CAShapeLayer* shape = [[CAShapeLayer alloc] init];
    [shape setPath:rounded.CGPath];

    view.layer.mask = shape;
}

Чтобы использовать его, просто позвоните с соответствующим перечислением UIRectCorner, например:

[self setMaskTo:self.photoView byRoundingCorners:UIRectCornerTopLeft|UIRectCornerBottomLeft];

Обратите внимание, что я использую его для округления углов фотографий в сгруппированном UITableViewCell, радиус 10,0работает нормально для меня, если нужно просто изменить значение соответствующим образом.

РЕДАКТИРОВАТЬ: просто обратите внимание на ранее ответил очень похоже, как этот ( ссылка ).Вы по-прежнему можете использовать этот ответ в качестве дополнительной удобной функции, если это необходимо.

РЕДАКТИРОВАТЬ: Тот же код, что и расширение UIView в Swift 3

extension UIView {
    func maskByRoundingCorners(_ masks:UIRectCorner, withRadii radii:CGSize = CGSize(width: 10, height: 10)) {
        let rounded = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: masks, cornerRadii: radii)

        let shape = CAShapeLayer()
        shape.path = rounded.cgPath

        self.layer.mask = shape
    }
}

Чтобы использовать его, просто позвоните maskByRoundingCorner на любой UIView:

view.maskByRoundingCorners([.topLeft, .bottomLeft])
63 голосов
/ 01 августа 2017

CACornerMask , представленный в iOS 11, который помогает определить верхний левый, верхний левый, нижний левый и правый нижние слои в виде представления Ниже приведен пример использования.

Здесь я пытаюсь закруглить только два верхних угла:

myView.clipsToBounds = true
myView.layer.cornerRadius = 10
myView.layer.maskedCorners = [.layerMinXMinYCorner,.layerMaxXMinYCorner]

Заранее спасибо.

К вашему сведению: enter image description here

56 голосов
/ 08 февраля 2011

Я не смог уместить все это в комментарии к ответу @ lomanf.Поэтому я добавляю это как ответ.

Как сказал @lomanf, вам нужно добавить маску слоя, чтобы подслой не рисовался за пределами границ вашего пути.Сейчас это сделать намного проще.Пока вы ориентируетесь на iOS 3.2 или выше, вам не нужно создавать изображение с кварцем и устанавливать его в качестве маски.Вы можете просто создать CAShapeLayer с UIBezierPath и использовать его в качестве маски.

Кроме того, при использовании масок слоев убедитесь, что маскируемый слой не является частью какой-либо иерархии слоев, когдаВы добавляете маску.В противном случае поведение не определено.Если ваше представление уже находится в иерархии, вам нужно удалить его из суперпредставления, замаскировать его и вернуть обратно на прежнее место.

CAShapeLayer *maskLayer = [CAShapeLayer layer];
UIBezierPath *roundedPath = 
  [UIBezierPath bezierPathWithRoundedRect:maskLayer.bounds
                        byRoundingCorners:UIRectCornerTopLeft |
                                          UIRectCornerBottomRight
                              cornerRadii:CGSizeMake(16.f, 16.f)];    
maskLayer.fillColor = [[UIColor whiteColor] CGColor];
maskLayer.backgroundColor = [[UIColor clearColor] CGColor];
maskLayer.path = [roundedPath CGPath];

//Don't add masks to layers already in the hierarchy!
UIView *superview = [self.view superview];
[self.view removeFromSuperview];
self.view.layer.mask = maskLayer;
[superview addSubview:self.view];

Из-за того, как работает рендеринг Core Animation, маскированиеотносительно медленная работа.Каждая маска требует дополнительного прохода рендеринга.Поэтому используйте экономно.

Одна из лучших частей этого подхода - вам больше не нужно создавать пользовательский UIView и переопределять drawRect:.Это должно сделать ваш код проще и, возможно, даже быстрее.

30 голосов
/ 23 января 2013

Я взял пример Натана и создал категорию на UIView, чтобы позволить ей придерживаться принципов СУХОЙ.Без лишних слов:

UIView + Roundify.h

#import <UIKit/UIKit.h>

@interface UIView (Roundify)

-(void)addRoundedCorners:(UIRectCorner)corners withRadii:(CGSize)radii;
-(CALayer*)maskForRoundedCorners:(UIRectCorner)corners withRadii:(CGSize)radii;

@end

UIView + Roundify.m

#import "UIView+Roundify.h"

@implementation UIView (Roundify)

-(void)addRoundedCorners:(UIRectCorner)corners withRadii:(CGSize)radii {
    CALayer *tMaskLayer = [self maskForRoundedCorners:corners withRadii:radii];
    self.layer.mask = tMaskLayer;
}

-(CALayer*)maskForRoundedCorners:(UIRectCorner)corners withRadii:(CGSize)radii {
    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    maskLayer.frame = self.bounds;

    UIBezierPath *roundedPath = [UIBezierPath bezierPathWithRoundedRect:
        maskLayer.bounds byRoundingCorners:corners cornerRadii:radii];
    maskLayer.fillColor = [[UIColor whiteColor] CGColor];
    maskLayer.backgroundColor = [[UIColor clearColor] CGColor];
    maskLayer.path = [roundedPath CGPath];

    return maskLayer;
}

@end

Для вызова:

[myView addRoundedCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight
                withRadii:CGSizeMake(20.0f, 20.0f)];
24 голосов
/ 24 марта 2015

Чтобы немного расширить ответ PL, я переписал метод так, чтобы он не округлял определенные объекты, такие как UIButton правильно

- (void)setMaskTo:(id)sender byRoundingCorners:(UIRectCorner)corners withCornerRadii:(CGSize)radii
{
    // UIButton requires this
    [sender layer].cornerRadius = 0.0;

    UIBezierPath *shapePath = [UIBezierPath bezierPathWithRoundedRect:[sender bounds]
                                                byRoundingCorners:corners
                                                cornerRadii:radii];

    CAShapeLayer *newCornerLayer = [CAShapeLayer layer];
    newCornerLayer.frame = [sender bounds];
    newCornerLayer.path = shapePath.CGPath;
    [sender layer].mask = newCornerLayer;
}

И вызывал его как

[self setMaskTo:self.continueButton byRoundingCorners:UIRectCornerBottomLeft|UIRectCornerBottomRight withCornerRadii:CGSizeMake(3.0, 3.0)];
11 голосов
/ 24 июля 2014

Если вы хотите сделать это в Swift, вы можете использовать расширение UIView.Таким образом, все подклассы смогут использовать следующий метод:

import QuartzCore

extension UIView {
    func roundCorner(corners: UIRectCorner, radius: CGFloat) {
        let maskPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        let maskLayer = CAShapeLayer()
        maskLayer.frame = bounds
        maskLayer.path = maskPath.CGPath
        layer.mask = maskLayer
    }
}    

Пример использования:

self.anImageView.roundCorner(.topRight, radius: 10)
2 голосов
/ 14 марта 2012
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(5, 5, self.bounds.size.width-10, self.bounds.size.height-10)
                                               byRoundingCorners:UIRectCornerAllCorners
                                                     cornerRadii:CGSizeMake(12.0, 12.0)];

изменить "AllCorners" в соответствии с вашими потребностями.

2 голосов
/ 11 марта 2015

Все предоставленные решения достигают цели.Но, UIConstraints иногда может взорвать это.

Например, нижние углы должны быть закруглены.Если для высоты или нижнего интервала установлено значение UIView, которое необходимо округлить, фрагменты кода, округляющие углы, необходимо переместить в метод viewDidLayoutSubviews.

Подсветка:

UIBezierPath *maskPath = [UIBezierPath
    bezierPathWithRoundedRect:roundedView.bounds byRoundingCorners:
    (UIRectCornerTopRight | UIRectCornerBottomRight) cornerRadii:CGSizeMake(16.0, 16.0)];

Приведенный выше фрагмент кода будет закруглен только в верхнем правом углу, если этот код установлен в viewDidLoad.Потому что roundedView.bounds изменится после того, как ограничения обновят UIView.

1 голос
/ 31 января 2011

Создайте маску и установите ее на слой вида

...