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

83

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

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

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

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

ремикиты
источник

Ответы:

102

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

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

CGFloat borderWidth = 2.0f;

self.frame = CGRectInset(self.frame, -borderWidth, -borderWidth);
self.layer.borderColor = [UIColor yellowColor].CGColor;
self.layer.borderWidth = borderWidth;
Эллиотт Джеймс Перри
источник
Я использовал это для представления uitableviewcell. но в строке self.frame = CGRectInset (self.frame, -borderWidth, -borderWidth); Ширина и высота представления продолжают увеличиваться, если мы продолжаем прокручивать представление таблицы ... В итоге я удалил его.
Neela
Этот метод продолжает увеличивать высоту просмотра, поэтому я удаляю этот код и использую следующий код self.view.layer.borderColor = [[UIColor colorWithRed: 209.0f / 255.0f зеленый: 33.0f / 255.0f синий: 8.0f / 255.0f alpha: 1.0f] CGColor]; self.view.layer.borderWidth = 1.0f;
Ризван Шах
4
Пробовал это решение в Swift, к сожалению, не работает, граница продолжает рисоваться внутри UIImageView
theDC
Если этот код находится внутри метода, который вызывается несколько раз, он будет продолжать увеличиваться в размере, потому что self.frame повторяется. Чтобы предотвратить это, объявите свойство для хранения self.frame и установите его в viewDidload. например, _originalSize = self.frame; где originalSize - это свойство CGRect. Затем измените код на self.frame = CGRectInset (_originalSize, -borderWidth, -borderWidth);
GeneCode
Когда это реализовано в ViewDidLoad, вид автоматически изменяется в соответствии с размером экрана (т. Е. Размер представления изменяется до исходного размера, и, таким образом, граница становится видимой). Если он реализован в ViewDidAppear, он вообще не создает границу за пределами исходного представления. Есть мысли о том, как реализовать его, чтобы граница создавалась за пределами исходного представления, а размер представления не изменялся?
JeffB6688
23

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

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и рамку слоя. Это лучше, чем использование второго вида, немного большего размера, поскольку a CALayerлегче, чем a, UIViewи вам не нужно изменять рамку myView, что хорошо, например, если myViewэтоUIImageView

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

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

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

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

Hugues Duvillier
источник
2
myView.layer.masksToBounds = НЕТ; это создает проблему, когда myView имеет CornerRadius.
umakanta
.masksToBounds = no // это также приведет к переполнению вашего изображения, если его режим масштабирования - aspectFill
iOS Blacksmith
22

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

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

Итак, я поделюсь с вами своим расширением 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()
Питер Крейнц
источник
в моем случае это должно быть внутри :)
Питер Крейнц
хороший образ мышления, но ваша граница все еще «внутри», так что это нужно исправить, чтобы она была контура :)
Серж Рубенс
17

Для реализации 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()
    }

}
Picciano
источник
Превосходно! Спасибо! Некоторые вещи изменены в Swift 4 и теперь выглядят иначе, но Xcode объясняет, как изменять код. И в методе removeExternalBorder должен быть Guard externalBorder.name ==.
DmitryKanunnikoff
13

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

  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;
    
Lithu TV
источник
2

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

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;
Хоссам Гариб
источник
2

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

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

источник
1

Мне понравилось решение @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

    }
Максим Князев
источник
0

Мне понравилось решение @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
}
Джимбунг
источник
0

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

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

Изображение вложенного UIView с ограничениями в 2 пикселя сверху, что делает его меньше, чем больше

Джонс-младший 876
источник
0

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()
    }
}
Pacyjent
источник