84 votes

Tour deux coins de UIView

Il y A peu, j'ai posté une question à propos de l'arrondissement tout juste deux angles de vue, et a obtenu une grande réponse, mais éprouve des difficultés à mettre en œuvre. Voici mon drawRect: méthode:

- (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?

72voto

lomanf Points 1666


autant que je sache, si vous aussi vous avez besoin de masquer les sous-vues, vous pouvez utiliser CALayer de masquage. Il y a 2 façons de le faire. Le premier est un peu plus élégante, la seconde est une solution de contournement :-) mais il est aussi très rapide. Les deux sont basés sur CALayer de masquage. J'ai utilisé les deux méthodes dans un couple des projets de l'année dernière, alors j'espère que vous trouverez quelque chose d'utile.

Solution 1

Tout d'abord, j'ai créé cette fonction pour générer un masque d'image à la volée (UIImage) avec l'angle arrondi dont j'ai besoin. Cette fonction essentiellement besoin de 5 paramètres: les limites de l'image et des 4 coins de rayon (haut-gauche, haut-droite, bas-gauche et 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, il vous suffit de quelques lignes de code. J'ai mis des trucs dans mon viewController viewDidLoad méthode, car il est plus rapide, mais vous pouvez aussi l'utiliser dans votre personnalisé UIView avec l' layoutSubviews méthode dans l'exemple.



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

Solution 2

Cette solution est un peu plus "sale". Essentiellement, vous pourriez créer un masque de calque avec les coins arrondis dont vous avez besoin (tous les coins). Ensuite, vous devez augmenter la hauteur de la couche de masque par la valeur de l'angle. De cette manière, le bas des coins arrondis sont cachés et vous ne pouvez voir le haut des coins arrondis. J'ai mis le code dans l' viewDidLoad méthode, car il est plus rapide, mais vous pouvez aussi l'utiliser dans votre personnalisé UIView avec l' layoutSubviews méthode 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];
}

Espérons que cette aide. Ciao!

69voto

P.L. Points 488

Le peignage à travers les quelques réponses et commentaires, j'ai découvert que l'utilisation d' UIBezierPath bezierPathWithRoundedRect et CAShapeLayer le plus simple et le plus simple. Il pourrait ne pas être approprié pour les cas très complexes, mais pour les arrondis des coins, il travaille vite et bien pour moi.

J'avais créé un simplifiées helper qui définit l'angle 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 suffit de l'appeler avec le UIRectCorner enum, par exemple:

[self setMaskTo:self.photoView byRoundingCorners:UIRectCornerTopLeft|UIRectCornerBottomLeft];

Veuillez noter que pour moi, je l'utilise pour arrondir les angles de vos photos dans un groupe de UITableViewCell, la 10.0 rayon fonctionne très bien pour moi, si juste besoin de changer la valeur appropriée.

EDIT: juste un avis a déjà répondu de manière similaire que celui-ci (lien). Vous pouvez toujours utiliser cette réponse comme une commodité fonction si nécessaire.

57voto

Nathan Eror Points 6713

Je ne pouvais pas adaptés à ce tous les dans un commentaire de @lomanf de réponse. Je suis donc de l'ajouter comme une réponse.

Comme @lomanf dit, vous devez ajouter un masque de calque pour éviter les sous-couches de dessin en dehors de votre chemin de limites. C'est beaucoup plus facile de le faire maintenant, si. Tant que vous ciblez iOS 3.2 ou supérieur, vous n'avez pas besoin de créer une image avec du quartz et de la définir comme le masque. Vous pouvez simplement créer un CAShapeLayer avec un UIBezierPath et de l'utiliser comme masque.

Aussi, lors de l'utilisation de masques de calque, sélectionnez le calque que vous masquez ne fait partie d'aucune hiérarchie des calques lorsque vous ajoutez le masque. Sinon le comportement est indéfini. Si votre vue est déjà dans la hiérarchie, vous devez le retirer de son superview, masque, puis le remettre là où il était.

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 de la façon Core Animation, de rendu des travaux, le masquage est relativement lent opération. Chaque masque nécessite une passe de rendu. Donc, utiliser des masques avec parcimonie.

Une des meilleures parties de cette approche est que vous n'avez plus besoin de créer un personnalisé, UIView et remplacer drawRect:. Cela devrait rendre votre code plus simple, et peut-être même plus rapidement.

30voto

FreeAsInBeer Points 9791

J'ai pris de Nathan exemple et créé une catégorie sur UIView , afin de permettre à adhérer à SEC principes. Sans plus tarder:

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

L'appel de:

[myView addRoundedCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight
                withRadii:CGSizeMake(20.0f, 20.0f)];

1voto

Steve Riggins Points 165

Création d'un masque et de le poser sur le point de vue de la couche

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X