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 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также