Niedawno opublikowałem pytanie dotyczące zaokrąglania tylko dwóch rogów widoku i otrzymałem świetną odpowiedź, ale mam problemy z jej wdrożeniem. Oto moja metoda 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);
}
Metoda jest wywoływana, ale nie wydaje się wpływać na wynik widoku. Jakieś pomysły, dlaczego?
ios
objective-c
iphone
rounded-corners
Jumhyn
źródło
źródło
if #available(iOS 11.0, *) { }
clipsToBounds
siętrue
nie jest konieczne. Możesz zamaskować rogi i dodać cień do warstwy, gdyclipsToBounds
jestfalse
. Przydał się dla mnie.o ile wiem, jeśli chcesz również zamaskować podwidoki, możesz użyć
CALayer
maskowania. Można to zrobić na dwa sposoby. Pierwsza jest nieco bardziej elegancka, druga to obejście :-), ale jest też szybka. Obie opierają się naCALayer
maskowaniu. Użyłem obu metod w kilku projektach w zeszłym roku i mam nadzieję, że znajdziesz coś pożytecznego.Rozwiązanie 1
Przede wszystkim utworzyłem tę funkcję, aby wygenerować maskę obrazu w locie (
UIImage
) z zaokrąglonym rogiem, którego potrzebuję. Ta funkcja zasadniczo wymaga 5 parametrów: granic obrazu i 4 promień narożnika (lewy górny, prawy górny, lewy dolny i prawy dolny).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; }
Teraz potrzebujesz tylko kilku wierszy kodu. I umieścić rzeczy w moim viewController
viewDidLoad
metody, ponieważ jest to szybsze, ale można go używać również w zwyczajuUIView
zlayoutSubviews
metody opisanej w przykładzie.- (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]; }
Rozwiązanie 2
To rozwiązanie jest nieco bardziej „brudne”. Zasadniczo możesz utworzyć warstwę maski z zaokrąglonym rogiem, którego potrzebujesz (wszystkie rogi). Następnie należy zwiększyć wysokość warstwy maski o wartość promienia narożnika. W ten sposób dolne zaokrąglone rogi są ukryte i widać tylko górny zaokrąglony róg. Umieściłem kod tylko w
viewDidLoad
metodzie, ponieważ jest szybszy, ale możesz go użyć również w swoim niestandardowymUIView
zlayoutSubviews
metodą z przykładu.- (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]; }
Mam nadzieję że to pomoże. Cześć!
źródło
Czesanie przez kilka odpowiedzi i komentarze, I okazało się, że za pomocą
UIBezierPath bezierPathWithRoundedRect
iCAShapeLayer
najprostszym i najbardziej prosty sposób. To może nie być odpowiednie dla bardzo skomplikowanych przypadków, ale w przypadku sporadycznego zaokrąglania rogów działa szybko i płynnie.Stworzyłem uproszczonego pomocnika, który ustawia odpowiedni róg w masce:
-(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; }
Aby z niego skorzystać, wystarczy zadzwonić z odpowiednim wyliczeniem UIRectCorner, np .:
[self setMaskTo:self.photoView byRoundingCorners:UIRectCornerTopLeft|UIRectCornerBottomLeft];
Zwróć uwagę, że dla mnie używam go do zaokrąglania rogów zdjęć w zgrupowanym UITableViewCell, promień 10.0 działa dobrze, jeśli trzeba tylko odpowiednio zmienić wartość.
EDYTUJ: po prostu zwróć uwagę na poprzednią odpowiedź bardzo podobną do tej ( link ). W razie potrzeby możesz nadal używać tej odpowiedzi jako dodatkowej funkcji zapewniającej wygodę.
EDYCJA: ten sam kod co rozszerzenie UIView w Swift 3
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 } }
Aby z niego skorzystać, wystarczy zadzwonić
maskByRoundingCorner
do dowolnegoUIView
:źródło
Nie mogłem tego wszystkiego zmieścić w komentarzu do odpowiedzi @ lomanf. Więc dodaję to jako odpowiedź.
Jak powiedział @lomanf, musisz dodać maskę warstwy, aby zapobiec rysowaniu podwarstw poza granicami ścieżki. Jednak teraz jest to o wiele łatwiejsze. Jeśli celujesz w system iOS 3.2 lub nowszy, nie musisz tworzyć obrazu z kwarcem i ustawiać go jako maski. Możesz po prostu utworzyć
CAShapeLayer
z aUIBezierPath
i użyć tego jako maski.Jeśli używasz masek warstw, po dodaniu maski upewnij się, że maskowana warstwa nie jest częścią żadnej hierarchii warstw. W przeciwnym razie zachowanie jest niezdefiniowane. Jeśli Twój widok jest już w hierarchii, musisz usunąć go z jego nadzoru, zamaskować, a następnie umieścić z powrotem na miejscu.
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];
Ze względu na sposób działania renderowania Core Animation maskowanie jest operacją stosunkowo powolną. Każda maska wymaga dodatkowego przejścia renderowania. Dlatego oszczędnie używaj masek.
Jedną z najlepszych części tego podejścia jest to, że nie musisz już tworzyć niestandardowego
UIView
i zastępowaniadrawRect:
. To powinno uczynić twój kod prostszym, a może nawet szybszym.źródło
Wziąłem przykład Nathana i utworzyłem kategorię,
UIView
aby umożliwić przestrzeganie zasad DRY. Bez zbędnych ceregieli: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
Zadzwonić:
[myView addRoundedCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight withRadii:CGSizeMake(20.0f, 20.0f)];
źródło
Aby rozszerzyć nieco odpowiedź PL, przepisałem metodę tak, aby nie zaokrąglała pewnych obiektów, takich jak
UIButton
poprawnie- (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; }
I zadzwoń do tego
[self setMaskTo:self.continueButton byRoundingCorners:UIRectCornerBottomLeft|UIRectCornerBottomRight withCornerRadii:CGSizeMake(3.0, 3.0)];
źródło
Jeśli chcesz to zrobić w Swift, możesz użyć rozszerzenia
UIView
. W ten sposób wszystkie podklasy będą mogły używać następującej metody: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 } }
Przykładowe użycie:
self.anImageView.roundCorner(.topRight, radius: 10)
źródło
Rozszerzając zaakceptowaną odpowiedź, dodajmy do niej kompatybilność wsteczną. Przed iOS 11, view.layer.maskedCorners nie jest dostępny. Więc możemy to zrobić
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 } }
Napisaliśmy maskByRoundingCorners jako rozszerzenie UIView, aby usprawniało ponowne wykorzystanie kodu.
Kredyty dla @SachinVsSachin i @PL :) Połączyłem ich kody, aby było lepiej.
źródło
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)];
zmień „AllCorners” zgodnie z potrzebami.
źródło
Wszystkie dostarczone rozwiązania osiągają cel. Ale
UIConstraints
czasami może to wysadzić.Podświetlanie:
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:roundedView.bounds byRoundingCorners: (UIRectCornerTopRight | UIRectCornerBottomRight) cornerRadii:CGSizeMake(16.0, 16.0)];
Powyższy fragment kodu zaokrągla prawy górny róg tylko wtedy, gdy ten kod jest ustawiony w
viewDidLoad
. PonieważroundedView.bounds
zmieni się po zaktualizowaniu wiązańUIView
.źródło
Utwórz maskę i ustaw ją na warstwie widoku
źródło
Zaczynając od kodu, możesz użyć czegoś takiego jak poniższy fragment.
Nie jestem pewien, czy o tego rodzaju wynikach chodzi. Warto również zauważyć, że jeśli / kiedy system wywoła drawRect: ponownie, prosząc tylko o przerysowanie tylko części rekta, zachowa się to bardzo dziwnie. Podejście Nevana , opisane powyżej, może być lepszym sposobem.
// 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); }
źródło
-drawRect:
wpływa tylko na widok, który rysujesz. Jego podglądy są rysowane niezależnie po zakończeniu rysowania.Nieco hackerskim, ale stosunkowo prostym (bez podklas, maskowania itp.) Sposobem jest utworzenie dwóch UIViews. Obie z
clipToBounds = YES
. Ustaw zaokrąglone rogi w widoku podrzędnym, a następnie umieść go w widoku nadrzędnym, tak aby rogi, które chcesz, aby były proste, zostały przycięte.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];
Nie obsługuje przypadku, w którym chcesz zaokrąglić dwa przeciwległe rogi po przekątnej.
źródło
Ścieżka Beziera jest odpowiedzią, jeśli potrzebujesz dodatkowego kodu, ten działał dla mnie: 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];
źródło
Rozwiązanie 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]; }
źródło
Może to działać tylko wtedy, gdy niektóre rzeczy są ustawione poprawnie:
źródło
Zdaję sobie sprawę, że próbujesz zaokrąglić dwa górne rogi UITableView, ale z jakiegoś powodu stwierdziłem, że najlepszym rozwiązaniem jest użycie:
self.tableView.layer.cornerRadius = 10;
Programowo powinien zaokrąglać wszystkie cztery rogi, ale z jakiegoś powodu zaokrągla tylko dwa górne rogi. ** Proszę zobaczyć zrzut ekranu poniżej, aby zobaczyć efekt kodu, który napisałem powyżej.
Mam nadzieję, że to pomoże!
źródło
UITableView
musi istnieć kod w innym miejscu, który powoduje, żeUITableView
nie ma zaokrąglonych rogów na dole, lub może nawet aUIView
lub,UILabel
który zakrywa dolną częśćUITableView
.Prawdopodobnie musisz przyciąć do granic. Dodaj linię
self.clipsToBounds = YES
gdzieś w kodzie, aby ustawić tę właściwość.
źródło