UIBezierPath — клас от UIKit, който капсулира векторен път за 2D графика в iOS. Той обединява геометричното описание на контура с възможности за рисуване, позволявайки създаването на линии, криви на Безие, дъги и правоъгълници. За разлика от CGPath, UIBezierPath съдържа собствени методи за рисуване директно в текущия графичен контекст. Според Apple Developer Documentation, 2026, UIBezierPath поддържа всички стандартни операции на Quartz 2D в обектно-ориентирана обвивка.
Основни точки
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 и остава стандарт за създаване на заоблени контейнери.
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 | Квадратна Безие | 1 | 2 |
| addCurve | Кубична Безие | 2 | 3 |
| 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:) преди всеки нов контур.
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 за ефект на “рисуване” на линия от началото до края.
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.
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, съдържаща вградени методи fill() и stroke(). CGPath е немодифицируем C обект на Core Graphics, изискващ изрично рисуване чрез CGContext. UIBezierPath е по-удобен за UIKit, CGPath е по-производителен за многократна употреба.
Използвайте init(roundedRect:byRoundingCorners:cornerRadii:) с параметър .topLeft и .topRight за roundedRect. Това е единственият метод на UIBezierPath, позволяващ селективно заобляне на ъгли без създаване на сложен съставен път.
Свойството cgPath връща неизменим CGPathRef, който може да се предава на CAShapeLayer или да се използва с различни цветове и дебелини. Модификациите на UIBezierPath (преместване на точки) не влияят на предварително получения cgPath до изрично обновяване.
Проверете usesEvenOddFillRule — ако контурът има самопресичания, even-odd правилото може да остави области незапълнени. Задайте usesEvenOddFillRule = false за стандартното non-zero winding правило за запълване.
Използвайте CABasicAnimation с ключов път “path” на CAShapeLayer. И двете състояния на пътя трябва да съдържат еднакъв брой сегменти и контролни точки — в противен случай анимацията ще бъде рязка или няма да работи.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също