Округленный UIView с использованием CALayers - только некоторые углы - как?

121

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

  • Верхний левый
  • Левая нижняя
  • В правом верхнем углу
  • Нижний правый

Над кнопками есть изображение (или 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]];

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

Является ли это возможным? Как?

Сагар Р. Котари
источник

Ответы:

44

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

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

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

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

- (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, чтобы это работало.

Неванский король
источник
Не работает должным образом с представлениями, размер которых может измениться. Пример: UILabel. Установка пути маски и последующее изменение размера путем установки текста сохранит старую маску. Потребуется создание подклассов и перегрузка drawRect.
user3099609 01
358

Начиная с iOS 3.2, вы можете использовать функциональность UIBezierPaths для создания нестандартного прямоугольника с закругленными углами (где скругляются только углы, которые вы укажете). Затем вы можете использовать это как путь к a 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:метода представляет собой битовую маску, и поэтому несколько углы могут быть скруглены по ORing их вместе.


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

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

Поскольку " imageView.layer.mask = maskLayer" применяет маску, тень обычно не отображается вне ее. Уловка состоит в том, чтобы использовать прозрачное представление, а затем добавить два подслоя ( CALayerи) к слою представления: 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];
Стюарт
источник
1
Хороший, это очень помогло мне в сгруппированных ячейках UITableView selectedBackgroundViews :-)
Luke47
В любом случае добавить тень к этому виду после того, как вы скруглили углы? Попытка сделать следующее безуспешно. `self.layer.masksToBounds = NO; self.layer.shadowColor = [UIColor blackColor] .CGColor; self.layer.shadowRadius = 3; self.layer.shadowOffset = CGSizeMake (-3, 3); self.layer.shadowOpacity = 0,8; self.layer.shouldRasterize = ДА; `
Крис Вагнер
1
@ChrisWagner: См. Мою правку в отношении применения тени к закругленному виду.
Стюарт
@StuDev отлично! Я использовал subview для теневого просмотра, но это намного лучше! Не думал добавлять такие подслои. Спасибо!
Крис Вагнер
Почему мое изображение становится белым, тогда когда я прокручиваю UITableView и ячейка воссоздается - все работает! Зачем?
Фернандо Редондо
18

Я использовал этот код во многих местах своего кода, и он работает на 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];
itsaboutcode
источник
При использовании этого решения в UITableView-подпредставлениях (например, UITableViewCells или UITableViewHeaderFooterViews) это приводит к плохой плавности при прокрутке . Другой подход для такого использования - это решение с более высокой производительностью (оно добавляет угловой радиус ко всем углам). Чтобы «показать» только определенные закругленные углы (например, верхний и нижний правые углы), я добавил подпредставление с отрицательным значением на нем frame.origin.xи назначил угловой радиус его слою. Если кто-то нашел лучшее решение, мне интересно.
аннеблю
11

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

let view = UIView()

view.clipsToBounds = true
view.layer.cornerRadius = 8
view.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
onmyway133
источник
8

Расширение 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))
инакомыслящий
источник
5

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

// 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; 
aZtraL-силовик
источник
При использовании этого решения в UITableView-подпредставлениях (например, UITableViewCells или UITableViewHeaderFooterViews) это приводит к плохой плавности при прокрутке . Другой подход для такого использования - это решение с более высокой производительностью (оно добавляет угловой радиус ко всем углам). Чтобы «показать» только определенные закругленные углы (например, верхний и нижний правые углы), я добавил подпредставление с отрицательным значением на нем frame.origin.xи назначил угловой радиус его слою. Если кто-то нашел лучшее решение, мне интересно.
anneblue
5

Спасибо, что поделился. Здесь я хотел бы поделиться решением для 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
Jog Dan
источник
4

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

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

user1259710
источник
3

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

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

MrMage
источник
2

На полдесятилетия позже, но я думаю, что нынешний способ, которым люди это делают, не на 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
    }
}

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

Дэвид
источник
1
Это неправильный способ решения проблемы. Вы должны переопределить, только drawRect(_:)если вы выполняете пользовательский рисунок в своем представлении (например, с помощью Core Graphics), иначе даже пустая реализация может повлиять на производительность. Создавать новый слой маски каждый раз тоже нет необходимости. Все, что вам действительно нужно сделать, это обновить pathсвойство маскирующего слоя при изменении границ представления, и место для этого находится в пределах переопределения layoutSubviews()метода.
Стюарт
2

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

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

Rivera
источник
1

Чтобы добавить к ответу и дополнению , я создал простой, многоразовый UIViewв Swift. В зависимости от вашего варианта использования вы можете захотеть внести изменения (избегать создания объектов в каждом макете и т. Д.), Но я хотел, чтобы это было как можно проще. Расширение позволяет вам UIImageViewпроще применить это к другим представлениям (например ), если вам не нравится создание подклассов.

extension UIView {

    func roundCorners(_ roundedCorners: UIRectCorner, toRadius radius: CGFloat) {
        roundCorners(roundedCorners, toRadii: CGSize(width: radius, height: radius))
    }

    func roundCorners(_ roundedCorners: UIRectCorner, toRadii cornerRadii: CGSize) {
        let maskBezierPath = UIBezierPath(
            roundedRect: bounds,
            byRoundingCorners: roundedCorners,
            cornerRadii: cornerRadii)
        let maskShapeLayer = CAShapeLayer()
        maskShapeLayer.frame = bounds
        maskShapeLayer.path = maskBezierPath.cgPath
        layer.mask = maskShapeLayer
    }
}

class RoundedCornerView: UIView {

    var roundedCorners: UIRectCorner = UIRectCorner.allCorners
    var roundedCornerRadii: CGSize = CGSize(width: 10.0, height: 10.0)

    override func layoutSubviews() {
        super.layoutSubviews()
        roundCorners(roundedCorners, toRadii: roundedCornerRadii)
    }
}

Вот как бы вы применили его к UIViewController:

class MyViewController: UIViewController {

    private var _view: RoundedCornerView {
        return view as! RoundedCornerView
    }

    override func loadView() {
        view = RoundedCornerView()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        _view.roundedCorners = [.topLeft, .topRight]
        _view.roundedCornerRadii = CGSize(width: 10.0, height: 10.0)
    }
}
kgaidis
источник
0

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

@implementation UIView (RoundCorners)

- (void)applyRoundCorners:(UIRectCorner)corners radius:(CGFloat)radius {
    UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:corners cornerRadii:CGSizeMake(radius, radius)];

    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    maskLayer.frame = self.bounds;
    maskLayer.path = maskPath.CGPath;

    self.layer.mask = maskLayer;
}

@end

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

[self.imageView applyRoundCorners:UIRectCornerTopRight|UIRectCornerTopLeft radius:10];
Уилли
источник
0

Я предлагаю определить маску слоя. Сама маска должна быть CAShapeLayerобъектом с выделенным путем. Вы можете использовать следующее расширение UIView (Swift 4.2):

extension UIView {
    func round(corners: UIRectCorner, with radius: CGFloat) {
        let maskLayer = CAShapeLayer()
        maskLayer.frame = bounds
        maskLayer.path = UIBezierPath(
            roundedRect: bounds,
            byRoundingCorners: corners,
            cornerRadii: CGSize(width: radius, height: radius)
        ).cgPath
        layer.mask = maskLayer
   }
}
sgl0v
источник