Округленный UIView с использованием CALayers - только некоторые углы - Как? - PullRequest
119 голосов
/ 15 февраля 2010

В моем приложении четыре кнопки, названные так:

  • Верх - левый
  • Внизу - слева
  • Вверху - справа
  • Внизу - справа

Над кнопками находится вид изображения (или UIView).

Теперь предположим, что пользователь нажимает на верхнюю левую кнопку. Выше изображение / вид должны быть округлены в этом конкретном углу.

У меня возникли сложности с применением закругленных углов к UIView.

Сейчас я использую следующий код, чтобы применить закругленные углы к каждому представлению:

    // imgVUserImg is a image view on IB.
    imgVUserImg.image=[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"any Url Here"];
    CALayer *l = [imgVUserImg layer];
    [l setMasksToBounds:YES];
    [l setCornerRadius:5.0];  
    [l setBorderWidth:2.0];
    [l setBorderColor:[[UIColor darkGrayColor] CGColor]];

Вышеуказанный код применяет округлость к каждому из углов предоставленного вида. Вместо этого я просто хотел применить округлость к выбранным углам, таким как - верх / верх + левый / нижний + правый и т. Д.

Возможно ли это? Как?

Ответы [ 14 ]

357 голосов
/ 29 апреля 2011

Начиная с iOS 3.2, вы можете использовать функциональность UIBezierPath s для создания прямоугольного прямоугольника с закругленными углами (только закругленные углы). Затем вы можете использовать это как путь к CAShapeLayer и использовать это как маску для слоя вашего вида:

// Create the path (with only the top-left corner rounded)
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imageView.bounds 
                                               byRoundingCorners:UIRectCornerTopLeft
                                                     cornerRadii:CGSizeMake(10.0, 10.0)];

// Create the shape layer and set its path
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = imageView.bounds;
maskLayer.path = maskPath.CGPath;

// Set the newly created shape layer as the mask for the image view's layer
imageView.layer.mask = maskLayer;

И это все - не нужно возиться с определением фигур вручную в Core Graphics, не создавать маскирующие изображения в Photoshop. Слой даже не нуждается в аннулировании. Применение закругленного угла или переход к новому углу так же просто, как определение нового UIBezierPath и использование его CGPath в качестве пути слоя маски. Параметр corners метода bezierPathWithRoundedRect:byRoundingCorners:cornerRadii: является битовой маской, поэтому несколько углов могут быть округлены путем ИЛИ их вместе.


РЕДАКТИРОВАТЬ: Добавление тени

Если вы хотите добавить к этому тень, потребуется немного больше работы.

Поскольку "imageView.layer.mask = maskLayer" применяет маску, тень обычно не будет отображаться за ее пределами. Хитрость заключается в том, чтобы использовать прозрачный вид, а затем добавить два подслоя (CALayer s) к слою вида: shadowLayer и roundedLayer. Оба должны использовать UIBezierPath. Изображение добавляется как содержимое roundedLayer.

// Create a transparent view
UIView *theView = [[UIView alloc] initWithFrame:theFrame];
[theView setBackgroundColor:[UIColor clearColor]];

// Create the path (with only the top-left corner rounded)
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:theView.bounds 
                                               byRoundingCorners:UIRectCornerTopLeft
                                                     cornerRadii:CGSizeMake(10.0f, 10.0f)];

// Create the shadow layer
CAShapeLayer *shadowLayer = [CAShapeLayer layer];
[shadowLayer setFrame:theView.bounds];
[shadowLayer setMasksToBounds:NO];
[shadowLayer setShadowPath:maskPath.CGPath];
// ...
// Set the shadowColor, shadowOffset, shadowOpacity & shadowRadius as required
// ...

// Create the rounded layer, and mask it using the rounded mask layer
CALayer *roundedLayer = [CALayer layer];
[roundedLayer setFrame:theView.bounds];
[roundedLayer setContents:(id)theImage.CGImage];

CAShapeLayer *maskLayer = [CAShapeLayer layer];
[maskLayer setFrame:theView.bounds];
[maskLayer setPath:maskPath.CGPath];

roundedLayer.mask = maskLayer;

// Add these two layers as sublayers to the view
[theView.layer addSublayer:shadowLayer];
[theView.layer addSublayer:roundedLayer];
44 голосов
/ 23 февраля 2010

Я использовал ответ в Как создать UILabel с закругленными углами на iPhone? и код из Как сделать округленный прямоугольный вид с прозрачностью на iphone? для сделать этот код

Затем я понял, что ответил на неправильный вопрос (дал округленный UILabel вместо UIImage), поэтому я использовал этот код, чтобы изменить его:

http://discussions.apple.com/thread.jspa?threadID=1683876

Создайте проект iPhone с шаблоном просмотра. В контроллере представления добавьте это:

- (void)viewDidLoad
{
    CGRect rect = CGRectMake(10, 10, 200, 100);
    MyView *myView = [[MyView alloc] initWithFrame:rect];
    [self.view addSubview:myView];
    [super viewDidLoad];
}

MyView это просто UIImageView подкласс:

@interface MyView : UIImageView
{
}

Я никогда раньше не использовал графические контексты, но мне удалось собрать воедино этот код. Не хватает кода для двух углов. Если вы прочитаете код, вы увидите, как я это реализовал (удалив некоторые из вызовов CGContextAddArc и удалив некоторые значения радиуса в коде. Код для всех углов есть, поэтому используйте его в качестве отправной точки и удалите части, которые создают ненужные углы. Обратите внимание, что вы можете также сделать прямоугольники с 2 или 3 закругленными углами.

Код не идеален, но я уверен, что вы можете немного привести его в порядок.

static void addRoundedRectToPath(CGContextRef context, CGRect rect, float radius, int roundedCornerPosition)
{

    // all corners rounded
    //  CGContextMoveToPoint(context, rect.origin.x, rect.origin.y + radius);
    //  CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height - radius);
    //  CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + rect.size.height - radius, 
    //                  radius, M_PI / 4, M_PI / 2, 1);
    //  CGContextAddLineToPoint(context, rect.origin.x + rect.size.width - radius, 
    //                          rect.origin.y + rect.size.height);
    //  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 + radius);
    //  CGContextAddArc(context, rect.origin.x + rect.size.width - radius, rect.origin.y + radius, 
    //                  radius, 0.0f, -M_PI / 2, 1);
    //  CGContextAddLineToPoint(context, rect.origin.x + radius, rect.origin.y);
    //  CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + radius, radius, 
    //                  -M_PI / 2, M_PI, 1);

    // top left
    if (roundedCornerPosition == 1) {
        CGContextMoveToPoint(context, rect.origin.x, rect.origin.y + radius);
        CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height - radius);
        CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + rect.size.height - radius, 
                        radius, M_PI / 4, M_PI / 2, 1);
        CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, 
                                rect.origin.y + rect.size.height);
        CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y);
        CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y);
    }   

    // bottom left
    if (roundedCornerPosition == 2) {
        CGContextMoveToPoint(context, rect.origin.x, rect.origin.y);
        CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height);
        CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, 
                                rect.origin.y + rect.size.height);
        CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y);
        CGContextAddLineToPoint(context, rect.origin.x + radius, rect.origin.y);
        CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + radius, radius, 
                        -M_PI / 2, M_PI, 1);
    }

    // add the other corners here


    CGContextClosePath(context);
    CGContextRestoreGState(context);
}


-(UIImage *)setImage
{
    UIImage *img = [UIImage imageNamed:@"my_image.png"];
    int w = img.size.width;
    int h = img.size.height;

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(NULL, w, h, 8, 4 * w, colorSpace, kCGImageAlphaPremultipliedFirst);

    CGContextBeginPath(context);
    CGRect rect = CGRectMake(0, 0, w, h);


    addRoundedRectToPath(context, rect, 50, 1);
    CGContextClosePath(context);
    CGContextClip(context);

    CGContextDrawImage(context, rect, img.CGImage);

    CGImageRef imageMasked = CGBitmapContextCreateImage(context);
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);
    [img release];

    return [UIImage imageWithCGImage:imageMasked];
}

альтернативный текст http://nevan.net/skitch/skitched-20100224-092237.png

Не забывайте, что вам понадобится интегрированная среда QuartzCore, чтобы это работало.

18 голосов
/ 20 апреля 2012

