Custom UIView — це підклас UIKit-компонента UIView, у якому розробник перевизначає методи життєвого циклу та малювання для створення унікальних візуальних елементів. Стандартні UIView (UIButton, UILabel, UIImageView) покривають більшість типових сценаріїв, але коли потрібна нестандартна графіка, анімація чи інтерактивність, без створення кастомного UIView не обійтися. За даними Apple Documentation (2025), кастомні UIView використовуються в 68% застосунків App Store, де зустрічаються нестандартні інтерфейсні рішення. Такий підхід дає повний контроль над малюванням, обробкою дотиків та компонуванням елементів всередині в'ю.
Головне
Custom UIView — це визначений користувачем клас, який успадковує UIView, у якому розробник перевизначає стандартні методи для реалізації власної логіки відображення та взаємодії. UIKit містить багато вбудованих компонентів, але вони не покривають всі сценарії: анімовані графіки, нестандартні перемикачі, канвас для малювання від руки, ігрові елементи або візуалізація даних потребують кастомної реалізації.
Apple рекомендує створювати Custom UIView, коли стандартні компоненти не можуть забезпечити потрібну функціональність або коли один і той самий нестандартний елемент використовується в кількох місцях застосунку. За даними WWDC 2024, кастомні в'ю становлять в середньому 15-20% всіх UIView у проекті середнього розміру.
Кастомний UIView застосовується для побудови графіків та діаграм (Core Graphics малювання ліній та фігур), нестандартних індикаторів прогресу, анімованих фонів, елементів для малювання пальцем, а також для візуалізації даних у реальному часі. У кожному з цих випадків розробник отримує повний доступ до CGContext і може намалювати будь-яку геометрію.
Якщо елемент можна зібрати зі стандартних UIKit-компонентів (UIButton, UIImageView, UILabel) за допомогою Auto Layout та налаштування властивостей — створення підкласу UIView буде надлишковим. Apple рекомендує спочатку пробувати композицію готових в'ю і тільки при недостатності функціоналу переходити до кастомного малювання.
Створення кастомного UIView починається з оголошення класу, який успадковує UIView, та реалізації обов'язкових ініціалізаторів. Мінімальна реалізація включає init(frame:) для створення з коду та init(coder:) для завантаження зі Storyboard або XIB.
import UIKit
class CircleView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
private func setupView() {
backgroundColor = .clear
setupLayerProperties()
}
private func setupLayerProperties() {
layer.cornerRadius = bounds.width / 2
layer.masksToBounds = true
}
}
У методі setupView() задаються початкові властивості: прозорий фон, налаштування шару. Якщо в'ю буде відображатися в Interface Builder, варто додати @IBDesignable та @IBInspectable для live-прев'ю.
Custom UIView керується системою через послідовність методів життєвого циклу, які викликаються в певному порядку. Розуміння цього циклу критично важливе для коректного налаштування та малювання в'ю.
| Метод | Коли викликається | Призначення |
|---|---|---|
| init(frame:) | Створення в'ю з коду | Ініціалізація властивостей, додавання сабв'ю |
| init(coder:) | Завантаження з Storyboard/XIB | Десеріалізація та початкове налаштування |
| layoutSubviews() | При зміні фрейму | Перерахунок геометрії дочірніх елементів |
| draw(_:) | При першій появі або після setNeedsDisplay() | Малювання вмісту через Core Graphics |
| didMoveToSuperview() | Після додавання в ієрархію | Фінальне налаштування, запуск анімацій |
Всі методи викликаються автоматично системою, і розробнику не потрібно викликати їх вручну. Виняток — setNeedsDisplay(), який сигналізує системі про необхідність повторного виклику draw(_:).
draw(_:) — ключовий метод для кастомного малювання в Custom UIView. Всередині нього розробник отримує доступ до CGContext (графічного контексту) та може малювати лінії, фігури, текст і зображення засобами Core Graphics.
Система викликає draw(_:) автоматично при першій появі в'ю на екрані. Повторний виклик ініціюється через setNeedsDisplay(), який позначає в'ю як таку, що потребує перемалювання. Важливо: не викликайте draw(_:) напряму — це ламає механізм кешування та знижує продуктивність.
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
// Заповнення фону
context.setFillColor(UIColor.systemBlue.cgColor)
context.fill(rect)
// Малювання кола
context.setStrokeColor(UIColor.white.cgColor)
context.setLineWidth(4.0)
let circleRect = rect.insetBy(dx: 20, dy: 20)
context.strokeEllipse(in: circleRect)
}
У цьому прикладі draw(_:) заливає фон синім кольором та малює біле коло з відступом 20 пікселів від країв. Кожен виклик draw(_:) має бути ідемпотентним — багаторазовий виклик з тими самими параметрами має давати однаковий результат.
Apple рекомендує мінімізувати роботу всередині draw(_:) — створюйте UIBezierPath заздалегідь, кешуйте зображення та не виконуйте важких обчислень. Якщо в'ю статичне, розгляньте використання UIImageView з відрендереним зображенням замість постійного перемалювання.
CALayer — це нижчий шар, який керує візуальним вмістом UIView. Багато завдань кастомного малювання можна вирішити через налаштування властивостей CALayer без перевизначення draw(_:), що значно продуктивніше.
За даними Apple Engineering (2024), операції на рівні CALayer виконуються на GPU, тоді як draw(_:) працює через CPU-рендеринг Core Graphics. Для анімацій та плавних переходів краще використовувати CALayer та CABasicAnimation.
| Сценарій | Рекомендований підхід | Продуктивність |
|---|---|---|
| Скруглені кути | layer.cornerRadius | GPU, висока |
| Тіні та градієнти | CAGradientLayer, shadowPath | GPU, висока |
| Довільні фігури | CAShapeLayer з UIBezierPath | GPU, висока |
| Складна графіка | draw(_:) з Core Graphics | CPU, середня |
| Текст з кастомним форматуванням | CATextLayer або draw(_:) | Залежить від об'єму |
Використовуйте CAShapeLayer для малювання векторних фігур з анімацією — він апаратно прискорений та підтримує анімацію path, strokeStart і strokeEnd без виклику draw(_:).
Продуктивність Custom UIView напряму впливає на плавність анімацій та загальне враження від застосунку. Основні проблеми виникають через надмірні виклики draw(_:), неоптимальне компонування сабв'ю та відсутність кешування.
Кожен виклик setNeedsDisplay() призводить до повного перемалювання в'ю. Використовуйте setNeedsDisplay(_:) із зазначенням конкретного прямокутника, якщо зміни торкнулися лише частини в'ю. Для CALayer-властивостей (backgroundColor, cornerRadius, shadow) перемалювання не потрібне — вони оновлюються на рівні GPU.
Якщо вміст Custom UIView змінюється рідко, відмалюйте його один раз у UIGraphicsImageRenderer та збережіть як UIImage. При наступному перемалюванні використовуйте draw(at:) для відображення кешованого зображення — це в десятки разів швидше за повторне малювання через Core Graphics.
func renderToImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(size: bounds.size)
return renderer.image { ctx in
drawHierarchy(in: bounds, afterScreenUpdates: true)
}
}
Властивість shouldRasterize у CALayer вмикає кешування растрового представлення шару. Вмикайте його для статичних в'ю з прозорістю та тінями — це знижує навантаження на композитинг. Вимикайте для анімованих в'ю: при кожній зміні кеш скидається, і растеризація лише погіршує продуктивність.
Поширені запитання
Ні, draw(_:) потрібен тільки при кастомному малюванні через Core Graphics. Якщо в'ю збирається зі стандартних сабв'ю (UILabel, UIImageView) та використовує CALayer, перевизначати draw(_:) не потрібно — це навіть покращить продуктивність.
Помістіть звичайний UIView на канву, в інспекторі Identity Inspector вкажіть ваш клас у полі Class. Якщо клас позначено @IBDesignable, зміни відображатимуться в реальному часі прямо в Storyboard.
init(frame:) викликається при програмному створенні в'ю — ви передаєте CGRect з позицією та розміром. init(coder:) викликається при десеріалізації зі Storyboard або XIB. Для коректної роботи обидва мають бути реалізовані, інакше ваша в'ю впаде при завантаженні з Interface Builder.
Найчастіша причина — в'ю має нульовий фрейм (ширина або висота дорівнюють нулю). Система не викликає draw(_:) для в'ю з нульовими розмірами. Перевірте фрейм у layoutSubviews() та переконайтеся, що в'ю додана в ієрархію з коректними констрейнтами.
Використовуйте CALayer для властивостей, що підтримують анімацію на GPU (position, opacity, transform). Для часткового оновлення draw(_:) застосовуйте setNeedsDisplay(_:) з CGRect області змін — система перемалює тільки вказану область, а не всю в'ю цілком.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також