iOS 圓角,最後一次研究它了,真的

對於目前手機的性能來講,對於少許圓角設置帶來的離屏渲染問題,帶來的卡頓問題已經並非那麼的明顯了,可是對於一些類型的app來講,項目中會有大量的圓角設置,同時又對性能要求比較高的話,那麼避免圓角設置帶來的性能問題,仍是有必要解決的。git

iOS設置圓角方法以及指定位置設圓角
iOS圓角設置的幾種方法的對比以及性能檢測github

上面兩篇文章中介紹了iOS常見的設置圓角的方法,以及對應方法性能的檢測😊。c#

然而在實際項目中仍然有其餘問題存在,也就是前面的總結仍有不足,下面提出兩個遇到的問題bash

針對Masonry佈局的控件,如何進行圓角設置?

其實解決方案很是簡單,調用layoutIfNeeded 函數進行佈局,而後所約束的控件纔會按照約束條件,生成當前佈局相應的framebounds,那麼就能夠進行各類樣式的圓角設置app

針對四個不一樣的角,設置不一樣的半徑,如何操做?

方法

實際上與以前文章裏所描述的一種方法混合圖層一個道理。在所需切圓角的控件上加一層,利用貝塞爾切割出不一樣角所須要的圓角半徑函數

思路

如上圖所示,標記了幾個點,進行畫圓弧以及連線,具體代碼以下工具

[bezierPath moveToPoint:self.hLeftUpPoint];
    [bezierPath addLineToPoint:self.hRightUpPoint];
    [bezierPath addArcWithCenter:self.centerRightUp radius:self.radius_TopRight ?: self.radius startAngle:(CGFloat)(M_PI * 3 / 2) endAngle:(CGFloat)(M_PI * 2) clockwise: true];
    [bezierPath addLineToPoint:self.vRightDownPoint];
    [bezierPath addArcWithCenter:self.centerRightDown radius:self.radius_BottomRight ?: self.radius startAngle: 0 endAngle: (CGFloat)(M_PI / 2) clockwise: true];
    [bezierPath addLineToPoint:self.hLeftDownPoint];
    [bezierPath addArcWithCenter:self.centerLeftDown radius:self.radius_BottomLeft ?: self.radius startAngle: (CGFloat)(M_PI / 2) endAngle: (CGFloat)(M_PI) clockwise: true];
    [bezierPath addLineToPoint:self.vLeftUpPoint];
    [bezierPath addArcWithCenter:self.centerLeftUp radius:self.radius_TopLeft ?: self.radius startAngle: (CGFloat)(M_PI) endAngle: (CGFloat)(M_PI * 3 / 2) clockwise: true];
    [bezierPath addLineToPoint:self.hLeftUpPoint];
    [bezierPath closePath];
    
    [bezierPath moveToPoint:CGPointZero];
    [bezierPath addLineToPoint:CGPointMake(0, self.rectSize.height)];
    [bezierPath addLineToPoint:CGPointMake(self.rectSize.width, self.rectSize.height)];
    [bezierPath addLineToPoint:CGPointMake(self.rectSize.width, 0)];
    [bezierPath addLineToPoint:CGPointZero];
    [bezierPath closePath];
複製代碼

上面的代碼具體是切割圓角,具體圓角半徑與對應點的位置有關,合理的利用上圖所描述的點的座標,能夠切割出不一樣的弧度,同時利用位移枚舉UIRectCorner可實現任意一個或多個角的切割。佈局

具體代碼

第一步: 新增了一個UIVIew+CornerRadius的分類,.h文件裏聲明瞭兩種切割圓角的方法。post

@interface UIView (CornerRadius)

/// 繪製裁剪圓角,可選任意角進行切割
/// @param radius 圓角半徑
/// @param fillColor 填充色
/// @param type 裁剪角
- (void)drawCircularBeadImageWithRadius:(float)radius fillColor:(UIColor *)fillColor CornerStyle:(UIRectCorner)type;

/// 繪製裁剪圓角,可選任意角進行切割,且每一個角可選切割任意半徑
/// @param radius_TL 左上角半徑
/// @param radius_TR 右上角半徑
/// @param radius_BL 左下角半徑
/// @param radius_BR 右下角半徑
/// @param fillColor 填充色
- (void)drawCircularBeadImageWithRadius_TL:(float)radius_TL
                                 radius_TR:(float)radius_TR
                                 radius_BL:(float)radius_BL
                                 radius_BR:(float)radius_BR
                                 fillColor:(UIColor *)fillColor;
@end
複製代碼

具體的實現:性能

@implementation UIView (CornerRadius)

- (void)drawCircularBeadImageWithRadius:(float)radius fillColor:(UIColor *)fillColor CornerStyle:(UIRectCorner)type {
    UIImageView *imgView = [[UIImageView alloc] init];
    [self addSubview:imgView];
    [imgView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.top.width.height.equalTo(self);
    }];
    [self layoutIfNeeded];
    
    imgView.image = [ZBCornerRadiusTool drawAntiRoundedCornerImageWithRadius:radius rectSize:imgView.frame.size fillColor:fillColor cornerStyle:type];
}

- (void)drawCircularBeadImageWithRadius_TL:(float)radius_TL
                             radius_TR:(float)radius_TR
                             radius_BL:(float)radius_BL
                             radius_BR:(float)radius_BR
                             fillColor:(UIColor *)fillColor {
    UIImageView *imgView = [[UIImageView alloc] init];
    [self addSubview:imgView];
    [imgView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.top.width.height.equalTo(self);
    }];
    [self layoutIfNeeded];
    
    imgView.image = [ZBCornerRadiusTool drawAntiRoundedCornerWithRadius_TL:radius_TL radius_TR:radius_TR radius_BL:radius_BL radius_BR:radius_BR rectSize:imgView.frame.size fillColor:fillColor];
}

@end
複製代碼

從上面代碼能夠看出,在這裏咱們在須要切割圓角的控件上,加了一層UIImageView,同時這裏利用Masonry對新增的圖層進行了約束,最重要的一句代碼[self layoutIfNeeded];千萬不能少,這句話,就解決了上面所說的第一個問題。

這裏代碼均是針對利用Masonry佈局約束的控件去實現圓角,若是是利用frame的也很是方面,直接利用控件的size即可。筆者只提供一下思路

第二步: 添加兩個工具類,先看ZBCornerRadiusTool

/// 繪製裁剪圓角後圖片
/// @param radius 圓角
/// @param rectSize 視圖尺寸
/// @param fillColor 填充色
/// @param cornerStyle 圓角位置
+ (UIImage *)drawAntiRoundedCornerImageWithRadius:(float)radius rectSize:(CGSize)rectSize fillColor:(UIColor *)fillColor cornerStyle:(UIRectCorner)cornerStyle {
    UIGraphicsBeginImageContextWithOptions(rectSize, false, [UIScreen mainScreen].scale);
    CGContextRef contextRef = UIGraphicsGetCurrentContext();
    UIBezierPath *bezierPath = [UIBezierPath bezierPath];
    [ZBCornerRadiusTool configPathWithBezierPath:&bezierPath Radius:radius rectSize:rectSize cornerStyle:cornerStyle fillColor:fillColor];
    CGContextDrawPath(contextRef, kCGPathFillStroke);
    UIImage *antiRoundedCornerImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return antiRoundedCornerImage;
}


