CAShapeLayer — это подкласс CALayer, предназначенный для аппаратной отрисовки векторных контуров на GPU. В отличие от базового CALayer, который работает с растровыми изображениями через contents, CAShapeLayer рендерит произвольные фигуры, заданные через CGPath, без создания отдельного offscreen-буфера. По данным Apple QuartzCore Documentation, 2025, CAShapeLayer использует аппаратное ускорение для сглаживания краёв и поддерживает анимацию path, strokeStart и strokeEnd. Это стандартный инструмент для создания иконок, индикаторов прогресса и масок в iOS.
Главное
CAShapeLayer — это специализированный слой для рендеринга векторной графики, представленный в iOS 3.0. В отличие от CALayer, который хранит растровое изображение, CAShapeLayer хранит математическое описание контура — CGPath — и рендерит его непосредственно на GPU. Это позволяет масштабировать фигуру без потери качества и анимировать её свойства без перерисовки содержимого.
Архитектурно CAShapeLayer наследует все возможности CALayer: тени, границы, трансформации, маски. Но добавляет специфические свойства для работы с контурами: path, fillColor, strokeColor, lineWidth, lineCap, lineJoin, miterLimit, strokeStart, strokeEnd и fillRule. Каждое из этих свойств может быть анимировано через CABasicAnimation.
По данным Apple Documentation, CAShapeLayer использует аппаратное ускорение при рендеринге. В отличие от рисования через Core Graphics в drawRect:, где CPU отрисовывает контур в растровый контекст, CAShapeLayer отправляет команды напрямую на GPU через Metal. Результат — более высокая производительность, особенно при анимации.
CAShapeLayer особенно полезен для элементов интерфейса, которые меняют форму в ответ на действия пользователя: индикаторы прогресса, графики, иконки таб-бара, маски для анимации появления контента.
Процесс рендеринга CAShapeLayer отличается от базового CALayer. Вместо хранения растрового буфера с пикселями, CAShapeLayer передаёт GPU вершинное описание контура — набор точек, кривых Безье и отрезков. GPU растеризует этот контур за один проход, применяя цвет заливки и обводки на этапе фрагментного шейдера.
Этот подход даёт два преимущества: масштабирование без потери качества (векторная графика остаётся чёткой при любом разрешении) и эффективная анимация — при изменении path GPU пересчитывает только изменившиеся пиксели, а не весь экран.
path — центральное свойство CAShapeLayer, принимающее CGPath. Контур может содержать линии, дуги, кривые Безье и прямоугольники. Создаётся через UIBezierPath в Swift или CGPath в Core Graphics. Пример: UIBezierPath(roundedRect: rect, cornerRadius: 16).cgPath создаёт скруглённый прямоугольник.
Свойство lineDashPattern позволяет создавать пунктирные линии. Принимает массив чисел: [6, 3] означает 6 точек штриха и 3 точки пробела. lineDashPhase задаёт смещение паттерна и может анимироваться для эффекта движущегося пунктира.
Выбор между CAShapeLayer и базовым CALayer зависит от типа контента. Если элемент — прямоугольная область с фоном, границей и тенью — достаточно CALayer. Если элемент имеет произвольную форму, требует анимации контура или масштабирования без потери качества — CAShapeLayer предпочтительнее.
Производительность также различается. CALayer с cornerRadius и masksToBounds создаёт offscreen-буфер для обрезки содержимого, что добавляет 2–5 мс на кадр. CAShapeLayer не требует offscreen-буфера — обрезка выполняется на уровне вершинного шейдера. Для элементов со сложной маской CAShapeLayer может быть в 2–3 раза быстрее.
Комбинирование CAShapeLayer с другими слоями — распространённый паттерн. Например, CAGradientLayer использует CAShapeLayer в качестве маски для создания фигуры с градиентной заливкой. Такой подход даёт и векторное качество, и градиентное заполнение без потери производительности.
Анимация path — одна из самых мощных возможностей CAShapeLayer. Если оба контура (начальный и конечный) имеют одинаковое количество контрольных точек и сегментов, Core Animation выполняет плавную интерполяцию между ними. Результат — эффект морфинга одной фигуры в другую.
Условие совместимости path: начальный и конечный UIBezierPath должны иметь одинаковую структуру — количество moveTo, addLine, addCurve и addQuadCurve вызовов должно совпадать. Если структура различается, анимация будет мгновенной, без интерполяции.
По данным Apple WWDC 2024, комбинация strokeStart и strokeEnd — наиболее часто используемый паттерн анимации CAShapeLayer. Анимация strokeEnd от 0.0 до 1.0 создаёт эффект рисования контура от начала до конца. Это используется в индикаторах загрузки, анимации появления графиков и кастомных переходах между экранами.
Для создания пульсирующего эффекта анимируйте одновременно strokeEnd и opacity с разными timing functions. Для эффекта бегущего пунктира анимируйте lineDashPhase при фиксированном lineDashPattern. Оба приёма реализуются через CABasicAnimation без участия CPU.
CAShapeLayer часто используется как mask для других слоёв. Например, для анимации появления контента: на начальном этапе mask.path — это прямоугольник нулевого размера, конечный — полный размер. Core Animation интерполирует path, и контент открывается плавно.
Этот паттерн используется в iOS для анимации разворачивания карточек, появления модальных окон и transition между view controllers. В отличие от анимации alpha, анимация через маску CAShapeLayer даёт более естественный эффект появления с произвольной формой.
Первый пример — создание круглого индикатора прогресса с анимацией strokeEnd. Это стандартный UI-элемент, который демонстрирует основные возможности CAShapeLayer:
func createProgressIndicator(in view: UIView) -> CAShapeLayer {
let circlePath = UIBezierPath(
arcCenter: CGPoint(x: 50, y: 50),
radius: 40,
startAngle: -.pi / 2,
endAngle: 3 * .pi / 2,
clockwise: true
)
let trackLayer = CAShapeLayer()
trackLayer.path = circlePath.cgPath
trackLayer.strokeColor = UIColor.systemGray5.cgColor
trackLayer.fillColor = nil
trackLayer.lineWidth = 8
view.layer.addSublayer(trackLayer)
let progressLayer = CAShapeLayer()
progressLayer.path = circlePath.cgPath
progressLayer.strokeColor = UIColor.systemBlue.cgColor
progressLayer.fillColor = nil
progressLayer.lineWidth = 8
progressLayer.lineCap = .round
progressLayer.strokeEnd = 0
view.layer.addSublayer(progressLayer)
return progressLayer
}
// Animate to 75%
progressLayer.animateStrokeEnd(to: 0.75, duration: 1.5)
Второй пример — анимация морфинга между кругом и квадратом. Оба UIBezierPath имеют совместимую структуру:
let morphLayer = CAShapeLayer()
morphLayer.fillColor = UIColor.systemPurple.cgColor
morphLayer.frame = rect
let circlePath = UIBezierPath(roundedRect: rect, cornerRadius: rect.width / 2)
let squarePath = UIBezierPath(roundedRect: rect, cornerRadius: 0)
morphLayer.path = circlePath.cgPath
let morphAnimation = CABasicAnimation(keyPath: "path")
morphAnimation.toValue = squarePath.cgPath
morphAnimation.duration = 0.6
morphAnimation.autoreverses = true
morphAnimation.repeatCount = .infinity
morphLayer.add(morphAnimation, forKey: "morph")
Третий пример — маска с CAShapeLayer для анимации появления контента. Маска расширяется от центра к краям:
func revealContent(_ view: UIView) {
let maskLayer = CAShapeLayer()
let size = view.bounds.size.width * 1.5
let center = CGPoint(x: view.bounds.midX, y: view.bounds.midY)
let initialPath = UIBezierPath(arcCenter: center, radius: 0, startAngle: 0, endAngle: .pi * 2, clockwise: true)
let finalPath = UIBezierPath(arcCenter: center, radius: size, startAngle: 0, endAngle: .pi * 2, clockwise: true)
maskLayer.path = initialPath.cgPath
view.layer.mask = maskLayer
let animation = CABasicAnimation(keyPath: "path")
animation.toValue = finalPath.cgPath
animation.duration = 0.4
animation.timingFunction = CAMediaTimingFunction(name: .easeOut)
maskLayer.add(animation, forKey: "reveal")
maskLayer.path = finalPath.cgPath
}
Часто задаваемые вопросы
drawRect использует CPU для отрисовки в растровый контекст — это медленно для анимаций. CAShapeLayer рендерит контур на GPU, что даёт 60 FPS при анимации path, strokeEnd и трансформаций. Кроме того, CAShapeLayer не требует перерисовки при изменении размеров — фигура масштабируется векторно.
Плавная анимация path требует одинаковой структуры начального и конечного UIBezierPath. Количество вызовов moveTo, addLine, addCurve должно совпадать. Используйте UIBezierPath с одинаковым числом сегментов для начальной и конечной фигуры.
Задайте lineDashPattern — массив чисел, чередующих длину штриха и пробела. Например, [8, 4] создаёт штрихи 8pt с пробелами 4pt. Для бегущего пунктира анимируйте lineDashPhase через CABasicAnimation с линейной timing function.
Да, это один из основных сценариев. Присвойте CAShapeLayer свойству mask любого CALayer, включая CAGradientLayer и другие подклассы. Анимация path маски создаёт эффект плавного появления контента через фигуру произвольной формы.
Создайте CAGradientLayer и установите его mask = CAShapeLayer с нужным path. Градиент будет виден только внутри контура маски. Этот паттерн сочетает преимущества векторной формы CAShapeLayer и градиентной заливки CAGradientLayer.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также