Подтвердить что ты не робот

UIBezierPath: Как добавить границу вокруг представления с закругленными углами?

Я использую UIBezierPath, чтобы иметь мое изображение в круглых углах, но я также хочу добавить границу к представлению изображения. Помните, что верхняя часть - это uiimage, а нижняя - метка.

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

let rectShape = CAShapeLayer()
rectShape.bounds = myCell2.NewFeedImageView.frame
rectShape.position = myCell2.NewFeedImageView.center
rectShape.path = UIBezierPath(roundedRect: myCell2.NewFeedImageView.bounds,
    byRoundingCorners: .TopRight | .TopLeft,
    cornerRadii: CGSize(width: 25, height: 25)).CGPath
myCell2.NewFeedImageView.layer.mask = rectShape

current

Я хочу добавить к нему зеленую рамку, но я не могу использовать

myCell2.NewFeedImageView.layer.borderWidth = 8
myCell2.NewFeedImageView.layer.borderColor = UIColor.greenColor().CGColor

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

issue

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

4b9b3361

Ответ 1

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

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Create a view with red background for demonstration
        let v = UIView(frame: CGRectMake(0, 0, 100, 100))
        v.center = view.center
        v.backgroundColor = UIColor.redColor()
        view.addSubview(v)

        // Add rounded corners
        let maskLayer = CAShapeLayer()
        maskLayer.frame = v.bounds
        maskLayer.path = UIBezierPath(roundedRect: v.bounds, byRoundingCorners: .TopRight | .TopLeft, cornerRadii: CGSize(width: 25, height: 25)).CGPath
        v.layer.mask = maskLayer

        // Add border
        let borderLayer = CAShapeLayer()
        borderLayer.path = maskLayer.path // Reuse the Bezier path
        borderLayer.fillColor = UIColor.clearColor().CGColor
        borderLayer.strokeColor = UIColor.greenColor().CGColor
        borderLayer.lineWidth = 5
        borderLayer.frame = v.bounds
        v.layer.addSublayer(borderLayer)   
    }

}

Конечный результат выглядит следующим образом.

Скриншот Simulator

Обратите внимание, что это работает только в том случае, если размер вида фиксирован. Когда представление может изменить размер, вам необходимо создать пользовательский класс представления и изменить размер слоев в layoutSubviews.

Ответ 2

Как сказано выше:

Это не легко сделать идеально.

Здесь раскрывающееся решение.


Это

  • правильно решает проблему, которую вы рисуете ПОЛОВИНА ГРАНИЦЫ

  • полностью используется в autolayout

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

  • полностью IBDesignable - вы можете увидеть это в режиме реального времени на вашей раскадровке

на 2019 год...

@IBDesignable
class RoundedCornersAndTrueBorder: UIView {
    @IBInspectable var cornerRadius: CGFloat = 10 {
        didSet { setup() }
    }
    @IBInspectable var borderColor: UIColor = UIColor.black {
        didSet { setup() }
    }
    @IBInspectable var trueBorderWidth: CGFloat = 2.0 {
        didSet { setup() }
    }

    override func layoutSubviews() {
        setup()
    }

    var border:CAShapeLayer? = nil

    func setup() {
        // make a path with round corners
        let path = UIBezierPath(
          roundedRect: self.bounds, cornerRadius:cornerRadius)

        // note that it is >exactly< the size of the whole view

        // mask the whole view to that shape
        // note that you will ALSO be masking the border we'll draw below
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        self.layer.mask = mask

        // add another layer, which will be the border as such

        if (border == nil) {
            border = CAShapeLayer()
            self.layer.addSublayer(border!)
        }
        // IN SOME APPROACHES YOU would INSET THE FRAME
        // of the border-drawing layer by the width of the border
        // border.frame = bounds.insetBy(dx: borderWidth, dy: borderWidth)
        // so that when you draw the line, ALL of the WIDTH of the line
        // DOES fall within the actual mask.

        // here, we will draw the border-line LITERALLY ON THE EDGE
        // of the path. that means >HALF< THE LINE will be INSIDE
        // the path and HALF THE LINE WILL BE OUTSIDE the path
        border!.frame = bounds
        let pathUsingCorrectInsetIfAny =
          UIBezierPath(roundedRect: border!.bounds, cornerRadius:cornerRadius)

        border!.path = pathUsingCorrectInsetIfAny.cgPath
        border!.fillColor = UIColor.clear.cgColor

        // the following is not what you want:
        // it results in "half-missing corners"
        // (note however, sometimes you do use this approach):
        //border.borderColor = borderColor.cgColor
        //border.borderWidth = borderWidth

        // this approach will indeed be "inside" the path:
        border!.strokeColor = borderColor.cgColor
        border!.lineWidth = trueBorderWidth * 2.0
        // HALF THE LINE will be INSIDE the path and HALF THE LINE
        // WILL BE OUTSIDE the path. so MAKE IT >>TWICE AS THICK<<
        // as requested by the consumer class.

    }
}

Так что это.


