web-dev-qa-db-fra.com

UIView arrondi en utilisant CALayers - seulement quelques coins - Comment?

Dans ma candidature, quatre boutons sont nommés comme suit:

  • En haut à gauche
  • En bas à gauche
  • En haut à droite
  • En bas à droite

Au-dessus des boutons se trouve une vue d’image (ou une vue UIV).

Supposons maintenant qu'un utilisateur appuie sur le bouton - en haut à gauche. L'image/vue ci-dessus doit être arrondie à ce coin particulier.

J'ai quelques difficultés à appliquer des angles arrondis à UIView.

En ce moment, j'utilise le code suivant pour appliquer les angles arrondis à chaque vue:

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

Le code ci-dessus applique l’arrondi à chacun des coins de la vue fournie. Au lieu de cela, je voulais juste appliquer de la rondeur aux coins sélectionnés comme - haut/haut + gauche/bas + droite etc.

C'est possible? Comment?

118
Sagar R. Kothari

J'ai utilisé la réponse à Comment créer un UILabel aux angles arrondis sur l'iPhone? et le code de Comment une vue rectangle arrondie avec transparence est-elle réalisée sur un iphone? faire ce code. 

Ensuite, j'ai réalisé que j'avais répondu à la mauvaise question (j'ai donné un UILabel arrondi au lieu de UIImage) et j'ai donc utilisé ce code pour le changer:

http://discussions.Apple.com/thread.jspa?threadID=1683876

Créez un projet iPhone avec le modèle Voir. Dans le contrôleur de vue, ajoutez ceci:

- (void)viewDidLoad
{
    CGRect rect = CGRectMake(10, 10, 200, 100);
    MyView *myView = [[MyView alloc] initWithFrame:rect];
    [self.view addSubview:myView];
    [super viewDidLoad];
}

MyView est simplement une UIImageView sous-classe:

@interface MyView : UIImageView
{
}

Je n'avais jamais utilisé de contextes graphiques auparavant, mais j'ai réussi à bricoler ce code. Il manque le code pour deux des coins. Si vous lisez le code, vous pouvez voir comment je l'ai implémenté (en supprimant certains des appels CGContextAddArc et en supprimant certaines valeurs de rayon dans le code. Le code pour tous les coins est là, alors utilisez-le comme point de départ et supprimez les pièces qui créent des coins inutiles Notez que vous pouvez aussi créer des rectangles avec 2 ou 3 coins arrondis si vous le souhaitez.

Le code n'est pas parfait, mais je suis sûr que vous pouvez le ranger un peu. 

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

alt text http://nevan.net/skitch/skitched-20100224-092237.png

N'oubliez pas que vous aurez besoin d'inclure le framework QuartzCore pour que cela fonctionne.

44
nevan king

À partir de iOS 3.2, vous pouvez utiliser la fonctionnalité de UIBezierPaths pour créer un rectangle arrondi prêt à l'emploi (où seuls les coins que vous spécifiez sont arrondis). Vous pouvez ensuite l'utiliser comme chemin d'une variable CAShapeLayer et l'utiliser comme masque pour le calque de votre vue:

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

Et c'est tout - pas de déconner de définir manuellement des formes dans Core Graphics, pas de créer des images de masquage dans Photoshop. La couche n'a même pas besoin d'être invalidée. Appliquer le coin arrondi ou changer de coin est aussi simple que de définir une nouvelle UIBezierPath et d’utiliser sa CGPath comme chemin du calque de masque. Le paramètre corners de la méthode bezierPathWithRoundedRect:byRoundingCorners:cornerRadii: est un masque de bits. Il est donc possible d'arrondir plusieurs angles en ouvrant la propriété OU ensemble .


EDIT: Ajouter une ombre

Si vous souhaitez ajouter une ombre à cela, un peu plus de travail est nécessaire.

Parce que "imageView.layer.mask = maskLayer" applique un masque, une ombre ne sera normalement pas affichée en dehors de celui-ci. L'astuce consiste à utiliser une vue transparente, puis à ajouter deux sous-couches (CALayers) à la couche de la vue: shadowLayer et roundedLayer. Les deux doivent utiliser la variable UIBezierPath. L'image est ajoutée en tant que contenu de 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];
356
Stuart