/// 繪製裁剪圓角後圖片
/// @param radius_TL 左上角半徑
/// @param radius_TR 右上角半徑
/// @param radius_BL 左下角半徑
/// @param radius_BR 右下角半徑
/// @param rectSize 視圖尺寸
/// @param fillColor 填充色
+ (UIImage *)drawAntiRoundedCornerWithRadius_TL:(float)radius_TL
                                      radius_TR:(float)radius_TR
                                      radius_BL:(float)radius_BL
                                      radius_BR:(float)radius_BR
                                       rectSize:(CGSize)rectSize
                                      fillColor:(UIColor *)fillColor {
    UIGraphicsBeginImageContextWithOptions(rectSize, false, [UIScreen mainScreen].scale);
    CGContextRef contextRef = UIGraphicsGetCurrentContext();
    UIBezierPath *bezierPath = [UIBezierPath bezierPath];
    [ZBCornerRadiusTool configPathWithBezierPath:&bezierPath radius_TL:radius_TL radius_TR:radius_TR radius_BL:radius_BL radius_BR:radius_BR rectSize:rectSize fillColor:fillColor];
    CGContextDrawPath(contextRef, kCGPathFillStroke);
    UIImage *antiRoundedCornerImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return antiRoundedCornerImage;
}
複製代碼

關鍵方法:

// 對任意一個或多個角設置圓角
+ (void)configPathWithBezierPath:(UIBezierPath **)bezier Radius:(float)radius rectSize:(CGSize)rectSize cornerStyle:(UIRectCorner)cornerStyle fillColor:(UIColor *)fillColor {
    UIBezierPath *bezierPath = *bezier;
    BezierPathTool *tool = [[BezierPathTool alloc]initWithRadius:radius rectSize:rectSize fillColor:fillColor];
    
    if (cornerStyle == UIRectCornerAllCorners) {
        [tool configAllCornerPoint];
    }else {
        if (cornerStyle == UIRectCornerTopLeft) {
            [tool configTopLeftPoint];
        }
        if (cornerStyle == UIRectCornerTopRight) {
            [tool configTopRightPoint];
        }
        if (cornerStyle == UIRectCornerBottomLeft) {
            [tool configBottomLeftPoint];
        }
        if (cornerStyle == UIRectCornerBottomRight) {
            [tool configBottomRightPoint];
        }
    }
    bezierPath = [tool configCornerBezierPath:bezierPath];
}

// 對四個角分別設置圓角,0即爲不進行圓角設置
+ (void)configPathWithBezierPath:(UIBezierPath **)bezier
                       radius_TL:(float)radius_TL
                       radius_TR:(float)radius_TR
                       radius_BL:(float)radius_BL
                       radius_BR:(float)radius_BR
                        rectSize:(CGSize)rectSize
                       fillColor:(UIColor *)fillColor {
    UIBezierPath *bezierPath = *bezier;
    BezierPathTool *tool = [[BezierPathTool alloc]initWithRadius_TopLeft:radius_TL radius_TopRight:radius_TR radius_BottomLeft:radius_BL radius_BottomRight:radius_BR rectSize:rectSize fillColor:fillColor];
    bezierPath = [tool configCornerBezierPath:bezierPath];
}
複製代碼

最後一步: OK,最後一步的實現都寫在了BezierPathTool類裏,對應兩個初始化方法,分別有不一樣的初始化

- (instancetype)initWithRadius:(float)radius rectSize:(CGSize)rectSize fillColor:(UIColor *)fillColor {
    if (self = [super init]) {
        _radius = radius;
        _rectSize = rectSize;
        _fillColor = fillColor;
        [self configData]; // 默認每一個點都是在四個拐角
    }
    return self;
}

- (instancetype)initWithRadius_TopLeft:(float)radius_TL
                       radius_TopRight:(float)radius_TR
                     radius_BottomLeft:(float)radius_BL
                    radius_BottomRight:(float)radius_BR
                              rectSize:(CGSize)rectSize
                             fillColor:(UIColor *)fillColor {
    if (self = [super init]) {
        _radius_TopLeft = radius_TL;
        _radius_TopRight = radius_TR;
        _radius_BottomLeft = radius_BL;
        _radius_BottomRight = radius_BR;
        _rectSize = rectSize;
        _fillColor = fillColor;
        [self configCornerPointData];
    }
    return self;
}

