CALayers를 사용하는 둥근 UIView-일부 모서리 만-어떻게?


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

iPhone에서 둥근 모서리 UILabel을 만드는 방법에 대한 답변을 사용했습니다 . 그리고 아이폰에서 투명성이있는 둥근 사각형 뷰는 어떻게 되나요? 이 코드를 작성합니다.

그런 다음 잘못된 질문에 대답했다는 것을 깨달았으므로 (UIImage 대신 둥근 UILabel을 사용했습니다)이 코드를 사용하여 변경했습니다.

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 프레임 워크를 가져와야한다는 것을 잊지 마십시오.


크기가 변경 될 수있는보기에서 예상대로 작동하지 않습니다. 예 : UILabel. 마스크 경로를 설정 한 다음 텍스트를 설정하여 크기를 변경하면 이전 마스크가 유지됩니다. drawRect를 서브 클래 싱하고 오버로딩해야합니다.
user3099609 2014-10-01

358

iOS 3.2부터는 UIBezierPaths 의 기능을 사용하여 즉시 사용 가능한 둥근 사각형을 만들 수 있습니다 (지정한 모서리 만 둥근 경우). 그런 다음 이것을의 경로로 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:비트 마스크이므로 여러 모서리를 OR로 둥글게 만들 수 있습니다.


편집 : 그림자 추가

여기에 그림자를 추가하려면 약간의 작업이 필요합니다.

" 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 = 아니오; self.layer.shadowColor = [UIColor blackColor] .CGColor; self.layer.shadowRadius = 3; self.layer.shadowOffset = CGSizeMake (-3, 3); self.layer.shadowOpacity = 0.8; self.layer.shouldRasterize = 예; `
Chris Wagner

1
@ChrisWagner : 둥근보기에 그림자를 적용하는 것과 관련하여 내 편집을 참조하십시오.
Stuart 2011

@StuDev 훌륭합니다! 그림자보기에 대한 하위보기를 사용했지만 훨씬 더 좋습니다! 이와 같은 하위 레이어를 추가하려고 생각하지 않았습니다. 감사!
Chris Wagner 2011

내 이미지가 흰색이되는 이유는 UITableView를 스크롤하고 셀이 다시 생성되면 작동합니다! 왜?
Fernando Redondo

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

UITableView-subviews (예 : UITableViewCells 또는 UITableViewHeaderFooterViews) 에서이 솔루션을 사용 하면 스크롤 할 때 부드러움이 나빠 집니다. 이를위한 또 다른 접근 방식 은 더 나은 성능을 제공하는 솔루션입니다 (모든 모서리에 cornerRadius 추가). 특정 모서리 만 둥글게 표시하기 위해 (오른쪽 상단 및 하단 모서리와 같이) 음수 값이있는 하위 뷰를 추가 frame.origin.x하고 cornerRadius를 레이어에 할당했습니다. 누군가가 더 나은 해결책을 찾았다면 관심이 있습니다.
anneblue 2014 년

11

iOS 11에서는 이제 일부 모서리 만 둥글게 할 수 있습니다.

let view = UIView()

view.clipsToBounds = true
view.layer.cornerRadius = 8
view.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]

7

Swift 3+ 구문을 사용하는 CALayer 확장

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

특정 모서리를 둥글게하는 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; 

UITableView-subviews (예 : UITableViewCells 또는 UITableViewHeaderFooterViews) 에서이 솔루션을 사용 하면 스크롤 할 때 부드러움이 나빠 집니다. 이를위한 또 다른 접근 방식 은 더 나은 성능을 제공하는 솔루션입니다 (모든 모서리에 cornerRadius 추가). 특정 모서리 만 둥글게 표시하기 위해 (오른쪽 상단 및 하단 모서리와 같이) 음수 값이있는 하위 뷰를 추가 frame.origin.x하고 cornerRadius를 레이어에 할당했습니다. 누군가가 더 나은 해결책을 찾았다면 관심이 있습니다.
anneblue 2014 년

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

4

필요에 따라 작동하고 그림자와 함께 작동 할 수있는 더 쉽고 빠른 답변이 있습니다. 수퍼 레이어의 maskToBounds를 true로 설정하고 모서리 중 2 개가 수퍼 레이어 경계 밖에 있도록 자식 레이어를 오프셋하여 두면에서 둥근 모서리를 효과적으로 절단 할 수 있습니다.

물론 이것은 같은면에 둥근 모서리가 2 개만 있고 한면에서 몇 픽셀을 잘라 내면 레이어의 내용이 동일하게 보일 때만 작동합니다. 막대 차트를 윗면 만 둥글게하는 데 적합합니다.


3

이 관련 질문을 참조하십시오 . 당신은 당신의 자신의 사각형을 그릴해야 CGPath약간 둥근 모서리의 추가 CGPath당신에게 CGContext그것을 사용에 다음 클립 CGContextClip.

알파 값이있는 둥근 사각형을 이미지에 그린 다음 해당 이미지를 사용하여 레이어의 mask속성 으로 설정 한 새 레이어를 만들 수도 있습니다 ( Apple 문서 참조 ).


2

50 년이 늦었지만 현재 사람들이 이것을하는 방식이 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()메서드 재정의 내에 있습니다 .
Stuart

2

일부 모서리 만 둥글게하는 것은 자동 크기 조정 또는 자동 레이아웃에서 제대로 작동하지 않습니다.

따라서 또 다른 옵션은 일반을 사용 cornerRadius하고 다른 뷰 아래 또는 수퍼 뷰 경계 외부에서 원하지 않는 모서리를 숨기고 내용을 클리핑하도록 설정하는 것입니다.


1

받는 사람에 추가하려면 대답 하고, 또한 , 나는 재사용 가능한 간단한 생성 UIView스위프트에 있습니다. 사용 사례에 따라 수정을 원할 수도 있지만 (모든 레이아웃에 개체를 생성하지 않는 등) 가능한 한 간단하게 유지하고 싶었습니다. 확장 기능을 사용하면 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)
    }
}

0

Stuart의 답변을 마무리하면 다음과 같이 둥근 모서리 방법을 사용할 수 있습니다.

@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
   }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.