iOS:带边框的圆角矩形颜色渗透问题

19

我正在绘制圆形头像图片,只需将cornerRadius应用于UIImageView的图层,并通过使用borderWithborderColor添加边框。代码如下:

self.layer.masksToBounds = YES;
self.layer.cornerRadius = imageDimension / 2.f;
self.layer.borderWidth = 1.f;
self.layer.borderColor = borderColor.CGColor;

这个方法很好,但是有一个微小但明显的问题,就是内容会超出边框,如下图所示:

Content bleeding outside the mask and border

有没有一种方法可以将边框向外扩展几个1/10点,或者将内容缩进比边框更多?


解决方案

感谢FelixLam,我想出了一个好的解决方案,并将其留在这里供后人参考:

@interface MMRoundImageViewWithBorder : UIView

- (id)initWithImage:(UIImage *)image borderWidth:(CGFloat)borderWidth;

@property (strong, nonatomic) UIImageView *imageView;
@property (assign, nonatomic) CGFloat borderWidth;
@property (strong, nonatomic) UIColor *borderColor;

@end

@implementation MMRoundImageViewWithBorder

- (id)initWithImage:(UIImage *)image borderWidth:(CGFloat)borderWidth {
    if (self = [super init]) {
        self.borderWidth = borderWidth;
        self.borderColor = UIColor.whiteColor;

        self.imageView = [[UIImageView alloc] initWithImage:image];
        [self addSubview:self.imageView];

        self.imageView.layer.masksToBounds = YES;
        self.layer.masksToBounds = YES;
    }
    return self;
}

- (void)setBorderColor:(UIColor *)borderColor {
    _borderColor = borderColor;
    self.backgroundColor = borderColor;
}

- (void)layoutSubviews {
    [super layoutSubviews];
    [self refreshDimensions];
}

- (void)refreshDimensions {
    self.layer.cornerRadius = CGRectGetWidth(self.bounds) / 2.f;

    self.imageView.frame = CGRectInset(self.bounds, _borderWidth, _borderWidth);
    self.imageView.layer.cornerRadius = CGRectGetWidth(self.imageView.bounds) / 2.f;
}

- (void)setBorderWidth:(CGFloat)borderWidth {
    _borderWidth = borderWidth;
    [self refreshDimensions];
}

- (void)setFrame:(CGRect)frame {
    [super setFrame:frame];
    [self refreshDimensions];
}

@end

1
你可以尝试在图像文件中添加一个透明的1像素边框,这样你的图像将会比原来宽高各增加2个像素,这应该有助于混合。 - dariaa
7个回答

14

你可以尝试使用一个具有圆形路径的CAShapelayer作为层的蒙版,而不是使用角半径。

或者,你可以将图片视图放置在一个带有边框且大一/两个像素的容器中。


1
谢谢你的建议!我选择了第二个选项,看起来很棒 :) - manmal

1

如评论中所说,您可以向图像添加透明边框,这样就可以了。 以下是一段代码(UIImageView的类别):

+ (UIImage *)imageWithImage:(UIImage *)image withTransparentBorderOfWidth:(CGFloat)width {
    CGSize size = image.size;
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(size.width + width * 2, size.height + width * 2), NO, 0.0);
    [image drawInRect:CGRectMake(width, width, size.width, size.height)];
    image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

1
这是一个次优解决方案。如果图像是从缓存中获取或刷新的,则始终需要重新生成图像。在图像周围绘制边框是渲染问题,而不是内容问题(IMO)。 - manmal
当有9种方法可以达到目标时,不要选择一种糟糕的方法。否则你的代码库很快就会腐烂。 - manmal

0

这是一个解决这个问题的UIImageView子类。

正如接受的答案所建议的那样, 它添加了一个圆角CAShapeLayer,其边框宽度和圆角半径比"所需"大1像素。它的框架从CGPoint(x: 1, y: 1)开始,并且高度比图像视图的高度大2像素。这样,它就覆盖了透过的部分。

Swift 3+ 解决方案

子类:

class BorderedRoundedImageView: UIImageView {
    let borderLayer = CALayer()
    
    var borderWidth: CGFloat!
    var borderColor: UIColor!
    
    func setUp() {
        borderLayer.borderWidth = borderWidth + 1
        borderLayer.borderColor = borderColor.cgColor
        layer.addSublayer(borderLayer)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        borderLayer.cornerRadius = layer.cornerRadius + 1
        borderLayer.frame = CGRect(x: -1, y: -1, width: frame.width + 2, height: frame.height + 2)
    }
}

使用方法:

@IBOutlet weak var imageView: UIImageView!

[...]

imageView.layer.cornerRadius = 20
imageView.borderWidth = 2
imageView.borderColor = .lightGray
imageView.setUp()

结果:

screenshot of the result


0

我的UIButton需要圆角半径,但是边缘很锯齿。将borderStyle设置为.roundedRect可以解决这个问题。

button.borderStyle = .roundedRect
button.layer.cornerRadius = 4
button.layer.borderWidth = 1
button.layer.borderColor = .red.cgColor

0

OP的解决方案过于复杂。一个更简单和更清晰的解决方案是,创建一个UIView,裁剪边界、遮罩、边框和圆角,然后将UIIamgeview作为该UIView的子视图添加进来。UIIamgeview将被父视图裁剪,您就不会遇到出血问题。


嗯,那正是我的代码所做的,或者是我忽略了什么? - manmal

-1
我曾经遇到同样的问题,想要一个能够与Storyboard良好配合的解决方案。我所做的就是将我的图像放在一个视图里,并将两者都设置为控制行为的自定义类。现在我可以完全通过Storyboard控制设计了。
我已经将代码放在GitHub上。

https://github.com/brennanMKE/CircleButton

它的作用是覆盖UIButton和常规UIView中的drawRect函数,以便它可以与许多视图一起使用。还使用包装器视图来控制边框颜色和宽度。我只需确保包装视图和超级视图之间有一些余量,并将差异用作边框宽度。超级视图的背景颜色成为边框颜色。现在,我可以在Storyboard中快速地在许多场景中进行这些更改,而无需为每个实例编写自定义代码。
- (void)drawRect:(CGRect)rect {
    // round view and superview with a border using the background color of the superview
    self.layer.cornerRadius = CGRectGetWidth(self.frame) / 2;
    self.layer.masksToBounds = YES;

    if ([self.superview isKindOfClass:[SSTCircleWrapperView class]]) {
        self.superview.layer.cornerRadius = CGRectGetWidth(self.superview.frame) / 2;
        self.superview.layer.masksToBounds = YES;
        self.superview.layer.borderColor = self.superview.backgroundColor.CGColor;
        self.superview.layer.borderWidth = (CGRectGetWidth(self.superview.frame) - CGRectGetWidth(self.frame)) / 2;
    }
}

谢谢。我的解决方案在Storyboard中不起作用(虽然我并不是很在意)。同时,在两个图层上设置shouldRasterizeYES,否则性能会大大降低。遮罩似乎非常昂贵。 - manmal
1
这是滥用绘制矩形,大部分的操作应该只在框架改变时发生。 - Colin Swelin

-1

尝试设置:

[self setClipToBounds:YES]

这不就是和 self.layer.masksToBounds = YES; 一样的吗? - Felix Lamouroux

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接