UIBezierPath: какво е, методи за рисуване и как работи

Автор: IT Sectr Публикувано: 2026-07-21 Време за четене: 8 мин

UIBezierPath — клас от UIKit, който капсулира векторен път за 2D графика в iOS. Той обединява геометричното описание на контура с възможности за рисуване, позволявайки създаването на линии, криви на Безие, дъги и правоъгълници. За разлика от CGPath, UIBezierPath съдържа собствени методи за рисуване директно в текущия графичен контекст. Според Apple Developer Documentation, 2026, UIBezierPath поддържа всички стандартни операции на Quartz 2D в обектно-ориентирана обвивка.

Основни точки

  • UIBezierPath — обектно-ориентирана обвивка на CGPath за работа с векторни контури в UIKit.
  • move(to:) и addLine(to:) задават прави сегменти на пътя.
  • addQuadCurve(to:controlPoint:) и addCurve(to:controlPoint1:controlPoint2:) изграждат криви на Безие.
  • addArc(withCenter:radius:startAngle:endAngle:clockwise:) създава дъги и сектори.
  • fill и stroke — директно рисуване на пътя в текущия контекст без използване на допълнителни обекти.

Какво е UIBezierPath?

UIBezierPath — клас на UIKit рамката, наследяващ NSObject и имплементиращ NSCopying, NSSecureCoding. Той предоставя високо ниво Objective-C/Swift API за изграждане и рисуване на векторни контури. Вътрешно UIBezierPath съхранява препратка към CGPathRef — обект на Core Graphics, но добавя удобни методи за рисуване, промяна на геометрията и управление на стилове.

Основното предимство на UIBezierPath пред директното използване на CGPath са вградените методи за рисуване. Извикването на fill() или stroke() на инстанция на UIBezierPath автоматично конфигурира контекста и рисува пътя. Класът поддържа също свойствата lineWidth, lineCapStyle, lineJoinStyle, miterLimit и flatness, което позволява управление на външния вид без отделен обект за съхранение на стилове.

UIBezierPath се използва в повечето iOS приложения за персонализирано рисуване вътре в UIView: от прости икони до сложни анимирани графики. Методът draw(_:) на класа UIView е основното място, където разработчиците създават и рисуват UIBezierPath. Класът е напълно съвместим с Core Animation и може да се използва за анимация на пътя чрез CAShapeLayer и CABasicAnimation.

Прави линии и правоъгълници

Методът move(to:) премества началната точка на пътя до зададените координати без рисуване. Това е задължително извикване преди изграждане на който и да е сегмент. addLine(to:) добавя прав сегмент от текущата позиция до посочената точка. За затваряне на контура се използва методът close(), който тегли линия от последната точка до първата точка на последното move(to:).

Правоъгълници със заоблени ъгли

UIBezierPath предоставя няколко удобни инициализатора за готови фигури. init(rect:) създава правоъгълник, init(roundedRect:cornerRadius:) — правоъгълник със заоблени ъгли. Параметърът UIRectCorner позволява заобляне само на избрани ъгли, например topLeft и bottomRight, оставяйки останалите прави. Това се използва широко за създаване на персонализирани бутони и карти.

init(ovalIn:) създава елипса, вписана в зададения правоъгълник. Ако правоъгълникът е квадрат, елипсата се превръща в кръг. init(roundedRect:byRoundingCorners:cornerRadii:) — най-гъвкавият вариант, позволяващ задаване на различни радиуси за всеки ъгъл. Този инициализатор се появи в iOS 6 и остава стандарт за създаване на заоблени контейнери.

swift
let path = UIBezierPath()
path.move(to: CGPoint(x: 50, y: 50))
path.addLine(to: CGPoint(x: 200, y: 50))
path.addLine(to: CGPoint(x: 200, y: 150))
path.addLine(to: CGPoint(x: 125, y: 200))
path.addLine(to: CGPoint(x: 50, y: 150))
path.close()

Криви на Безие: квадратни и кубични

addQuadCurve(to:controlPoint:) добавя квадратна крива на Безие с една контролна точка. Този тип крива се използва за прости плавни извивки, когато е необходимо да се минимизира броят на изчисленията. Квадратната крива винаги е парабола и гарантирано лежи вътре в триъгълника, образуван от началната, контролната и крайната точка.

