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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також