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

Мне удалось создать закругленные углы, но у меня возникли проблемы с первым закругленным углом (внизу справа)

Вопрос :

  • Можно ли добавить метод (addArcWithCenter) перед методом (moveToPoint)?
  • Как я могу избавиться от прямой линии в начале прямоугольника (внизу справа)?

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

let path = UIBezierPath()
path.moveToPoint(CGPoint(x: 300, y: 0))
path.addArcWithCenter(CGPoint(x: 300-10, y: 50), radius: 10 , startAngle: 0 , endAngle: CGFloat(M_PI/2)  , clockwise: true) //1st rounded corner
path.addArcWithCenter(CGPoint(x: 200, y: 50), radius:10, startAngle: CGFloat(2 * M_PI / 3), endAngle:CGFloat(M_PI) , clockwise: true)// 2rd rounded corner
path.addArcWithCenter(CGPoint(x: 200, y: 10), radius:10, startAngle: CGFloat(M_PI), endAngle:CGFloat(3 * M_PI / 2), clockwise: true)// 3rd rounded corner
// little triangle at the bottom
path.addLineToPoint(CGPoint(x:240 , y:0))
path.addLineToPoint(CGPoint(x: 245, y: -10))
path.addLineToPoint(CGPoint(x:250, y: 0))
path.addArcWithCenter(CGPoint(x: 290, y: 10), radius: 10, startAngle: CGFloat(3 * M_PI / 2), endAngle: CGFloat(2 * M_PI ), clockwise: true)
path.closePath()

введите здесь описание изображения


person AziCode    schedule 03.07.2015    source источник
comment
Так что же остальное? Вы действительно получаете аналогичный эффект.   -  person NSDeveloper    schedule 04.07.2015


Ответы (5)


Я думаю, что то, что вы делаете, слишком сложно. UIBezierPath дает вам UIBezierPath(roundedRect:), так почему бы его не использовать? Обведите закругленный прямоугольник; сотрите место, куда вы собираетесь поместить маленький треугольник; добавить треугольник; заполнить составной путь; и обведите недостающие две стороны треугольника. Вот так (это просто какой-то код, который у меня случайно завалялся - вы, конечно, должны изменить числа, чтобы они соответствовали вашей форме):

let con = UIGraphicsGetCurrentContext()
CGContextTranslateCTM(con, 10, 10)
UIColor.blueColor().setStroke()
UIColor.blueColor().colorWithAlphaComponent(0.4).setFill()
let p = UIBezierPath(roundedRect: CGRectMake(0,0,250,180), cornerRadius: 10)
p.stroke()
CGContextClearRect(con, CGRectMake(20,170,10,11))
let pts = [
    CGPointMake(20,180), CGPointMake(20,200),
    CGPointMake(20,200), CGPointMake(30,180)
]
p.moveToPoint(pts[0])
p.addLineToPoint(pts[1])
p.addLineToPoint(pts[3])
p.fill()
CGContextStrokeLineSegments(con, pts, 4)

введите здесь описание изображения

person matt    schedule 04.07.2015
comment
Очень хорошей причиной для того, чтобы не использовать его, было бы, если вы планируете делать анимации strokeStart и strokeEnd. В этом случае важно, где прямоугольник начинает рисовать свою линию. При использовании пути у вас есть контроль над этим. - person Philipp Jahoda; 16.11.2018

Вместо того, чтобы начинать код с прямой строки:

path.moveToPoint(CGPoint(x: 300, y: 0))

Вместо этого я начинаю с дуги (вверху справа):

path.addArcWithCenter(CGPoint(x: 300-10, y: 50), radius: 10 , startAngle: 0 , endAngle: CGFloat(M_PI/2)  , clockwise: true) //1st rounded corner

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

path.closePath()  

Вот код и скриншот:

let path = UIBezierPath()
path.addArcWithCenter(CGPoint(x: 300-10, y: 50), radius: 10 , startAngle: 0 , endAngle: CGFloat(M_PI/2)  , clockwise: true) //1st rounded corner
path.addArcWithCenter(CGPoint(x: 200, y: 50), radius:10, startAngle: CGFloat(2 * M_PI / 3), endAngle:CGFloat(M_PI) , clockwise: true)// 2rd rounded corner
path.addArcWithCenter(CGPoint(x: 200, y: 10), radius:10, startAngle: CGFloat(M_PI), endAngle:CGFloat(3 * M_PI / 2), clockwise: true)// 3rd rounded corner
// little triangle
path.addLineToPoint(CGPoint(x:240 , y:0))
path.addLineToPoint(CGPoint(x: 245, y: -10))
path.addLineToPoint(CGPoint(x:250, y: 0))
path.addArcWithCenter(CGPoint(x: 290, y: 10), radius: 10, startAngle: CGFloat(3 * M_PI / 2), endAngle: CGFloat(2 * M_PI ), clockwise: true)
path.addLineToPoint(CGPoint(x:300 , y:50))
path.closePath()

введите здесь описание изображения

person AziCode    schedule 06.07.2015
comment
Рад, что ты нашел это. Собственно это я и описывал. :D - person Fogmeister; 06.07.2015
comment
Чтобы улучшить это, вы должны взять радиус, высоту и ширину в переменные. Таким образом, вы можете сделать пузырек любого размера и с любым радиусом закругления, просто изменив одно значение. - person Fogmeister; 06.07.2015
comment
Должен ли я помещать x и y в переменные? Но они отличаются от одной строки кода к другой. Не могли бы вы объяснить больше? Спасибо - person AziCode; 06.07.2015
comment
ах. Ну, я бы на самом деле нарисовал это в отдельном UIView, который можно позиционировать независимо. Тогда положение пузырька начнется с (0, 0). В этом случае ваши значения x y зависят от положения пузырька и его размера. Правый нижний угол равен (x + ширина, y + высота) и так далее. - person Fogmeister; 06.07.2015

Пара наблюдений:

  1. Убедитесь, что вы берете вид bounds и вставляете его на половину ширины линии. Это гарантирует, что вся заштрихованная граница попадает в bounds представления. Если ширина вашей линии равна 1, это может быть не так очевидно, но при большей ширине линии проблема становится более явной.

  2. #P3# #P4# <блочная цитата> #P5#
  3. Я бы дал всем различным свойствам представления didSet наблюдателя, который вызовет перерисовку представления. Таким образом, любые переопределения IB или программно установленные значения будут автоматически отражены в результирующем представлении.

  4. Если вы хотите, вы можете сделать все это @IBDesignable и сделать свойства @IBInspectable, чтобы вы могли видеть, что это визуализируется в Interface Builder. Это не обязательно, но может быть полезно, если вы хотите увидеть это в раскадровках или NIB.

  5. Хотя вы можете скруглять углы, используя дугу окружности, использовать четырехугольную кривую проще, ИМХО. Вы просто указываете, где заканчивается дуга и угол прямоугольника, и квадратичный Безье создаст красиво закругленный угол. При использовании этого метода не требуется вычисление углов или центра дуги.

Таким образом:

@IBDesignable
class BubbleView: UIView {
    @IBInspectable var lineWidth:    CGFloat = 1       { didSet { setNeedsDisplay() } }
    @IBInspectable var cornerRadius: CGFloat = 10      { didSet { setNeedsDisplay() } }
    @IBInspectable var calloutSize:  CGFloat = 5       { didSet { setNeedsDisplay() } }
    @IBInspectable var fillColor:    UIColor = .yellow { didSet { setNeedsDisplay() } }
    @IBInspectable var strokeColor:  UIColor = .black  { didSet { setNeedsDisplay() } }

    override func draw(_ rect: CGRect) {
        let rect = bounds.insetBy(dx: lineWidth / 2, dy: lineWidth / 2)
        let path = UIBezierPath()

        // lower left corner
        path.move(to: CGPoint(x: rect.minX + cornerRadius, y: rect.maxY - calloutSize))
        path.addQuadCurve(to: CGPoint(x: rect.minX, y: rect.maxY - calloutSize - cornerRadius),
                          controlPoint: CGPoint(x: rect.minX, y: rect.maxY - calloutSize))

        // left
        path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + cornerRadius))

        // upper left corner
        path.addQuadCurve(to: CGPoint(x: rect.minX + cornerRadius, y: rect.minY),
                          controlPoint: CGPoint(x: rect.minX, y: rect.minY))

        // top
        path.addLine(to: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY))

        // upper right corner
        path.addQuadCurve(to: CGPoint(x: rect.maxX, y: rect.minY + cornerRadius),
                          controlPoint: CGPoint(x: rect.maxX, y: rect.minY))

        // right
        path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - calloutSize - cornerRadius))

        // lower right corner
        path.addQuadCurve(to: CGPoint(x: rect.maxX - cornerRadius, y: rect.maxY - calloutSize),
                          controlPoint: CGPoint(x: rect.maxX, y: rect.maxY - calloutSize))

        // bottom (including callout)
        path.addLine(to: CGPoint(x: rect.midX + calloutSize, y: rect.maxY - calloutSize))
        path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
        path.addLine(to: CGPoint(x: rect.midX - calloutSize, y: rect.maxY - calloutSize))
        path.close()

        fillColor.setFill()
        path.fill()

        strokeColor.setStroke()
        path.lineWidth = lineWidth
        path.stroke()
    }
}

