Добавить границу за пределами UIView (вместо внутри)

Если добавить границу вида, используя код в виде, как

self.layer.borderColor = [UIColor yellowColor].CGColor;
self.layer.borderWidth = 2.0f;

граница добавляется в представление следующим образом:

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

12 ответов

Решение

К сожалению, вы не можете просто установить небольшое свойство, чтобы выровнять границу снаружи. Он рисует выровненным по внутренней части, потому что операции рисования по умолчанию UIViews рисуют в пределах своих границ.

Самое простое решение, которое приходит на ум, - это расширить UIView на размер ширины границы при применении границы:

CGFloat borderWidth = 2.0f;

self.frame = CGRectInset(self.frame, -borderWidth, -borderWidth);
self.layer.borderColor = [UIColor yellowColor].CGColor;
self.layer.borderWidth = borderWidth;

Для реализации Swift вы можете добавить это как расширение UIView.

extension UIView {

    struct Constants {
        static let ExternalBorderName = "externalBorder"
    }

    func addExternalBorder(borderWidth: CGFloat = 2.0, borderColor: UIColor = UIColor.whiteColor()) -> CALayer {
        let externalBorder = CALayer()
        externalBorder.frame = CGRectMake(-borderWidth, -borderWidth, frame.size.width + 2 * borderWidth, frame.size.height + 2 * borderWidth)
        externalBorder.borderColor = borderColor.CGColor
        externalBorder.borderWidth = borderWidth
        externalBorder.name = Constants.ExternalBorderName

        layer.insertSublayer(externalBorder, atIndex: 0)
        layer.masksToBounds = false

        return externalBorder
    }

    func removeExternalBorders() {
        layer.sublayers?.filter() { $0.name == Constants.ExternalBorderName }.forEach() {
            $0.removeFromSuperlayer()
        }
    }

    func removeExternalBorder(externalBorder: CALayer) {
        guard externalBorder.name == Constants.ExternalBorderName else { return }
        externalBorder.removeFromSuperlayer()
    }

}

Получив лучший ответ, я получил опыт с такими не очень хорошими результатами и неприглядными краями:

Граница без безье пути

Поэтому я поделюсь с вами своим расширением UIView Swift, в котором вместо контура границы используется UIBezierPath - без неприглядных краев (вдохновлено @Fattie):

граница с безье пути

//  UIView+BezierPathBorder.swift

import UIKit

extension UIView {

    fileprivate var bezierPathIdentifier:String { return "bezierPathBorderLayer" }

    fileprivate var bezierPathBorder:CAShapeLayer? {
        return (self.layer.sublayers?.filter({ (layer) -> Bool in
            return layer.name == self.bezierPathIdentifier && (layer as? CAShapeLayer) != nil
        }) as? [CAShapeLayer])?.first
    }

    func bezierPathBorder(_ color:UIColor = .white, width:CGFloat = 1) {

        var border = self.bezierPathBorder
        let path = UIBezierPath(roundedRect: self.bounds, cornerRadius:self.layer.cornerRadius)
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        self.layer.mask = mask

        if (border == nil) {
            border = CAShapeLayer()
            border!.name = self.bezierPathIdentifier
            self.layer.addSublayer(border!)
        }

        border!.frame = self.bounds
        let pathUsingCorrectInsetIfAny =
            UIBezierPath(roundedRect: border!.bounds, cornerRadius:self.layer.cornerRadius)

        border!.path = pathUsingCorrectInsetIfAny.cgPath
        border!.fillColor = UIColor.clear.cgColor
        border!.strokeColor = color.cgColor
        border!.lineWidth = width * 2
    }

    func removeBezierPathBorder() {
        self.layer.mask = nil
        self.bezierPathBorder?.removeFromSuperlayer()
    }

}

Пример:

let view = UIView(frame: CGRect(x: 20, y: 20, width: 100, height: 100))
view.layer.cornerRadius = view.frame.width / 2
view.backgroundColor = .red

//add white 2 pixel border outline
view.bezierPathBorder(.white, width: 2)

//remove border outline (optional)
view.removeBezierPathBorder()

Хорошо, уже есть принятый ответ, но я думаю, что есть лучший способ сделать это, вам просто нужно иметь новый слой немного больше, чем ваш вид, и не маскировать его до границ слоя вида (который на самом деле поведение по умолчанию). Вот пример кода:

CALayer * externalBorder = [CALayer layer];
externalBorder.frame = CGRectMake(-1, -1, myView.frame.size.width+2, myView.frame.size.height+2);
externalBorder.borderColor = [UIColor blackColor].CGColor;
externalBorder.borderWidth = 1.0;

[myView.layer addSublayer:externalBorder];
myView.layer.masksToBounds = NO;

Конечно, если вы хотите, чтобы ваша граница была на 1 единицу больше, если вы хотите больше, вы адаптируете borderWidth и рамка слоя соответственно. Это лучше, чем использовать второй вид немного больше, чем CALayer легче чем UIView и вам не нужно изменять кадр myViewчто хорошо, например, если myView этоUIImageView

NB: Для меня результат был не идеальным на симуляторе (слой был не совсем в правильном положении, поэтому иногда слой был толще с одной стороны), но это было именно то, что требуется на реальном устройстве.

РЕДАКТИРОВАТЬ

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

Надеюсь, поможет

Ну, нет прямого способа сделать это. Вы можете рассмотреть некоторые обходные пути.

  1. Измените и увеличьте рамку и добавьте цвет рамки, как вы это сделали
  2. Добавьте вид позади текущего вида с большим размером, чтобы он выглядел как граница. Может работать как пользовательский класс представления
  3. Если вам не нужна определенная граница (четкая граница), вы можете зависеть от тени

    [view1 setBackgroundColor:[UIColor blackColor]];
    UIColor *color = [UIColor yellowColor];
    view1.layer.shadowColor = [color CGColor];
    view1.layer.shadowRadius = 10.0f;
    view1.layer.shadowOpacity = 1;
    view1.layer.shadowOffset = CGSizeZero;
    view1.layer.masksToBounds = NO;
    

Swift 5

extension UIView {
    fileprivate struct Constants {
        static let externalBorderName = "externalBorder"
    }

    func addExternalBorder(borderWidth: CGFloat = 2.0, borderColor: UIColor = UIColor.white) -> CALayer {
        let externalBorder = CALayer()
        externalBorder.frame = CGRect(x: -borderWidth, y: -borderWidth, width: frame.size.width + 2 * borderWidth, height: frame.size.height + 2 * borderWidth)
        externalBorder.borderColor = borderColor.cgColor
        externalBorder.borderWidth = borderWidth
        externalBorder.name = Constants.ExternalBorderName

        layer.insertSublayer(externalBorder, at: 0)
        layer.masksToBounds = false

        return externalBorder
    }

    func removeExternalBorders() {
        layer.sublayers?.filter() { $0.name == Constants.externalBorderName }.forEach() {
            $0.removeFromSuperlayer()
        }
    }

    func removeExternalBorder(externalBorder: CALayer) {
        guard externalBorder.name == Constants.externalBorderName else { return }
        externalBorder.removeFromSuperlayer()
    }
}

Увеличьте ширину и высоту рамки вида с шириной границы перед добавлением границы:

float borderWidth = 2.0f
CGRect frame = self.frame;
frame.width += borderWidth;
frame.height += borderWidth;
 self.layer.borderColor = [UIColor yellowColor].CGColor;
 self.layer.borderWidth = 2.0f;

На самом деле есть очень простое решение. Просто установите их обоих так:

view.layer.borderWidth = 5

view.layer.borderColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.5).cgColor

view.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.25).cgColor

Как я разместил рамку вокруг своего представления пользовательского интерфейса (основной - SubscriptionAd) в раскадровке, - это поместить его в другое представление пользовательского интерфейса (фон - BackgroundAd). Фон UIView имеет цвет фона, который соответствует желаемому цвету границы, а основной UIView имеет значение ограничения 2 с каждой стороны.

Я свяжу фоновое представление со своим ViewController, а затем включу и выключу границу, изменив цвет фона.

Мне понравилось решение @picciano. Если вы хотите взорвать круг вместо квадрата, замените функцию addExternalBorder на:

func addExternalBorder(borderWidth: CGFloat = 2.0, borderColor: UIColor = UIColor.white) {
        let externalBorder = CALayer()
        externalBorder.frame = CGRect(x: -borderWidth, y: -borderWidth, width: frame.size.width + 2 * borderWidth, height: frame.size.height + 2 * borderWidth)
        externalBorder.borderColor = borderColor.cgColor
        externalBorder.borderWidth = borderWidth
        externalBorder.cornerRadius = (frame.size.width + 2 * borderWidth) / 2
        externalBorder.name = Constants.ExternalBorderName
        layer.insertSublayer(externalBorder, at: 0)
        layer.masksToBounds = false

    }

SwiftUI

Используйте отступ() той же ширины, что и желаемая граница, согласно рекомендации Apple. https://developer.apple.com/documentation/swiftui/view/border(_:width:))

Например, в представлении сначала используйте отступы, а затем добавьте границу:

      .padding(8)
.border(Color.yellow, width: 8)

Результат:

Мне понравилось решение @picciano & @Maksim Kniazev. Мы также можем создать кольцевую границу следующим образом:

func addExternalAnnularBorder(borderWidth: CGFloat = 2.0, borderColor: UIColor = UIColor.white) {
    let externalBorder = CALayer()
    externalBorder.frame = CGRect(x: -borderWidth*2, y: -borderWidth*2, width: frame.size.width + 4 * borderWidth, height: frame.size.height + 4 * borderWidth)
    externalBorder.borderColor = borderColor.cgColor
    externalBorder.borderWidth = borderWidth
    externalBorder.cornerRadius = (frame.size.width + 4 * borderWidth) / 2
    externalBorder.name = Constants.ExternalBorderName
    layer.insertSublayer(externalBorder, at: 0)
    layer.masksToBounds = false
}
Другие вопросы по тегам