web-dev-qa-db-de.com

Runde zwei Ecken in UIView

Vor einiger Zeit habe ich eine Frage zu Abrunden von nur zwei Ecken einer Ansicht gepostet und eine großartige Antwort erhalten, aber ich habe Probleme bei der Implementierung. Hier ist meine drawRect: Methode:

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

Die Methode wird aufgerufen, scheint jedoch das Ergebnis der Ansicht nicht zu beeinflussen. Irgendwelche Ideen warum?

76
Jumhyn

CACornerMask In iOS 11 eingeführt, mit dessen Hilfe die Ebenen oben links, oben rechts, unten links und unten rechts definiert werden können. Unten finden Sie ein zu verwendendes Beispiel.

Hier versuche ich nur zwei obere Ecken abzurunden:

myView.clipsToBounds = true
myView.layer.cornerRadius = 10
myView.layer.maskedCorners = [.layerMinXMinYCorner,.layerMaxXMinYCorner]

Danke im Voraus.

Zu Ihrer Information Ref: enter image description here

70
SachinVsSachin


Wenn Sie die Unteransichten auch maskieren müssen, können Sie meines Wissens CALayer maskieren. Hierfür gibt es zwei Möglichkeiten. Das erste ist etwas eleganter, das zweite ist eine Umgehung :-), aber es ist auch schnell. Beide basieren auf der CALayer Maskierung. Ich habe letztes Jahr beide Methoden in ein paar Projekten angewendet, dann hoffe ich, dass Sie etwas Nützliches finden können.

Lösung 1

Zunächst habe ich diese Funktion erstellt, um im laufenden Betrieb eine Bildmaske (UIImage) mit der abgerundeten Ecke zu generieren, die ich benötige. Diese Funktion benötigt im Wesentlichen 5 Parameter: die Bildgrenzen und 4 Eckenradien (links oben, rechts oben, links unten und rechts unten).


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

Jetzt brauchen Sie nur noch wenige Codezeilen. Ich habe Sachen in meine viewController viewDidLoad -Methode eingefügt, weil es schneller ist, aber Sie können es auch in Ihrer benutzerdefinierten UIView -Methode verwenden, im Beispiel mit der layoutSubviews -Methode.



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

Lösung 2

Diese Lösung ist etwas "schmutziger". Grundsätzlich können Sie eine Maskenebene mit der abgerundeten Ecke erstellen, die Sie benötigen (alle Ecken). Dann sollten Sie die Höhe der Maskenebene um den Wert des Eckenradius erhöhen. Auf diese Weise werden die unteren abgerundeten Ecken ausgeblendet und Sie können nur die obere abgerundete Ecke sehen. Ich habe den Code nur in die viewDidLoad -Methode eingefügt, weil er schneller ist, aber Sie können ihn auch in Ihrer benutzerdefinierten UIView -Methode mit der layoutSubviews -Methode im Beispiel verwenden.

  

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

Hoffe das hilft. Ciao!

72
lomanf

Als ich die wenigen Antworten und Kommentare durchkämmte, fand ich heraus, dass UIBezierPath bezierPathWithRoundedRect und CAShapeLayer der einfachste und direkteste Weg. Es ist vielleicht nicht für sehr komplexe Fälle geeignet, aber für gelegentliches Abrunden von Ecken funktioniert es für mich schnell und reibungslos.

Ich habe einen vereinfachten Helfer erstellt, der die entsprechende Ecke in der Maske festlegt:

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

Rufen Sie dazu einfach mit der entsprechenden UIRectCorner-Enumeration auf, z.

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

Bitte beachten Sie, dass ich es verwende, um Ecken von Fotos in einer gruppierten UITableViewCell abzurunden. Der Radius 10.0 funktioniert für mich, wenn Sie den Wert nur entsprechend ändern müssen.

BEARBEITEN: Beachten Sie einfach eine zuvor sehr ähnlich wie diese ( Link ) beantwortet. Sie können diese Antwort bei Bedarf weiterhin als zusätzliche Komfortfunktion verwenden.


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

Um es zu benutzen, rufen Sie einfach maskByRoundingCorner auf einem beliebigen UIView auf:

view.maskByRoundingCorners([.topLeft, .bottomLeft])
68
P.L.

Ich konnte das nicht alles in einen Kommentar zu @ lomanfs Antwort einfügen. Also füge ich es als Antwort hinzu.

Wie @lomanf sagte, müssen Sie eine Ebenenmaske hinzufügen, um zu verhindern, dass Unterebenen außerhalb der Grenzen Ihres Pfads gezeichnet werden. Es ist jetzt jedoch viel einfacher. Solange Sie auf iOS 3.2 oder höher abzielen, müssen Sie kein Bild mit Quarz erstellen und es als Maske festlegen. Sie können einfach ein CAShapeLayer mit einem UIBezierPath erstellen und dieses als Maske verwenden.

Wenn Sie Ebenenmasken verwenden, stellen Sie außerdem sicher, dass die Ebene, die Sie maskieren, nicht Teil einer Ebenenhierarchie ist, wenn Sie die Maske hinzufügen. Ansonsten ist das Verhalten undefiniert. Befindet sich Ihre Ansicht bereits in der Hierarchie, müssen Sie sie aus der Übersicht entfernen, maskieren und wieder an ihren ursprünglichen Platz zurückversetzen.

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

Aufgrund der Funktionsweise des Core Animation-Renderings ist das Maskieren ein relativ langsamer Vorgang. Für jede Maske ist ein zusätzlicher Rendering-Pass erforderlich. Verwenden Sie deshalb nur sparsam Masken.

