Круглые два угла в 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);
}
метод вызывается, но, похоже, не влияет на результат представления. Есть идеи почему?
18 ответов:
CACornerMask введенный в iOS 11, который помогает определить topleft, topright, bottomleft, нижний правый слой вида. Ниже приведен пример использования .
здесь я стараюсь закруглить только два верхних угла:
myView.clipsToBounds = true myView.layer.cornerRadius = 10 myView.layer.maskedCorners = [.layerMinXMinYCorner,.layerMaxXMinYCorner]спасибо заранее.
насколько я знаю, если вам также нужно замаскировать подвиды, вы можете использовать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 3extension 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; @endUIView + 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где-то в коде, чтобы установить это свойство.
Это может работать только в том случае, если некоторые вещи установлены правильно:
- clipsToBounds должно быть установлено в YES
- непрозрачный должен быть нет
- backgroundColor должен быть " clearColor "(я не совсем уверен в этом)
- contentMode должен быть "UIContentModeRedraw", поскольку drawRect не вызывается часто, если это не
- [super drawRect:rect] должен быть вызван после CGContextClip
- ваше представление может не содержать произвольных подвидов (не уверен на этом)
- обязательно установите "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;программно он должен округлить все четыре угла, но по какой-то причине он округляет только два верхних. **Пожалуйста, смотрите скриншот ниже, чтобы увидеть эффект кода, который я написал выше.
надеюсь, это поможет!
расширяя принятый ответ, добавим к нему обратную совместимость. До 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