Круглые два угла в 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);
}

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

Ответ 1


насколько я знаю, если вам также нужно замаскировать subviews, вы можете использовать маскировку CALayer. Есть два способа сделать это. Первый из них немного более изящный, второй - обходной путь:-), но он также быстрый. Оба основаны на маскировке 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!

Ответ 2

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

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

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

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

Ответ 3

С помощью нескольких ответов и комментариев я узнал, что использование 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 отлично подходит для меня, если нужно просто изменить значение по мере необходимости.

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

Ответ 4

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

Ответ 5

Чтобы немного расширить ответ на P.L, я переписал метод так, чтобы он не округлял определенные объекты, такие как 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)];

Ответ 6

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

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)

Ответ 7

CACornerMask, представленный в iOS 11, который помогает определить topleft, topright, bottomleft, bottom right in view layer. Ниже приведен пример использования.

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

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

Спасибо заранее.

FYI Ref: введите описание изображения здесь

Ответ 8

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

Ответ 9

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

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

Выделение:

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

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

Ответ 10

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

Ответ 11

Начиная с вашего кода, вы можете перейти к фрагменту ниже.

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

 // make sure the view 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);    
}

Ответ 12

Путь Безье - это anwer, если вам нужен дополнительный код, он работал у меня: fooobar.com/questions/28273/...

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

Ответ 13

Решение 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];
}

Ответ 14

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

self.clipsToBounds = YES

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

Ответ 15

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

  • clipsToBounds должен быть установлен на YES
  • непрозрачный должен быть NO
  • backgroundColor должен быть "clearColor" (я не совсем уверен в этом)
  • contentMode должен быть "UIContentModeRedraw", поскольку drawRect не вызывается часто, если он не
  • [super drawRect: rect] должен вызываться после CGContextClip
  • Ваше представление может не содержать произвольных подзапросов (не уверен в этом)
  • Обязательно установите "needsDisplay:" хотя бы один раз, чтобы вызвать ваш drawrect.

Ответ 16

Немного взломанный, но относительно простой (без подкласса, маскировки и т.д.) путь к этому состоит в том, чтобы иметь два 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];

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

Ответ 17

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

self.tableView.layer.cornerRadius = 10;

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

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

enter image description here