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 за преглед на живо.
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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също