Помощь начинающим по вопросу в комментариях...

  1. Создайте "новый файл Swift" под названием "Fattie.swift". (Обратите внимание, что, как ни странно, на самом деле не имеет значения, как вы это называете. Если вы находитесь на стадии "не знаю, как создать новый файл", обратитесь к базовым учебникам по Xcode.)

  2. Поместите весь вышеуказанный код в файл

  3. Вы только что добавили класс "RoundedCornersAndTrueBorder" в свой проект.

  4. На вашей доске объявлений. Добавьте обычный UIView к вашей сцене. Фактически, сделайте это на самом деле любого размера/формы, что угодно.

  5. Посмотрите на инспектора идентичности. (Если вы не знаете, что это такое, ищите базовые учебные пособия.) Просто измените класс на "RoundedCornersAndTrueBorder". (Как только вы начнете набирать "Roun...", он угадает, какой класс вы имеете в виду.

  6. Готово - запустите проект.

Обратите внимание, что вы, конечно, должны добавить полные и правильные ограничения в UIView, так же, как и абсолютно все, что вы делаете в Xcode. Наслаждайтесь!

Ответ 3

Абсолютно идеальное решение 2019 года

enter image description here

Без лишних слов, вот как именно вы это делаете.

  1. На самом деле не используйте "базовый" слой, который поставляется с видом
  2. Сделайте новый слой только для изображения. Теперь вы можете маскировать это (по кругу), не затрагивая следующий слой
  3. Сделайте новый слой для границы как таковой. Он не будет замаскирован слоем изображения.

Ключевые факты:

  1. С помощью CALayer вы действительно можете применить маску. Это влияет только на этот слой
  2. При рисовании круга (или любой другой границы) нужно очень внимательно следить за тем, чтобы вы получали только "половину ширины" - короче говоря, никогда не обрезайте, используя ту же траекторию, с которой вы рисуете.
  3. Обратите внимание, что исходное изображение кошки имеет такую же ширину, как горизонтальная желтая стрелка. Вы должны быть осторожны, чтобы нарисовать изображение так, чтобы все изображение появилось в округлом поле, которое меньше, чем общий пользовательский элемент управления.

Итак, настройте обычным способом

import UIKit

@IBDesignable class GreenCirclePerson: UIView {

    @IBInspectable var borderColor: UIColor = UIColor.black { didSet { setup() } }
    @IBInspectable var trueBorderThickness: CGFloat = 2.0 { didSet { setup() } }
    @IBInspectable var trueGapThickness: CGFloat = 2.0 { didSet { setup() } }

    @IBInspectable var picture: UIImage? = nil { didSet { setup() } }

    override func layoutSubviews() { setup() }

    var imageLayer: CALayer? = nil
    var border: CAShapeLayer? = nil

    func setup() {

        if (imageLayer == nil) {
            imageLayer = CALayer()
            self.layer.addSublayer(imageLayer!)
        }
        if (border == nil) {
            border = CAShapeLayer()
            self.layer.addSublayer(border!)
        }

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

        // the ultimate size of our custom control:
        let box = self.bounds.aspectFit()

        let totalInsetOnAnyOneSide = trueBorderThickness + trueGapThickness

        let boxInWhichImageSits = box.inset(by:
           UIEdgeInsets(top: totalInsetOnAnyOneSide, left: totalInsetOnAnyOneSide,
           bottom: totalInsetOnAnyOneSide, right: totalInsetOnAnyOneSide))

        // just a note. that version of inset#by is much clearer than the
        // confusing dx/dy variant, so best to use that one

        imageLayer!.frame = boxInWhichImageSits
        imageLayer!.contents = picture?.cgImage
        imageLayer?.contentsGravity = .resizeAspectFill

        let halfImageSize = boxInWhichImageSits.width / 2.0

        let maskPath = UIBezierPath(roundedRect: imageLayer!.bounds,
           cornerRadius:halfImageSize)
        let maskLayer = CAShapeLayer()
        maskLayer.path = maskPath.cgPath
        imageLayer!.mask = maskLayer

Затем, как совершенно отдельный слой, нарисуйте границу по своему желанию:

        // now create the border

        border!.frame = bounds

        // To draw the border, you must inset it by half the width of the border,
        // otherwise you'll be drawing only half the border. (Indeed, as an additional
        // subtle problem you are clipping rather than rendering the outside edge.)

        let halfWidth = trueBorderThickness / 2.0
        let borderCenterlineBox = box.inset(by:
            UIEdgeInsets(top: halfWidth, left: halfWidth,
            bottom: halfWidth, right: halfWidth))

        let halfBorderBoxSize = borderCenterlineBox.width / 2.0

        let borderPath = UIBezierPath(roundedRect: borderCenterlineBox,
          cornerRadius:halfBorderBoxSize)

        border!.path = borderPath.cgPath
        border!.fillColor = UIColor.clear.cgColor

        border!.strokeColor = borderColor.cgColor
        border!.lineWidth = trueBorderThickness
    }
}

Все работает отлично, как в стандартных элементах управления iOS:

enter image description here

Все, что невидимо, невидимо; Вы можете видеть сквозной общий пользовательский элемент управления для любого материала позади, нет проблем с "половиной толщины" или отсутствующего материала изображения, вы можете установить цвет фона пользовательского элемента управления обычным способом и т.д. и т.д. Инспектор контролирует все работы правильно. (Уф!)

Ответ 4

Конечно! Каждое представление имеет свойство layer (которое, как вам известно, дает ваш слой закругленными углами). Еще два свойства на layer: borderColor и borderWidth. Просто установив их, вы можете добавить границу к своему виду! (Граница будет следовать за закругленными углами.) Обязательно используйте UIColor.CGColor для borderColor, так как простой UIColor не будет соответствовать типу.