Это дает:

введите описание изображения здесь

person Rob    schedule 27.02.2020
comment
Вау! Это действительно хороший ответ! Я попытался выяснить, как решить мою проблему, чтобы установить угол с помощью UIBezierPath, и я сам получил то, что хочу. Но если бы я мог найти этот ответ, я думаю, что смогу получить ответ быстрее! Хорошо!! - person User18474728; 27.02.2020
comment
Это должен быть принятый ответ. - person appiconhero.co; 07.12.2020

Swift 5 с переменными конфигурации:

override func draw(_ rect: CGRect) {
    let arrowXOffset: CGFloat = 13
    let cornerRadius: CGFloat = 6
    let arrowHeight: CGFloat = 6

    let mainRect = CGRect(origin: rect.origin, size: CGSize(width: rect.width, height: rect.height - arrowHeight))

    let leftTopPoint = mainRect.origin
    let rightTopPoint = CGPoint(x: mainRect.maxX, y: mainRect.minY)
    let rightBottomPoint = CGPoint(x: mainRect.maxX, y: mainRect.maxY)
    let leftBottomPoint = CGPoint(x: mainRect.minX, y: mainRect.maxY)

    let leftArrowPoint = CGPoint(x: leftBottomPoint.x + arrowXOffset, y: leftBottomPoint.y)
    let centerArrowPoint = CGPoint(x: leftArrowPoint.x + arrowHeight, y: leftArrowPoint.y + arrowHeight)
    let rightArrowPoint = CGPoint(x: leftArrowPoint.x + 2 * arrowHeight, y: leftArrowPoint.y)

    let path = UIBezierPath()
    path.addArc(withCenter: CGPoint(x: rightTopPoint.x - cornerRadius, y: rightTopPoint.y + cornerRadius), radius: cornerRadius,
                startAngle: CGFloat(3 * Double.pi / 2), endAngle: CGFloat(2 * Double.pi), clockwise: true)
    path.addArc(withCenter: CGPoint(x: rightBottomPoint.x - cornerRadius, y: rightBottomPoint.y - cornerRadius), radius: cornerRadius,
                startAngle: 0, endAngle: CGFloat(Double.pi / 2), clockwise: true)

    path.addLine(to: rightArrowPoint)
    path.addLine(to: centerArrowPoint)
    path.addLine(to: leftArrowPoint)

    path.addArc(withCenter: CGPoint(x: leftBottomPoint.x + cornerRadius, y: leftBottomPoint.y - cornerRadius), radius: cornerRadius,
                startAngle: CGFloat(Double.pi / 2), endAngle: CGFloat(Double.pi), clockwise: true)
    path.addArc(withCenter: CGPoint(x: leftTopPoint.x + cornerRadius, y: leftTopPoint.y + cornerRadius), radius: cornerRadius,
                startAngle: CGFloat(Double.pi), endAngle: CGFloat(3 * Double.pi / 2), clockwise: true)


    path.addLine(to: rightTopPoint)
    path.close()
}
person Andrew Krotov    schedule 15.02.2020
comment
Спасибо за ваш ответ! Вы можете просто использовать CGFloat.pi вместо CGFloat(Double.pi) - person alc77; 15.07.2020

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

Так. Вместо того, чтобы добавлять линию к x, y, вы добавляете линию к x-радиусу, y. Затем добавьте дугу. Затем следующая строка начинается с x, y+радиус.

person Fogmeister    schedule 03.07.2015
comment
@AziCode Я могу завтра. Хотя сейчас 1:30. :-) - person Fogmeister; 04.07.2015