簡體   English   中英

UIView 中的兩個圓角

[英]Round two corners in UIView

不久前,我發布了一個關於將視圖的兩個角四舍五入的問題,得到了很好的回應,但在實施時遇到了問題。 這是我的 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);
}

正在調用該方法,但似乎不會影響視圖的結果。 任何想法為什么?

CACornerMask在 iOS 11 中引入,有助於在視圖層中定義左上、右上、左下、右下。 下面是使用示例。

在這里,我嘗試只對兩個頂角進行四舍五入:

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

僅供參考:


據我所知,如果您還需要屏蔽子視圖,則可以使用CALayer屏蔽。 有兩種方法可以做到這一點。 第一個更優雅,第二個是一種解決方法:-)但它也很快。 兩者都基於CALayer掩碼。 去年我在幾個項目中使用了這兩種方法,希望你能找到一些有用的東西。

解決方案1

首先,我創建了這個函數來動態生成一個帶有我需要的圓角的圖像蒙版( UIImage )。 這個函數本質上需要 5 個參數:圖像的邊界和 4 個角半徑(左上角、右上角、左下角和右下角)。


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

現在你只需要幾行代碼。 我把東西放在我的 viewController viewDidLoad方法中,因為它更快,但你也可以在你的自定義UIView使用它,例如layoutSubviews方法。



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

解決方案2

這個解決方案有點“臟”。 本質上,您可以創建一個帶有您需要的圓角(所有角)的蒙版層。 然后你應該通過角半徑的值增加遮罩層的高度。 通過這種方式,底部圓角被隱藏,您只能看到上部圓角。 我將代碼放在viewDidLoad方法中,因為它更快,但您也可以在自定義UIView使用它,例如使用layoutSubviews方法。

  

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

希望這會有所幫助。 咲!

梳理了幾個答案和評論,我發現使用UIBezierPath bezierPathWithRoundedRectCAShapeLayer是最簡單、最直接的方法。 它可能不適合非常復雜的情況,但對於偶爾的圓角,它對我來說工作得又快又順利。

我創建了一個簡化的助手,用於在蒙版中設置適當的角:

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

要使用它,只需使用適當的 UIRectCorner 枚舉調用,例如:

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

請注意,對我來說,我用它來圓化分組 UITableViewCell 中照片的角,10.0 半徑對我來說很好用,如果需要適當地更改值。

編輯:請注意以前的回答與此非常相似(鏈接)。 如果需要,您仍然可以將此答案用作附加的便利功能。


編輯:與 Swift 3 中的 UIView 擴展相同的代碼

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

要使用它, maskByRoundingCorner在任何UIView上調用maskByRoundingCorner

 view.maskByRoundingCorners([.topLeft, .bottomLeft])

我無法在對@lomanf 的回答的評論中說明這一切。 所以我將其添加為答案。

就像@lomanf 所說的那樣,您需要添加一個圖層蒙版以防止子圖層繪制到路徑邊界之外。 不過,現在做起來容易多了。 只要您的目標是 iOS 3.2 或更高版本,您就無需使用石英創建圖像並將其設置為遮罩。 您可以簡單地創建一個CAShapeLayerUIBezierPath並將其用作掩碼。

此外,在使用圖層蒙版時,請確保在添加蒙版時您正在蒙版的圖層不屬於任何圖層層次結構。 否則行為是未定義的。 如果您的視圖已經在層次結構中,您需要將其從其超視圖中刪除,屏蔽它,然后將其放回原來的位置。

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

由於 Core Animation 渲染的工作方式,遮罩是一個相對較慢的操作。 每個蒙版都需要一個額外的渲染通道。 因此,請謹慎使用口罩。

這種方法最好的部分之一是您不再需要創建自定義UIView並覆蓋drawRect: 這應該會使您的代碼更簡單,甚至更快。

我以 Nathan 為例,在UIView上創建了一個類別,以允許遵循 DRY 原則。 事不宜遲:

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

致電:

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

為了稍微擴展 PL 的答案,我重寫了該方法,因為它沒有正確舍入某些對象,例如UIButton

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

並調用它

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

如果你想在 Swift 中做到這一點,你可以使用UIView的擴展。 通過這樣做,所有子類都將能夠使用以下方法:

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

用法示例:

self.anImageView.roundCorner(.topRight, radius: 10)

擴展已接受的答案,讓我們為其添加向后兼容性。 在 iOS 11 之前,view.layer.maskedCorners 不可用。 所以我們可以這樣做

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

我們將 maskByRoundingCorners 編寫為 UIView 擴展,以提高代碼重用性。

感謝@SachinVsSachin 和@PL :) 我結合了他們的代碼以使其更好。

提供的所有解決方案都達到了目標。 但是, UIConstraints有時會UIConstraints它。

例如,底角需要倒圓。 如果對需要圓角的 UIView 設置了高度或底部間距約束,則需要將圓角的代碼片段移動到viewDidLayoutSubviews方法中。

突出顯示:

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

如果在viewDidLoad設置了此代碼,則上面的代碼片段只會viewDidLoad右上角。 因為roundedView.bounds將在約束更新UIView后發生變化。

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

根據您的需要更改“AllCorners”

貝塞爾路徑是答案,如果你需要額外的代碼,這個對我有用: 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];

一個稍微有點麻煩但相對簡單(沒有子類化、屏蔽等)的方法是擁有兩個 UIView。 兩者都帶有clipToBounds = YES 在子視圖上設置圓角,然后將其放置在父視圖中,以便裁剪您想要筆直的角。

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

不支持您想要圓角的兩個對角線的情況。

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

創建一個蒙版並將其設置在視圖的圖層上

從您的代碼開始,您可能會使用以下代碼段。

我不確定這是否是您想要的結果。 同樣值得注意的是,如果/當系統再次調用 drawRect: 時,只要求重繪矩形的一部分,這將表現得非常奇怪。 上面提到的Nevan 的方法可能是更好的方法。

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

我意識到您正試圖繞過 UITableView 的頂部兩個角,但出於某種原因,我發現最好的解決方案是使用:

self.tableView.layer.cornerRadius = 10;

以編程方式,它應該圍繞所有四個角,但由於某種原因,它只圍繞前兩個角。 **請看下面的截圖,看看我上面寫的代碼的效果。

我希望這會有所幫助!

在此處輸入圖片說明

這僅在某些設置正確時才有效:

  1. clipsToBounds 必須設置為 YES
  2. 不透明必須是 NO
  3. backgroundColor 應該是“clearColor”(我不完全確定)
  4. contentMode 必須是“UIContentModeRedraw”,因為如果不是,則不會經常調用 drawRect
  5. [super drawRect:rect] 必須在 CGContextClip 之后調用
  6. 您的視圖可能不包含任意子視圖(不確定)
  7. 一定要設置“needsDisplay:”至少一次來觸發你的drawrect

您可能必須限制到邊界。 添加行

self.clipsToBounds = YES

在代碼中的某處設置該屬性。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM