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

Некоторое время назад я опубликовал вопрос о округлении только двух углов представления и получил отличный ответ, но у меня возникли проблемы с его реализацией. Вот мой метод 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 ответов

Решение

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

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

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

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

К вашему сведению:


Насколько я знаю, если вам также нужно замаскировать подпредставления, вы можете использовать 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];
}

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

Прочесав несколько ответов и комментариев, я обнаружил, что используя 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])

Я не смог уместить все это в комментарии к ответу @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:, Это должно сделать ваш код проще, а может быть, даже быстрее.

Я взял пример Натана и создал категорию на 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)];

Чтобы немного расширить ответ 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)];

Если вы хотите сделать это в 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)

Расширяя принятый ответ, давайте добавим к нему обратную совместимость. До iOS 11 view.layer.maskedCorners недоступен. Таким образом, мы можем сделать это

if #available(iOS 11.0, *) {
    myView.layer.maskedCorners = [.layerMinXMinYCorner,.layerMaxXMinYCorner]
} else {
    myView.maskByRoundingCorners([.topLeft, .topRight])
}

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

Мы написали maskByRoundingCorners как расширение UIView, чтобы улучшить повторное использование кода.

Кредиты @SachinVsSachin и @PL:) Я объединил их коды, чтобы сделать его лучше.

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" в соответствии с вашими потребностями.

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

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

Подчеркивая:

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

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

Немного хакерский, но относительно простой (без подклассов, маскирования и т. Д.) Способ получить два UIViews. Оба с clipToBounds = YES, Установите закругленные углы в дочернем представлении, затем поместите его в родительском представлении, чтобы углы, которые вы хотите выровнять, обрезались.

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];

Не поддерживает случай, когда вы хотите, чтобы два диагонально противоположных угла были скруглены.

Решение UIBezierPath.

- (void) drawRect:(CGRect)rect {

    [super drawRect:rect];

    //Create shape which we will draw. 
    CGRect rectangle = CGRectMake(2, 
                                  2, 
                                  rect.size.width - 4,
                                  rect.size.height - 4);

    //Create BezierPath with round corners
    UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:rectangle 
                                                   byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight 
                                                         cornerRadii:CGSizeMake(10.0, 10.0)];

    //Set path width
    [maskPath setLineWidth:2];

    //Set color
    [[UIColor redColor] setStroke];

    //Draw BezierPath to see it
    [maskPath stroke];
}

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

Начиная с вашего кода, вы можете использовать что-то вроде приведенного ниже фрагмента.

Я не уверен, что вы добились такого результата. Стоит также отметить, что если / когда система вызывает drawRect: снова, запрашивая перерисовку только части прямоугольника, это будет вести себя очень странно. Подход Невана, отмеченный выше, может быть лучшим путем.

 // make sure the view's background is set to [UIColor clearColor]
- (void)drawRect:(CGRect)rect
{
    CGFloat radius = 10.0;
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextTranslateCTM(context, rect.size.width/2, rect.size.height/2);
    CGContextRotateCTM(context, M_PI); // rotate so image appears right way up
    CGContextTranslateCTM(context, -rect.size.width/2, -rect.size.height/2);

    CGContextBeginPath(context);

    CGContextMoveToPoint(context, rect.origin.x, rect.origin.y);
    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);
    CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y);
    CGContextClip(context); 

    // now do your drawing, e.g. draw an image
    CGImageRef anImage = [[UIImage imageNamed:@"image.jpg"] CGImage];
    CGContextDrawImage(context, rect, anImage);    
}

Путь Безье является ответом, если вам нужен дополнительный код, этот работал для меня: /questions/41947256/kak-ustanovit-cornerradius-tolko-dlya-verhnego-levogo-i-verhnego-pravogo-ugla-uiview/41947303#41947303

UIBezierPath *maskPath;
maskPath = [UIBezierPath bezierPathWithRoundedRect:_backgroundImageView.bounds 
                                 byRoundingCorners:(UIRectCornerBottomLeft | UIRectCornerBottomRight) 
                                       cornerRadii:CGSizeMake(3.0, 3.0)];

CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
_backgroundImageView.layer.mask = maskLayer;
[maskLayer release];

Это может работать, только если некоторые вещи установлены правильно:

  1. clipsToBounds должен быть установлен в YES
  2. непрозрачный должен быть НЕТ
  3. backgroundColor должен быть "clearColor" (я не совсем уверен в этом)
  4. contentMode должен быть "UIContentModeRedraw", поскольку drawRect вызывается не часто, если это не так
  5. [super drawRect:rect] должно вызываться после CGContextClip
  6. Ваше мнение не может содержать произвольные подпредставления (не уверены в этом)
  7. Убедитесь, что вы установили "needsDisplay:" хотя бы один раз, чтобы активировать ваше рисование

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

self.tableView.layer.cornerRadius = 10;

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

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

Вы, вероятно, должны обрезать границы. Добавьте строку

self.clipsToBounds = YES

где-то в коде, чтобы установить это свойство.

Другие вопросы по тегам