Il y a quelques instants, j'ai posté une question à propos de arrondir les angles d'une vue , et j'ai obtenu une excellente réponse, mais j'ai du mal à la mettre en œuvre. Voici ma méthode 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);
}
La méthode est appelée, mais ne semble pas affecter le résultat de la vue. Des idées pourquoi?
CACornerMask introduit dans iOS 11, qui aide à définir les couches supérieure, supérieure, inférieure gauche et inférieure droite dans la vue. Vous trouverez ci-dessous un exemple à utiliser.
Ici, j'essaye d'arrondir seulement deux coins supérieurs:
myView.clipsToBounds = true
myView.layer.cornerRadius = 10
myView.layer.maskedCorners = [.layerMinXMinYCorner,.layerMaxXMinYCorner]
Merci d'avance.
autant que je sache, si vous devez également masquer les sous-vues, vous pouvez utiliser CALayer
masquage. Il y a 2 façons de faire cela. Le premier est un peu plus élégant, le second est une solution de contournement :-) mais c'est aussi rapide. Les deux sont basés sur le masquage CALayer
. J'ai utilisé les deux méthodes dans quelques projets l'année dernière, alors j'espère que vous pourrez trouver quelque chose d'utile.
Tout d'abord, j'ai créé cette fonction pour générer un masque d'image à la volée (UIImage
) avec le coin arrondi dont j'ai besoin. Cette fonction nécessite essentiellement 5 paramètres: les limites de l’image et 4 rayons de coin (en haut à gauche, en haut à droite, en bas à gauche et en bas à droite).
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;
}
Maintenant, vous avez juste besoin de quelques lignes de code. J'ai mis des éléments dans ma méthode viewController viewDidLoad
car elle est plus rapide, mais vous pouvez également l'utiliser dans votre exemple personnalisé UIView
avec la méthode 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];
}
Cette solution est un peu plus "sale". Pour l'essentiel, vous pouvez créer un calque de masque avec le coin arrondi dont vous avez besoin (tous les coins). Ensuite, vous devez augmenter la hauteur du calque de masque de la valeur du rayon de l'angle. De cette façon, les coins arrondis inférieurs sont masqués et vous ne pouvez voir que le coin arrondi supérieur. J'ai mis le code dans la méthode viewDidLoad
car il est plus rapide, mais vous pouvez également l'utiliser dans votre UIView
personnalisé avec la méthode layoutSubviews
dans l'exemple.
- (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];
}
J'espère que cela t'aides. Ciao!
En combinant les quelques réponses et commentaires, j'ai découvert que l'utilisation de UIBezierPath bezierPathWithRoundedRect
et CAShapeLayer
le moyen le plus simple et le plus direct. Cela peut ne pas être approprié pour les cas très complexes, mais pour les arrondis occasionnels, cela fonctionne rapidement et en douceur pour moi.
J'avais créé un assistant simplifié qui définit le coin approprié dans le masque:
-(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;
}
Pour l'utiliser, il vous suffit d'appeler avec l'enumération UIRectCorner appropriée, par exemple:
[self setMaskTo:self.photoView byRoundingCorners:UIRectCornerTopLeft|UIRectCornerBottomLeft];
Veuillez noter que pour moi, je l'utilise pour arrondir les angles d'une photo dans un groupe UITableViewCell groupé; le rayon de 10,0 fonctionne correctement pour moi, si nécessaire, il suffit de modifier la valeur comme il convient.
MODIFIER: remarquez juste une réponse déjà très semblable à celle-ci ( lien ). Vous pouvez toujours utiliser cette réponse comme une fonction pratique supplémentaire si nécessaire.
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
}
}
Pour l'utiliser, appelez simplement maskByRoundingCorner
sur n'importe quel UIView
:
view.maskByRoundingCorners([.topLeft, .bottomLeft])
Je ne pouvais pas intégrer tout cela dans un commentaire à la réponse de @ lomanf. Donc, je l'ajoute comme une réponse.
Comme @lomanf l'a dit, vous devez ajouter un masque de calque pour empêcher les sous-couches de dessiner en dehors des limites de votre chemin. C'est beaucoup plus facile à faire maintenant, cependant. Tant que vous ciblez iOS 3.2 ou une version ultérieure, vous n'avez pas besoin de créer une image avec quartz et de la définir comme masque. Vous pouvez simplement créer un CAShapeLayer
avec un UIBezierPath
et l'utiliser comme masque.
De même, lorsque vous utilisez des masques de calque, assurez-vous que le calque que vous masquez ne fait pas partie d'une hiérarchie de calques lorsque vous ajoutez le masque. Sinon, le comportement n'est pas défini. Si votre vue est déjà dans la hiérarchie, vous devez la supprimer de sa vue d'ensemble, la masquer, puis la remettre à son emplacement d'origine.
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];
En raison du fonctionnement du rendu de Core Animation, le masquage est une opération relativement lente. Chaque masque nécessite une passe de rendu supplémentaire. Donc, utilisez les masques avec parcimonie.
L’un des avantages de cette approche est qu’il n’est plus nécessaire de créer un UIView
personnalisé et de remplacer drawRect:
. Cela devrait rendre votre code plus simple et peut-être même plus rapide.
J'ai pris l'exemple de Nathan et créé une catégorie sur UIView
pour permettre à celui-ci d'adhérer aux principes DRY. Sans plus tarder:
#import <UIKit/UIKit.h>
@interface UIView (Roundify)
-(void)addRoundedCorners:(UIRectCorner)corners withRadii:(CGSize)radii;
-(CALayer*)maskForRoundedCorners:(UIRectCorner)corners withRadii:(CGSize)radii;
@end
#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
Appeler:
[myView addRoundedCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight
withRadii:CGSizeMake(20.0f, 20.0f)];
Pour développer un peu la réponse de P.L, j'ai réécrit la méthode de manière à ne pas arrondir correctement certains objets tels que 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;
}
Et appelez par
[self setMaskTo:self.continueButton byRoundingCorners:UIRectCornerBottomLeft|UIRectCornerBottomRight withCornerRadii:CGSizeMake(3.0, 3.0)];
Si vous voulez le faire dans Swift, vous pouvez utiliser l'extension de UIView
. _. De cette manière, toutes les sous-classes pourront utiliser la méthode suivante:
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
}
}
Exemple d'utilisation:
self.anImageView.roundCorner(.topRight, radius: 10)
Toutes les solutions fournies permettent d'atteindre l'objectif. Mais, UIConstraints
peut parfois exploser.
Par exemple, les coins inférieurs doivent être arrondis. Si la contrainte de hauteur ou d'espacement inférieur est définie sur UIView à arrondir, les extraits de code qui arrondissent les angles doivent être déplacés vers la méthode
viewDidLayoutSubviews
.
Mise en évidence:
UIBezierPath *maskPath = [UIBezierPath
bezierPathWithRoundedRect:roundedView.bounds byRoundingCorners:
(UIRectCornerTopRight | UIRectCornerBottomRight) cornerRadii:CGSizeMake(16.0, 16.0)];
L'extrait de code ci-dessus ne contournera le coin supérieur droit que si ce code est défini dans viewDidLoad
. Car roundedView.bounds
va changer après que les contraintes aient mis à jour le UIView
.
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)];
changez "AllCorners" selon vos besoins.
En commençant par votre code, vous pouvez utiliser quelque chose comme l'extrait ci-dessous.
Je ne sais pas si c'est le genre de résultat que vous recherchez. Il convient de noter également que si/lorsque le système appelle drawRect: là encore, demandant de ne redessiner qu’une partie du rect, cela se comportera très étrangement. approche de Nevan , noté ci-dessus, pourrait être une meilleure façon de faire.
// 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);
}
Un moyen légèrement hacky, mais relativement simple (pas de sous-classement, masquage, etc.), consiste à avoir deux vues UIV. Les deux avec clipToBounds = YES
. Définissez des coins arrondis sur la vue enfant, puis positionnez-la dans la vue parent afin que les coins que vous souhaitez droits soient rognés.
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];
Ne prend pas en charge le cas où vous souhaitez arrondir les angles opposés en diagonale.
Solution 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];
}
Le chemin de Bézier est la réponse, si vous avez besoin de code supplémentaire, celui-ci a fonctionné pour moi: 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];
Créer un masque et le définir sur le calque de la vue
En élargissant la réponse acceptée, ajoutons-lui une compatibilité ascendante. Avant iOS 11, view.layer.maskedCorners n'était pas disponible. Donc on peut faire comme ça
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
}
}
Nous avons écrit maskByRoundingCorners en tant qu'extension UIView afin d'améliorer la réutilisation du code.
Crédits à @SachinVsSachin et @ P.L :) J'ai combiné leurs codes pour l'améliorer.
Je me rends compte que vous essayez de contourner les deux coins supérieurs d'un UITableView, mais pour une raison quelconque, j'ai découvert que la meilleure solution consiste à utiliser:
self.tableView.layer.cornerRadius = 10;
Par programme, il devrait tourner autour des quatre coins, mais pour une raison quelconque, il ne fait que tourner les deux premiers. ** S'il vous plaît voir la capture d'écran ci-dessous pour voir l'effet du code que j'ai écrit ci-dessus.
J'espère que ça aide!
Cela ne peut fonctionner que si certaines choses sont correctement définies: