IOS: закругленный прямоугольник с цветовым оттенком границы

Я рисую аватары с изображениями, просто применяя cornerRadius к слою UIImageView, а также добавляя границу через borderWith и borderColor. Например:

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

Вы можете попытаться использовать CAShapelayer с круговой дорожкой в ​​качестве маски для слоя вместо использования радиуса угла.

В качестве альтернативы вы можете поместить изображение в контейнер с границей и увеличить один/два пикселя.

Ответ 2

Как сказано в комментариях, вы можете добавить прозрачную рамку к изображению, чтобы все было нормально. Вот фрагмент кода для этого (категория для 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;
}

Ответ 3

Решение OP излишне сложно. Более простое и чистое решение. Создайте UIView, клип к границам, маске, границе и угловому радиусу, как указано выше, а затем добавьте UIIamgeview в качестве поднабора этого UIView. UIIamgeview будет обрезано родительским представлением, и у вас не будет проблемы с кровотечением.

Ответ 4

Моему UIButton нужен округленный радиус угла, в результате которого были зубчатые края. Установка borderStyle в .roundedRect исправлена.

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

Ответ 5

Попробуйте установить:

[self setClipToBounds:YES]

Ответ 6

У меня была такая же проблема, и мне хотелось найти решение, которое будет хорошо работать с раскадрой. То, что я сделал, - это разместить изображение внутри представления, а затем установить как пользовательский класс, который контролирует поведение. Теперь я могу полностью контролировать дизайн с раскадровки.

Я разместил код на GitHub.

https://github.com/brennanMKE/CircleButton

Что он делает, это переопределить функцию drawRect в UIView для UIButton и обычного UIView, чтобы он мог работать со многими представлениями. Оболочка также используется для управления цветом и шириной границы. Я просто удостоверяюсь, что есть некоторый запас между обернутым представлением и супервидом и использовать разницу в качестве ширины границы. Цвет фона супервизора становится цветом границы. Теперь я могу быстро сделать эти изменения во многих сценах в раскадровке без индивидуального кодирования каждого экземпляра.

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