web-dev-qa-db-ger.com

Abgerundete UIView mit CALayers - nur einige Ecken - wie?

In meiner Anwendung gibt es vier Schaltflächen, die wie folgt benannt werden:

  • Oben links
  • Unten links
  • Oben rechts
  • Unten rechts

Über den Schaltflächen befindet sich eine Bildansicht (oder eine UIView).

Nehmen wir an, ein Benutzer tippt auf die Schaltfläche oben links. Über Bild/Ansicht sollte an dieser bestimmten Ecke gerundet werden.

Ich habe Schwierigkeiten, abgerundete Ecken auf die UIView anzuwenden.

Im Moment verwende ich den folgenden Code, um die abgerundeten Ecken auf jede Ansicht anzuwenden:

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

Mit dem obigen Code wird die Rundung auf jede der Ecken der bereitgestellten Ansicht angewendet. Stattdessen wollte ich nur ausgewählte Ecken mit Rundheit versehen, z. B. - oben/oben + links/unten + rechts usw.

Ist es möglich? Wie?

118

Ich habe die Antwort verwendet unter Wie erstelle ich ein UILabel mit runden Ecken auf dem iPhone? und der Code aus Wie wird eine abgerundete Ansicht mit Transparenz auf dem iPhone gemacht? um diesen Code zu machen. 

Dann wurde mir klar, dass ich die falsche Frage beantwortet hatte (gab anstelle von UIImage ein abgerundetes UILabel an), also habe ich diesen Code verwendet, um sie zu ändern:

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

Erstellen Sie ein iPhone-Projekt mit der Ansichtsvorlage. Fügen Sie im View-Controller Folgendes hinzu:

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

MyView ist nur eine UIImageView-Unterklasse:

@interface MyView : UIImageView
{
}

Ich hatte noch nie zuvor Grafikkontexte verwendet, konnte diesen Code jedoch zusammen humpeln. Es fehlt der Code für zwei der Ecken. Wenn Sie den Code lesen, können Sie sehen, wie ich das implementiert habe (indem Sie einige der CGContextAddArc-Aufrufe löschen und einige Radiuswerte im Code löschen. Der Code für alle Ecken ist vorhanden, verwenden Sie ihn also als Ausgangspunkt und löschen Sie ihn Die Teile, die Ecken erzeugen, brauchen Sie nicht. Beachten Sie, dass Sie auch Rechtecke mit 2 oder 3 abgerundeten Ecken erstellen können, wenn Sie möchten.

Der Code ist nicht perfekt, aber ich bin sicher, Sie können ihn ein wenig aufräumen. 

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

Vergessen Sie nicht, dass Sie das QuartzCore-Framework dort installieren müssen, damit dies funktioniert.

44
nevan king

Ab iOS 3.2 können Sie die Funktionalität von UIBezierPaths verwenden, um ein abgerundetes Rechteck zu erstellen, bei dem nur die von Ihnen angegebenen Ecken gerundet sind. Sie können dies als Pfad einer CAShapeLayer und als Maske für die Ansichtsebene Ihrer Ansicht verwenden:

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

Und das ist es - kein Problem mit dem manuellen Definieren von Formen in Core Graphics, kein Erstellen von Maskenbildern in Photoshop. Die Ebene muss nicht einmal ungültig gemacht werden. Das Anwenden der abgerundeten Ecke oder das Ändern auf eine neue Ecke ist so einfach wie das Definieren einer neuen UIBezierPath und das Verwenden ihrer CGPath als Pfad der Maskenebene. Der corners-Parameter der bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:-Methode ist eine Bitmaske, so dass mehrere Ecken gerundet werden können, indem sie miteinander ODER-verknüpft werden .


BEARBEITEN: Einen Schatten hinzufügen

Wenn Sie einen Schatten hinzufügen möchten, ist etwas mehr Arbeit erforderlich.

Da "imageView.layer.mask = maskLayer" eine Maske anwendet, wird außerhalb der Maske normalerweise kein Schatten angezeigt. Der Trick besteht darin, eine transparente Ansicht zu verwenden und dann der Ebene der Ansicht zwei Unterebenen (CALayers) hinzuzufügen: shadowLayer und roundedLayer. Beide müssen die UIBezierPath verwenden. Das Bild wird als Inhalt von roundedLayer hinzugefügt.

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

Ich habe diesen Code an vielen Stellen in meinem Code verwendet und er arbeitet zu 100% korrekt. Sie können jeden Rahmen ändern, indem Sie eine Eigenschaft geändert haben "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

In iOS 11 können wir jetzt nur noch einige Ecken runden

let view = UIView()

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

Stuarts Beispiel zum Abrunden bestimmter Ecken funktioniert prima. Wenn Sie mehrere Ecken wie oben links und rechts abrunden möchten, gehen Sie wie folgt vor

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

Danke für das Teilen. Hier möchte ich die Lösung zu Swift 2.0 als Referenz für dieses Problem weitergeben. (um das UIRectCorner-Protokoll zu erfüllen)

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

CALayer-Erweiterung mit Swift 3 und höher Syntax

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

Es kann verwendet werden wie:

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

es gibt eine einfachere und schnellere Antwort, die je nach Ihren Bedürfnissen funktioniert und auch mit Schatten funktioniert. Sie können maskToBounds auf der Überlagerung auf true setzen und die untergeordneten Ebenen so verschieben, dass sich 2 ihrer Ecken außerhalb der Überlagerungsgrenzen befinden und die abgerundeten Ecken auf zwei Seiten effektiv wegschneiden.

dies funktioniert natürlich nur, wenn Sie nur 2 abgerundete Ecken auf derselben Seite haben möchten und der Inhalt der Ebene gleich aussieht, wenn Sie einige Pixel von einer Seite abschneiden. Funktioniert hervorragend, wenn Balkendiagramme nur auf der Oberseite gerundet werden.

4
user1259710

Siehe diese verwandte Frage . Sie müssen Ihr eigenes Rechteck mit some abgerundeten Ecken zu einer CGPath zeichnen, die CGPath zu Ihrer CGContext hinzufügen und dann mit CGContextClip einen Clip erstellen.

Sie können auch das abgerundete Rechteck mit Alpha-Werten in ein Bild zeichnen und dann mit diesem Bild eine neue Ebene erstellen, die Sie als mask -Eigenschaft Ihrer Ebene festlegen ( siehe Apple-Dokumentation ).

3
MrMage

Bei der Rundung nur einiger Ecken wird Nice nicht mit automatischer Größenänderung oder automatischem Layout wiedergegeben.

Eine andere Option ist die Verwendung der regulären cornerRadius und das Ausblenden der Ecken, die nicht unter einer anderen Ansicht oder außerhalb der Superview-Begrenzungen gewünscht werden.

2
Rivera

Ein halbes Jahrzehnt zu spät, aber ich denke, dass die derzeitige Art und Weise, wie die Menschen dies tun, nicht zu 100% stimmt. Viele Leute hatten das Problem, dass die Verwendung der UIBezierPath + CAShapeLayer-Methode das automatische Layout beeinflusst, insbesondere wenn sie im Storyboard festgelegt ist. Da diesbezüglich keine Antworten vorhanden sind, entschied ich mich, meine eigenen hinzuzufügen.

Dies lässt sich auf einfache Weise umgehen: Zeichnen Sie die abgerundeten Ecken in der Funktion drawRect(rect: CGRect).

Wenn ich zum Beispiel für eine UIView die besten abgerundeten Ecken haben wollte, würde ich die UIView-Klasse unterteilen und diese Unterklasse dann wo immer angemessen verwenden.

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

Dies ist der beste Weg, um das Problem zu überwinden, und es dauert keine Zeit, sich daran zu gewöhnen.

2
David

Zum Hinzufügen von answer und add habe ich eine einfache, wiederverwendbare UIView in Swift erstellt. Je nach Anwendungsfall möchten Sie möglicherweise Änderungen vornehmen (vermeiden Sie das Erstellen von Objekten in jedem Layout usw.), aber ich wollte es so einfach wie möglich halten. Die Erweiterung ermöglicht es Ihnen, dies auf andere Ansichten (z. B. UIImageView) anzuwenden, wenn Sie Unterklassen nicht mögen.

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

So würden Sie es auf eine UIViewController anwenden:

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

Ich würde vorschlagen, die Maske einer Ebene zu definieren. Die Maske selbst sollte ein CAShapeLayer-Objekt mit einem dedizierten Pfad sein. Sie können die nächste UIView-Erweiterung (Swift 4.2) verwenden:

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

Wenn Sie die Antwort von Stuart zusammenfassen, können Sie die Rundungswinkelmethode folgendermaßen verwenden:

@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

Um eine Rundungsecke anzuwenden, machen Sie einfach:

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