얼마 전에 뷰의 두 모서리 만 둥글게 하는 것에 대한 질문을 올렸고 훌륭한 응답을 받았지만 구현하는 데 문제가 있습니다. 내 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);
}
메서드가 호출되고 있지만 뷰의 결과에 영향을주지 않는 것 같습니다. 이유는 무엇입니까?
답변
CACornerMask는 , 뷰 레이어에서 오른쪽 아래를, 좌상 정의 topright, bottomleft하는 데 도움이 아이폰 OS (11)에 소개했다. 아래는 사용 예입니다.
여기에서는 상단 모서리 두 개만 둥글게 만들려고합니다.
myView.clipsToBounds = true
myView.layer.cornerRadius = 10
myView.layer.maskedCorners = [.layerMinXMinYCorner,.layerMaxXMinYCorner]
FYI 참조 :
답변
내가 아는 한, 하위 뷰도 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;
}
이제 몇 줄의 코드 만 있으면됩니다. viewDidLoad
더 빠르기 때문에 viewController 메서드 에 항목을 넣었 지만 예제 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 반경이 잘 작동합니다.
편집 : 이전에 이것과 매우 유사하게 답변 한 것을 주목하십시오 ( 링크 ). 필요한 경우이 답변을 추가 편의 기능으로 계속 사용할 수 있습니다.
편집 : Swift 3의 UIView 확장과 동일한 코드
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 이상을 대상으로하는 한 석영으로 이미지를 만들고 마스크로 설정할 필요가 없습니다. 을 사용하여 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];
Core Animation 렌더링이 작동하는 방식으로 인해 마스킹은 상대적으로 느린 작업입니다. 각 마스크에는 추가 렌더링 패스가 필요합니다. 따라서 마스크를 아껴 사용하십시오.
이 접근 방식의 가장 좋은 부분 중 하나는 더 이상 사용자 지정 UIView
을 만들고을 재정의 할 필요가 없다는 것 drawRect:
입니다. 이렇게하면 코드가 더 간단하고 더 빨라질 것입니다.
답변
나는 Nathan의 예를 들어 UIView
DRY 원칙을 고수 할 수 있도록 카테고리를 만들었습니다 . 더 이상 고민하지 않고 :
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)];
답변
PL의 답변을 조금 확장하기 위해 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)