iOS 裁剪View指定的某几个角为圆角以及遇到的问题
最近遇到一个需求,label样式设置如下,并不是四个圆角,而是右上和右下设置圆角。
指定裁剪圆角代码很简单,写一个方法,需要裁剪的控件调用以下方法即可。
-(void)changeLabelStyle:(UILabel *)label{
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:label.bounds byRoundingCorners:UIRectCornerTopRight | UIRectCornerBottomRight cornerRadii:CGSizeMake(20, 20)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = label.bounds;
maskLayer.path = maskPath.CGPath;
label.layer.mask = maskLayer;
}
- (instancetype)bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(UIRectCorner)corners cornerRadii:(CGSize)cornerRadii;在这个方法中,第二个参数UIRectCorner是一个枚举类型,即你需要指定裁剪为圆角的view的角。
typedef NS_OPTIONS(NSUInteger, UIRectCorner) {
UIRectCornerTopLeft = 1 << 0, //左上角
UIRectCornerTopRight = 1 << 1, //右上角
UIRectCornerBottomLeft = 1 << 2, //左下角
UIRectCornerBottomRight = 1 << 3, //右下角
UIRectCornerAllCorners = ~0UL //四个角
};
如果你想裁剪多个不同的角,可以用"|"进行组合,传入多个即可。方法中的CAShapeLayer是一个神奇的子类,可以定制很多有趣的UI控件,具体可以参考简书里的这篇文章放肆的使用UIBezierPath和CAShapeLayer画各种图形
从结果来看符合要求,但是这篇文章的重点不在这,而是我下面要说的事情,也是我实现这个需求过程中遇到的问题,记录一下。
这个需求是要在两个页面实现的,方法和调用都一致。只是这两个页面的我创建label的方式不一样,这也才导致了问题的发生。第一个页面的我是用xib拖的label。第二个页面我是用纯代码创建的label。下面就分两种情况具体说一下。
xib创建的label
这种情形下很简单,我在xib相关联的.m文件中awakeFromNib方法中调用方法即可,运行结果也正常。
- (void)awakeFromNib {
[super awakeFromNib];
// Initialization code
[self changeLabelStyle:self.pic_tag_label1];
[self changeLabelStyle:self.pic_tag_label2];
[self changeLabelStyle:self.pic_tag_label3];
}
xib运行结果
但是第二个页面用纯代码创建的label就出了问题了。
纯代码创建的label
ps:因为当时是在项目工程中发现的问题,已经解决上传了,所以我后来写了个简单的demo,效果跟我预期的差不多。
代码如下,是创建一个tableview,自定义headerview,以下是自定义headerview代码。
- (UIView *)creatHeaderView{
UIView *topView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 240)];
UILabel *label = [[UILabel alloc]init];
label.backgroundColor = [UIColor redColor];
label.text = @"圆角测试";
label.textAlignment = NSTextAlignmentCenter;
[topView addSubview:label];
[label mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(100, 40));
make.leading.equalTo(topView.mas_leading).offset(100);
make.top.equalTo(topView.mas_top).offset(100);
}];
[self changeLabelStyle:label];
return topView;
}
运行结果
label不能正常显示,接着切换3D视图查看。
3D视图再打印label的信息,控制台显示如下
Printing description of $3:
<UILabel: 0x7f83fc413740; frame = (100 100; 100 40); text = '圆角测试'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x6080002877b0>>
(lldb)
label其实是存在的,但是不能显示,自然就联想到了调用裁剪圆角的方法,肯定是这里出了问题,在这个方法里,传进的是label控件,涉及到label的frame,所以我就打印了一下label的frame,结果如下
打印label结果再回过头来看上面的代码,我是用masonry设置完label的约束之后调用的方法,我在想,是不是masonry设置约束block中的处理是放在另一个线程中异步进行的,block还没执行完就已经走到了下面使用frame的代码,所以把方法的调用写在了设置约束的block里,但是label依然不能正常显示,label的frame打印结果依旧是上面的结果。
后来和朋友说了这事,自己也在网上查了资料,才发现:
使用masonry的实质还是调用了ios7以后的autolayout,如果要更新frame,需要调用layoutIfNeeded函数进行布局,然后所约束的控件才会按照约束条件,生成当前布局相应的frame和bounds。这样就可以利用这两个属性来进行图片圆角剪裁。而调用layoutIfNeeded的目的是让系统调用layoutSubviews方法,我们也可以直接在这个方法里获取frame,因为这时候开始layout subviews,Masonry已经计算出了真实的frame。
最后我在调用裁剪圆角方法的前面调用了layoutIfNeeded方法,label成功显示,此时打印label的frame也是正确的。
- (UIView *)creatHeaderView{
UIView *topView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 240)];
UILabel *label = [[UILabel alloc]init];
label.backgroundColor = [UIColor redColor];
label.text = @"圆角测试";
label.textAlignment = NSTextAlignmentCenter;
[topView addSubview:label];
[label mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(100, 40));
make.leading.equalTo(topView.mas_leading).offset(100);
make.top.equalTo(topView.mas_top).offset(100);
}];
[label layoutIfNeeded];
[self changeLabelStyle:label];
return topView;
}
label正常显示 此时label打印结果
附上关于autolayout更新几个方法的区别:
- setNeedsLayout:告知页面需要更新,但是不会立刻开始更新。执行后会立刻调用layoutSubviews。
- layoutIfNeeded:告知页面布局立刻更新。所以一般都会和setNeedsLayout一起使用。如果希望立刻生成新的frame需要调用此方法,利用这点,动画可以在更新布局后直接使用这个方法让动画生效。
- layoutSubviews:系统重写布局
- setNeedsUpdateConstraints:告知需要更新约束,但是不会立刻开始
- updateConstraintsIfNeeded:告知立刻更新约束
- updateConstraints:系统更新约束