Я использовал этот код во многих местах моего кода, и он работает на 100% правильно.Вы можете изменить любой угол, изменив одно свойство "byRoundingCorners: UIRectCornerBottomLeft"

UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds byRoundingCorners:UIRectCornerBottomLeft cornerRadii:CGSizeMake(10.0, 10.0)];

                CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
                maskLayer.frame = view.bounds;
                maskLayer.path = maskPath.CGPath;
                view.layer.mask = maskLayer;
                [maskLayer release];
8 голосов
/ 02 февраля 2018

В iOS 11 теперь мы можем закруглять только некоторые углы

let view = UIView()

view.clipsToBounds = true
view.layer.cornerRadius = 8
view.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
5 голосов
/ 12 октября 2015

Спасибо, что поделились. Здесь я хотел бы поделиться решением на Swift 2.0 для дальнейшего использования в этом вопросе. (для соответствия протоколу UIRectCorner)

let mp = UIBezierPath(roundedRect: cell.bounds, byRoundingCorners: [.bottomLeft, .TopLeft], cornerRadii: CGSize(width: 10, height: 10))
let ml = CAShapeLayer()
ml.frame = self.bounds
ml.path = mp.CGPath
self.layer.mask = ml
5 голосов
/ 11 ноября 2013

Пример Stuarts для закругления определенных углов прекрасно работает. Если вы хотите скруглить несколько углов, таких как верхний левый и правый, вот как это сделать

// Create the path (with only the top-left corner rounded)
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imageview
                                               byRoundingCorners:UIRectCornerTopLeft|UIRectCornerTopRight
                                                     cornerRadii:CGSizeMake(10.0, 10.0)];

// Create the shape layer and set its path
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.frame = imageview.bounds;
maskLayer.path = maskPath.CGPath;

// Set the newly created shape layer as the mask for the image view's layer
imageview.layer.mask = maskLayer; 
4 голосов
/ 22 ноября 2016

расширение CALayer с Swift 3 и выше синтаксис

extension CALayer {

    func round(roundedRect rect: CGRect, byRoundingCorners corners: UIRectCorner, cornerRadii: CGSize) -> Void {
        let bp = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: cornerRadii)
        let sl = CAShapeLayer()
        sl.frame = self.bounds
        sl.path = bp.cgPath
        self.mask = sl
    }
}

Может использоваться как:

let layer: CALayer = yourView.layer
layer.round(roundedRect: yourView.bounds, byRoundingCorners: [.bottomLeft, .topLeft], cornerRadii: CGSize(width: 5, height: 5))
4 голосов
/ 04 сентября 2012

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

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

3 голосов
/ 18 февраля 2010

См. этот связанный вопрос . Вам нужно будет нарисовать собственный прямоугольник в CGPath с некоторыми закругленными углами, добавить CGPath к вашему CGContext и затем закрепить его с помощью CGContextClip.

Вы также можете нарисовать скругленный прямоугольник с альфа-значениями на изображении, а затем использовать это изображение для создания нового слоя, который вы устанавливаете в качестве свойства mask вашего слоя ( см. Документацию Apple ).

2 голосов
/ 20 сентября 2015

С опозданием на полдесятилетия, но я думаю, что нынешний способ, которым люди делают это, не на 100% прав. Многие люди сталкивались с проблемой, заключающейся в том, что использование метода UIBezierPath + CAShapeLayer мешает автоматическому макету, особенно когда он установлен на раскадровке. На этот вопрос нет ответов, поэтому я решил добавить свои.

Существует очень простой способ обойти это: нарисовать закругленные углы в функции drawRect(rect: CGRect).

Например, если бы я хотел верхние закругленные углы для UIView, я бы создал подкласс UIView, а затем использовал бы этот подкласс там, где это необходимо.

import UIKit

class TopRoundedView: UIView {

    override func drawRect(rect: CGRect) {
        super.drawRect(rect)

        var maskPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: UIRectCorner.TopLeft | UIRectCorner.TopRight, cornerRadii: CGSizeMake(5.0, 5.0))

        var maskLayer = CAShapeLayer()
        maskLayer.frame = self.bounds
        maskLayer.path = maskPath.CGPath

        self.layer.mask = maskLayer
    }
}

Это лучший способ решить проблему, и совсем не нужно времени для адаптации.

...