Кубични криви на Безие

addCurve(to:controlPoint1:controlPoint2:) добавя кубична крива на Безие с две контролни точки. Кубичните криви се използват във всички съвременни векторни редактори и TrueType шрифтове. Две контролни точки дават повече степени на свобода за прецизно управление на формата на кривата. Този метод се препоръчва за изграждане на органични и сложни контури.

При изграждане на криви е важно да се спазва принципът на непрекъснатост — гладкостта на кривата в точката на свързване на сегментите. За непрекъснатост C1 (допирателна) контролната точка на следващата крива трябва да лежи на същата права линия като контролната точка на предходната. За непрекъснатост C2 (кривина) е необходимо по-сложно съгласуване, което автоматично се осигурява в CAShapeLayer при анимация между два UIBezierPath с еднакъв брой контролни точки.

МетодТипКонтролни точкиСтепен
addQuadCurveКвадратна Безие12
addCurveКубична Безие23
addArcДъга

Дъги и окръжности

addArc(withCenter:radius:startAngle:endAngle:clockwise:) добавя дъга на окръжност със зададени параметри. Ъглите се задават в радиани спрямо центъра на окръжността. Нулевият ъгъл съответства на посока надясно (3 часа на циферблата). Параметърът clockwise определя посоката: true — по часовниковата стрелка, false — обратно. Дъгите са незаменими за кръгови диаграми, скоростомери и интерактивни пръстени на напредъка.

Окръжности и сектори

За пълна окръжност са достатъчни startAngle = 0 и endAngle = .pi * 2. За създаване на сектор (като част от кръгова диаграма) използвайте addArc последван от addLine(to:) до центъра и close(). UIBezierPath няма вграден метод addCircle — окръжността се създава чрез init(ovalIn:) с квадратен правоъгълник или чрез addArc с пълен ъгъл.

Методът addArc(withCenter:radius:startAngle:endAngle:clockwise:) автоматично изпълнява move(to:) до началната точка на дъгата, ако текущата позиция не съвпада с нея. Ако текущата позиция е различна, между нея и началото на дъгата се тегли права линия. Това поведение се различава от работата с Core Graphics, където се изисква изричен move(to:) преди всеки нов контур.

swift
let center = CGPoint(x: 150, y: 150)
let slice = UIBezierPath()
slice.move(to: center)
slice.addArc(
    withCenter: center,
    radius: 100,
    startAngle: 0,
    endAngle: .pi * 0.75,
    clockwise: true
)
slice.close()

Запълване и очертаване на пътя

fill() запълва вътрешната област на пътя с текущия цвят fillColor или цвят, зададен в графичния контекст чрез setFill(). За пътища със самопресичания правилото за запълване се определя от свойството usesEvenOddFillRule. Ако е true, се прилага even-odd правило: точка се счита за вътре във фигурата, ако лъч от нея пресича контура нечетен брой пъти.

Конфигурация на очертаването

stroke() рисува контура на пътя с текущите настройки lineWidth, lineCapStyle и lineJoinStyle. lineCapStyle определя формата на краищата на отворени сегменти: .butt (прав срез), .round (полукръгъл), .square (квадратен с издатина). lineJoinStyle определя формата на ъглите на съединенията: .miter (остър), .round (заоблен), .bevel (отрязан).

За пунктирани линии се използва свойството setLineDash(_:count:phase:). То приема масив от дължини на редуващи се тирета и празнини, брой елементи и начална фаза. Визуализацията на пунктираната линия се извършва на етапа на растеризация на пътя — сложните модели не увеличават геометричната сложност на контура. CAShapeLayer поддържа анимация на свойството strokeEnd за ефект на “рисуване” на линия от началото до края.

swift
override func draw(_ rect: CGRect) {
    let path = UIBezierPath(ovalIn: CGRect(x: 50, y: 50, width: 200, height: 200))
    UIColor.blue.setFill()
    UIColor.black.setStroke()
    path.lineWidth = 4
    path.fill()
    path.stroke()
}

Пример за създаване на сложна фигура

Нека създадем персонализиран индикатор за напредък под формата на пръстен с помощта на UIBezierPath. Пръстенът се изгражда от две дъги с еднакъв радиус, но различен цвят на очертаването. Първата дъга е пълен сив кръг (фон), втората е сегмент, показващ напредъка. За анимация се използват CAShapeLayer и CABasicAnimation.