J'ai utilisé ce code à plusieurs endroits dans mon code et cela fonctionne à 100% correctement. Vous pouvez modifier n’importe quel câble en modifiant une propriété "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];
18
itsaboutcode

Dans iOS 11, nous ne pouvons désormais arrondir que certains coins

let view = UIView()

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

Stuarts, par exemple, pour arrondir des angles spécifiques fonctionne très bien. Si vous voulez arrondir plusieurs coins comme en haut à gauche et à droite, voici comment procéder.

// 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; 
5
aZtraL-EnForceR

Merci d'avoir partagé. J'aimerais ici partager la solution sur Swift 2.0 pour une référence ultérieure sur ce sujet. (pour se conformer au protocole 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
5
Jog Dan

Extension CALayer avec Swift 3 et supérieur syntaxe

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
    }
}

Il peut être utilisé comme:

let layer: CALayer = yourView.layer
layer.round(roundedRect: yourView.bounds, byRoundingCorners: [.bottomLeft, .topLeft], cornerRadii: CGSize(width: 5, height: 5))
4
Maverick

il existe une réponse plus simple et plus rapide qui peut fonctionner en fonction de vos besoins et fonctionne également avec des ombres. vous pouvez définir maskToBounds sur la supercouche sur true et décaler les calques enfants afin que deux de leurs coins soient en dehors des limites de la supercouche, coupant ainsi les coins arrondis des deux côtés.

bien sûr, cela ne fonctionne que lorsque vous souhaitez avoir seulement 2 coins arrondis du même côté et que le contenu du calque est identique lorsque vous coupez quelques pixels d'un côté. fonctionne très bien pour avoir des graphiques à barres arrondis uniquement sur la face supérieure.

4
user1259710

Voir cette question connexe . Vous devrez dessiner votre propre rectangle à une CGPath avec certains coins arrondis, ajouter la CGPath à votre CGContext et y couper un clip en utilisant CGContextClip.

Vous pouvez également tracer le rectangle arrondi avec les valeurs alpha sur une image, puis utiliser cette image pour créer un nouveau calque que vous définissez comme propriété mask de votre calque ( voir la documentation Apple ).

3
MrMage

Arrondir seulement certains coins ne jouera pas Nice avec redimensionnement automatique ou mise en page automatique.

Une autre option consiste donc à utiliser la variable normale cornerRadius et à masquer les coins non souhaités sous une autre vue ou en dehors de ses limites supérieures de vue, en vous assurant qu'elle est définie pour en couper le contenu.

2
Rivera

Une demi-décennie de retard, mais je pense que la façon dont les gens font cela actuellement n’est pas correcte à 100%. De nombreuses personnes ont eu le problème suivant: l'utilisation de la méthode UIBezierPath + CAShapeLayer interfère avec la mise en page automatique, notamment lorsqu'elle est définie sur le scénarimage. Aucune réponse ne va à ce sujet, alors j'ai décidé d'ajouter la mienne.

Il existe un moyen très simple de contourner cela: Tracez les coins arrondis dans la fonction drawRect(rect: CGRect).

Par exemple, si je souhaitais utiliser les angles supérieurs arrondis pour UIView, je sous-classerais UIView, puis l’utiliserais chaque fois que nécessaire.

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
    }
}

C'est la meilleure façon de résoudre le problème et ne prend aucun temps pour s'y adapter.

2
David

Pour ajouter à answer et à addition , j'ai créé une simple variable UIView dans Swift. En fonction de votre cas d'utilisation, vous souhaiterez peut-être apporter des modifications (éviter de créer des objets sur chaque mise en page, etc.), mais je souhaitais le garder aussi simple que possible. L'extension vous permet de l'appliquer à d'autres vues (ex. UIImageView) plus facilement si vous n'aimez pas les sous-classes.

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)
    }
}

Voici comment vous l'appliqueriez à une 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)
    }
}
1
kgaidis

Je suggère de définir un masque de calque. Le masque lui-même devrait être un objet CAShapeLayer avec un chemin dédié. Vous pouvez utiliser la prochaine extension 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
   }
}
0
sgl0v

Pour conclure la réponse de Stuart, vous pouvez utiliser la méthode suivante:

@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

Donc, pour appliquer le coin arrondi, vous faites simplement:

[self.imageView applyRoundCorners:UIRectCornerTopRight|UIRectCornerTopLeft radius:10];
0
Willy