- (void)configData {
    _hLeftUpPoint = CGPointMake(0, 0);
    _hRightUpPoint = CGPointMake(_rectSize.width, 0);
    _hLeftDownPoint = CGPointMake(0, _rectSize.height);
    
    _vLeftUpPoint = CGPointMake(0, 0);
    _vRightDownPoint = CGPointMake(_rectSize.width, _rectSize.height);
    
    _centerLeftUp = CGPointMake(0, 0);
    _centerRightUp = CGPointMake(_rectSize.width, 0);
    _centerLeftDown = CGPointMake(0, _rectSize.height);
    _centerRightDown = CGPointMake(_rectSize.width, _rectSize.height);
}

- (void)configCornerPointData {
    [self configTopLeftPoint];
    [self configTopRightPoint];
    [self configBottomLeftPoint];
    [self configBottomRightPoint];
}
複製代碼

對於第一種初始化,對於須要標記的點的默認座標分別對應四個角,目的是爲了方即可以對任意一個或多個角進行切割,以下代碼

//四個角均切割
- (void)configAllCornerPoint {
    _hLeftUpPoint = CGPointMake(_radius, 0);//(10,0)
    _hRightUpPoint = CGPointMake(_rectSize.width - _radius, 0);//(30,0)
    _hLeftDownPoint = CGPointMake(_radius, _rectSize.height);//(10,40)
    
    _vLeftUpPoint = CGPointMake(0, _radius);//(0,10)
    _vRightDownPoint = CGPointMake(_rectSize.width, _rectSize.height - _radius);//(40,30)
    
    _centerLeftUp = CGPointMake(_radius, _radius);//(10,10)
    _centerRightUp = CGPointMake(_rectSize.width - _radius, _radius);//(30,10)
    _centerLeftDown = CGPointMake(_radius, _rectSize.height - _radius);//(10,30)
    _centerRightDown = CGPointMake(_rectSize.width - _radius, _rectSize.height - _radius);//(30,30)
}

//左上角圓角設置
- (void)configTopLeftPoint {
    float radius = _radius_TopLeft ?: _radius;
    _hLeftUpPoint = CGPointMake(radius, 0);//(10,0)
    _vLeftUpPoint = CGPointMake(0, radius);//(0,10)
    _centerLeftUp = CGPointMake(radius, radius);//(10,10)
}
//右上角圓角設置
- (void)configTopRightPoint {
    float radius = _radius_TopRight ?: _radius;
    _hRightUpPoint = CGPointMake(_rectSize.width - radius, 0);//(30,0)
    _centerRightUp = CGPointMake(_rectSize.width - radius, radius);//(30,10)
}
////左下角圓角設置
- (void)configBottomLeftPoint {
    float radius = _radius_BottomLeft ?: _radius;
    _hLeftDownPoint = CGPointMake(radius, _rectSize.height);//(10,40)
    _centerLeftDown = CGPointMake(radius, _rectSize.height - radius);//(10,30)
}
//右下角圓角設置
- (void)configBottomRightPoint {
    float radius = _radius_BottomRight ?: _radius;
    _vRightDownPoint = CGPointMake(_rectSize.width, _rectSize.height - radius);//(40,30)
    _centerRightDown = CGPointMake(_rectSize.width - radius, _rectSize.height - radius);//(30,30)
}
複製代碼

嗯,最後就是調用貝塞爾曲線方法進行設置就行,代碼在最上面已經給出,這裏就不過多贅述了。

下面是分別對UIImageViewUIButton的切割效果圖,也不會產生離屏渲染

上面描述,若有不正確之處,但願你們指出,謝謝

問題拋出

一個好的封裝,不須要任何依賴或者說須要減小對外界的依賴,那麼問題就來了,上面所封裝的依然依賴於控件的約束或者是frame

思考了好久,這個問題筆者依然沒有很好的解決方案,若是有好的方法,但願你們指出,再次感謝

demo傳送門

相關文章
相關標籤/搜索