swift
class RingView: UIView {

    private let progressLayer = CAShapeLayer()

    override func layoutSubviews() {
        super.layoutSubviews()
        let center = CGPoint(x: bounds.midX, y: bounds.midY)
        let radius = bounds.width * 0.4
        let lineWidth: CGFloat = 12

        let backgroundRing = UIBezierPath()
        backgroundRing.addArc(withCenter: center, radius: radius,
                          startAngle: -.pi / 2, endAngle: .pi * 1.5, clockwise: true)

        progressLayer.path = backgroundRing.cgPath
        progressLayer.strokeColor = UIColor.systemBlue.cgColor
        progressLayer.lineWidth = lineWidth
        progressLayer.lineCap = .round
        progressLayer.fillColor = UIColor.clear.cgColor
        progressLayer.strokeEnd = 0
        layer.addSublayer(progressLayer)
    }

    func animateProgress(to value: CGFloat) {
        CABasicAnimation(keyPath: "strokeEnd").apply {
            $0.toValue = value
            $0.duration = 1.5
            $0.timingFunction = CAMediaTimingFunction(name: .easeOut)
            progressLayer.add($0, forKey: nil)
        }
        progressLayer.strokeEnd = value
    }
}

В този пример UIBezierPath се използва за създаване на кръгов контур, който след това се предава на CAShapeLayer чрез свойството cgPath. Анимацията на strokeEnd от 0 до 1 визуализира запълването на пръстена. Този подход се прилага в индикатори за зареждане, фитнес тракери и уиджети за напредък. Методът layoutSubviews гарантира преизграждане на пътя при промяна на размерите на View.

lineCap = .round добавя заоблени краища на дъгата, което придава на индикатора по-спретнат вид. Свойството strokeEnd е анимируемо свойство на CAShapeLayer, което не изисква прерисуване на целия контур във всеки кадър. Анимацията се изпълнява изцяло на GPU чрез Core Animation, което осигурява 60 FPS без натоварване на CPU.

Често задавани въпроси

Каква е разликата между UIBezierPath и CGPath?

UIBezierPath — обектно-ориентирана обвивка на CGPath, съдържаща вградени методи fill() и stroke(). CGPath е немодифицируем C обект на Core Graphics, изискващ изрично рисуване чрез CGContext. UIBezierPath е по-удобен за UIKit, CGPath е по-производителен за многократна употреба.

Как да заобля само горните ъгли на правоъгълник?

Използвайте init(roundedRect:byRoundingCorners:cornerRadii:) с параметър .topLeft и .topRight за roundedRect. Това е единственият метод на UIBezierPath, позволяващ селективно заобляне на ъгли без създаване на сложен съставен път.

Как да използвам повторно UIBezierPath с различни стилове?

Свойството cgPath връща неизменим CGPathRef, който може да се предава на CAShapeLayer или да се използва с различни цветове и дебелини. Модификациите на UIBezierPath (преместване на точки) не влияят на предварително получения cgPath до изрично обновяване.

Защо fill() не запълва целия път?

Проверете usesEvenOddFillRule — ако контурът има самопресичания, even-odd правилото може да остави области незапълнени. Задайте usesEvenOddFillRule = false за стандартното non-zero winding правило за запълване.

Как да анимирам формата на UIBezierPath?

Използвайте CABasicAnimation с ключов път “path” на CAShapeLayer. И двете състояния на пътя трябва да съдържат еднакъв брой сегменти и контролни точки — в противен случай анимацията ще бъде рязка или няма да работи.

Резюме

  • UIBezierPath — обектно-ориентиран клас на UIKit за работа с векторни контури и 2D графика.
  • move(to:) и addLine(to:) — основни методи за изграждане на прави линии и многоъгълници.
  • addQuadCurve и addCurve създават квадратни и кубични криви на Безие с една и две контролни точки.
  • addArc рисува дъги на окръжност с гъвкаво управление на ъглите и посоката.
  • fill() и stroke() извършват рисуване директно в текущия графичен контекст.
  • Инициализаторите init(roundedRect:) и init(ovalIn:) ускоряват създаването на стандартни фигури.
  • Свойството cgPath позволява интегриране на UIBezierPath с Core Animation и CAShapeLayer за анимация.

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също