Das Beste an diesem Ansatz ist, dass Sie kein benutzerdefiniertes UIView mehr erstellen und drawRect: Überschreiben müssen. Dies sollte Ihren Code einfacher und vielleicht sogar noch schneller machen.

56
Nathan Eror

Ich habe Nathans Beispiel genommen und eine Kategorie für UIView erstellt, damit sich jemand an die DRY) - Prinzipien halten kann.

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

Anrufen:

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

Um die Antwort von P.L ein wenig zu erweitern, habe ich die Methode so umgeschrieben, dass bestimmte Objekte wie UIButton nicht richtig gerundet wurden

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

Und nenn es vorbei

[self setMaskTo:self.continueButton byRoundingCorners:UIRectCornerBottomLeft|UIRectCornerBottomRight withCornerRadii:CGSizeMake(3.0, 3.0)];
24
Josh Valdivieso

Wenn Sie es in Swift verwenden möchten, können Sie eine Erweiterung von UIView verwenden. Auf diese Weise können alle Unterklassen die folgende Methode verwenden:

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

Anwendungsbeispiel:

self.anImageView.roundCorner(.topRight, radius: 10)
11
Kevin Delord

Alle angebotenen Lösungen erreichen das Ziel. Aber UIConstraints kann dies manchmal in die Luft jagen.

Beispielsweise müssen die unteren Ecken abgerundet sein. Wenn für die UIView, die abgerundet werden soll, die Einschränkung für den Höhen- oder Bodenabstand festgelegt ist, müssen die Codeausschnitte, die die Ecken abrunden, auf die viewDidLayoutSubviews -Methode verschoben werden.

Hervorhebung:

UIBezierPath *maskPath = [UIBezierPath
    bezierPathWithRoundedRect:roundedView.bounds byRoundingCorners:
    (UIRectCornerTopRight | UIRectCornerBottomRight) cornerRadii:CGSizeMake(16.0, 16.0)];

Das obige Code-Snippet rundet die obere rechte Ecke nur ab, wenn dieser Code in viewDidLoad gesetzt ist. Weil roundedView.bounds wird sich ändern, nachdem die Einschränkungen UIView aktualisiert haben.

2
hasan
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)];

ändern Sie "AllCorners" entsprechend Ihren Anforderungen.

2
Umang

Beginnend mit Ihrem Code könnten Sie mit etwas wie dem folgenden Snippet gehen.

Ich bin mir nicht sicher, ob dies das gewünschte Ergebnis ist. Es ist auch erwähnenswert, dass, wenn/wenn das System drawRect aufruft: erneut gefragt wird, ob nur ein Teil des Rect neu gezeichnet werden soll, sich dies sehr seltsam verhalten wird. Nevans Ansatz , wie oben erwähnt, ist möglicherweise der bessere Weg.

 // 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);    
}
1
Obliquely

Ein leicht hackiger, aber relativ einfacher Weg (keine Unterklassen, Maskierung usw.), um dies zu erreichen, besteht darin, zwei UIViews zu haben. Beide mit clipToBounds = YES. Stellen Sie in der untergeordneten Ansicht abgerundete Ecken ein und positionieren Sie sie dann in der übergeordneten Ansicht, sodass die Ecken, die Sie gerade haben möchten, abgeschnitten werden.

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

Unterstützt nicht den Fall, dass zwei diagonal gegenüberliegende Ecken abgerundet werden sollen.

1
William Denniss

UIBezierPath-Lösung.

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

Der Bezier-Pfad ist die Antwort, wenn Sie zusätzlichen Code benötigen, hat dieser für mich funktioniert: 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];
1

Erstellen Sie eine Maske und legen Sie sie auf der Ebene der Ansicht fest

1
Steve Riggins

Lassen Sie uns die akzeptierte Antwort erweitern und die Abwärtskompatibilität hinzufügen. Vor iOS 11 war view.layer.maskedCorners nicht verfügbar. So können wir das machen

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

Wir haben maskByRoundingCorners als UIView-Erweiterung geschrieben, um die Wiederverwendung von Code zu verbessern.

Credits an @SachinVsSachin und @ P.L :) Ich habe ihre Codes kombiniert, um es besser zu machen.

1
Rizwan Ahmed

Mir ist klar, dass Sie versuchen, die beiden oberen Ecken eines UITableView abzurunden, aber aus irgendeinem Grund habe ich festgestellt, dass die beste Lösung darin besteht, Folgendes zu verwenden:

self.tableView.layer.cornerRadius = 10;

Programmatisch sollte es alle vier Ecken abrunden, aber aus irgendeinem Grund werden nur die beiden oberen Ecken abgerundet. ** Bitte sehen Sie sich den Screenshot unten an, um den Effekt des Codes zu sehen, den ich oben geschrieben habe.

Ich hoffe das hilft!

enter image description here

0
Adam Cooper

Dies kann nur funktionieren, wenn einige Dinge richtig eingestellt sind:

  1. clipsToBounds muss auf YES gesetzt sein
  2. undurchsichtig muss NEIN sein
  3. backgroundColor sollte "clearColor" sein (da bin ich mir nicht ganz sicher)
  4. contentMode muss "UIContentModeRedraw" sein, da drawRect sonst nicht oft aufgerufen wird
  5. [super drawRect: rect] muss nach dem CGContextClip aufgerufen werden
  6. Ihre Ansicht enthält möglicherweise keine willkürlichen Unteransichten (da bin ich mir nicht sicher)
  7. Stellen Sie sicher, dass "needsDisplay:" mindestens einmal eingestellt ist, um Ihr Drawrect auszulösen
0
Dunkelstern