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


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

845   18  

18 ответов:

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

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

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

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

FYI Ref:enter image description here


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

EDIT: просто обратите внимание, что ранее ответили очень похоже, как этот (ссылке). Вы все еще можете использовать этот ответ в качестве дополнительной функции удобства, если это необходимо.


EDIT: тот же код, что и расширение 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 или выше, вам не нужно создавать изображение с помощью quartz и устанавливать его в качестве маски. Вы можете просто создать 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];

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

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

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

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

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

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

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

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

 // 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);    
}

путь Безье является anwer, если вам нужен дополнительный код это один работал для меня:https://stackoverflow.com/a/13163693/936957

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

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

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

self.clipsToBounds = YES

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

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

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

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

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

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

self.tableView.layer.cornerRadius = 10;

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

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

enter image description here

расширяя принятый ответ, добавим к нему обратную совместимость. До iOS 11, просмотр.слой.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 и @P. L:) я объединил их коды, чтобы сделать его лучше.

Comments

    Ничего не найдено.