setNeedsDisplay(): суштина методе и механизам прецртавања у iOS-у

Аутор: IT Sectr Објављено: 2026-07-20 Време читања: 6 мин

setNeedsDisplay() — ово је метода инстанце UIView која означава приказ као да захтева поновно цртање. За разлику од директног позива draw(_:), setNeedsDisplay() само поставља dirty заставицу за приказ и одмах враћа контролу. Систем обрађује овај захтев у најближем циклусу рендеровања, што омогућава обједињавање више позива у једну операцију цртања. Према Apple Documentation (2025), одложено прецртавање путем setNeedsDisplay() је једини исправан начин ажурирања садржаја прилагођеног UIView-а.

Главне тачке

  • setNeedsDisplay() — одложени захтев за прецртавање UIView-а, не позива draw(_:) синхроно
  • dirty flag — унутрашња заставица приказа коју поставља setNeedsDisplay() и ресетује након draw(_:)
  • setNeedsDisplay(_:) — преоптерећење са CGRect-ом за делимично прецртавање само наведене области
  • displayIfNeeded() — принудно синхроно прецртавање, користи се за тренутно ажурирање
  • Циклус рендеровања — систем обрађује dirty приказе на крају run loop-а, обједињавајући више захтева у један пролаз

Шта је setNeedsDisplay()

setNeedsDisplay() — је UIView метода која сигнализира систему да је садржај приказа застарео и захтева поновни рендер. Метода не прихвата параметре и враћа void. Након позива, приказ се означава као dirty, и у најближем циклусу цртања систем позива draw(_:) за овај приказ.

Кључна карактеристика је асинхроност. Више узастопних позива setNeedsDisplay() за исти приказ неће довести до вишеструких позива draw(_:) — систем их обједињује у једну операцију. Ово је критично за перформансе: ако се својство приказа промени неколико пута по кадру, draw(_:) се позива само једном.

Како функционише одложено прецртавање

Механизам одложеног прецртавања заснива се на циклусу рендеровања UIKit-а. Када се setNeedsDisplay() позове, систем поставља унутрашњу заставицу layer.needsDisplay на true. На крају текућег run loop-а, систем пролази кроз све dirty слојеве и позива draw(_:) за сваки.

Run loop и кораци рендеровања

UIKit run loop укључује фазу update, током које се обрађују dirty прикази. У овој фази, layoutSubviews() се позива ако је потребно, затим draw(_:) за све означене приказе. Након завршетка цртања, резултат се преноси на GPU путем Core Animation-а за композитовање.

swift
class ProgressView: UIView {
    var progress: CGFloat = 0 {
        didSet {
            setNeedsDisplay()
        }
    }

    override func draw(_ rect: CGRect) {
        super.draw(rect)
        guard let ctx = UIGraphicsGetCurrentContext() else { return }

        // Позадина
        ctx.setFillColor(UIColor.lightGray.cgColor)
        ctx.fill(rect)

        // Трака напретка
        let fillRect = CGRect(x: rect.minX, y: rect.minY,
                              width: rect.width * progress, height: rect.height)
        ctx.setFillColor(UIColor.systemGreen.cgColor)
        ctx.fill(fillRect)
    }
}

У овом примеру, progressView ажурира progress, и сваки setter аутоматски позива setNeedsDisplay(). Систем ће сачекати крај run loop-а и позвати draw(_:) са тренутном вредношћу progress. Ако се progress промени три пута у једном кадру, draw(_:) ће се извршити само једном — са последњом вредношћу.

setNeedsDisplay() наспрам setNeedsDisplay(_:)

Метода setNeedsDisplay(_:) прихвата параметар rect типа CGRect, који ограничава област прецртавања. Ово омогућава прецртавање само измењеног дела приказа, а не целог. Разлика у перформансама постаје приметна при раду са великим приказима, где промене утичу на малу област.

КарактеристикаsetNeedsDisplay()setNeedsDisplay(_:)
ПараметарНемаCGRect — област прецртавања
Област прецртавањаЦео bounds приказаСамо наведени правоугаоник
ПерформансеНиже код великих приказаВише код делимичних измена
Параметар rect у draw(_:)Цео boundsНаведени правоугаоник
Када користитиПромена целог садржајаТачкасте измене (курсор, highlight)

За сложену графику, препоручује се прво цртање целе сцене у offscreen контексту, а затим позивање setNeedsDisplay(_:) само за измењену област. Ово је стандардна техника у апликацијама за цртање и графичким уређивачима на iOS-у.

displayIfNeeded() против setNeedsDisplay()

displayIfNeeded() — је синхрона метода која одмах покреће прецртавање свих dirty приказа у хијерархији, не чекајући крај run loop-а. За разлику од setNeedsDisplay(), која само поставља заставицу, displayIfNeeded() форсира тренутно извршење draw(_:).

Apple препоручује коришћење displayIfNeeded() само у крајњим случајевима, када је резултат цртања потребан одмах — на пример, пре креирања снимка екрана или приликом синхроног генерисања слике за UIGraphicsImageRenderer. У уобичајеним сценаријима, асинхрони модел путем setNeedsDisplay() је пожељнији, јер не блокира главну нит.

swift
    // Асинхроно — препоручено
someView.setNeedsDisplay()

    // Синхроно — само када је потребно
someView.setNeedsDisplay()
someView.displayIfNeeded() // draw(_:) позвано одмах

Позив displayIfNeeded() без претходног setNeedsDisplay() неће имати ефекта — метода обрађује само већ означене као dirty приказе. Ако ниједан приказ не захтева прецртавање, displayIfNeeded() се завршава без позивања draw(_:).

Оптимизација позива setNeedsDisplay()

Чести позиви setNeedsDisplay() могу смањити перформансе, посебно током анимације. Размотримо главне стратегије оптимизације.

Обједињавање измена путем модела

Уместо позивања setNeedsDisplay() при свакој промени појединачног својства, акумулирајте измене и позовите методу једном након примене свих ажурирања. На пример, при промени боје, величине и позиције елемента — позовите setNeedsDisplay() једном након постављања свих својстава.

Коришћење CALayer-а за анимирана својства

Ако својство може бити анимирано путем CALayer-а (backgroundColor, opacity, position, transform), користите Core Animation уместо draw(_:). CALayer својства се ажурирају на GPU-у без позивања draw(_:), што обезбеђује 60 FPS без оптерећења CPU-а.

Групно ажурирање путем CATransaction-а

За груписање више измена у један пролаз рендеровања користите CATransaction. Ово омогућава обједињавање више setNeedsDisplay() позива у једну draw(_:) операцију на крају трансакције.

swift
CATransaction.begin()
CATransaction.setDisableActions(true)

view1.setNeedsDisplay()
view2.setNeedsDisplay()
view3.setNeedsDisplay()

CATransaction.commit()

Не злоупотребљавајте displayIfNeeded() — синхроно прецртавање блокира main thread и може изазвати пад кадрова. Користите га само за операције где је резултат цртања критичан пре завршетка текућег run loop-а.

Често постављана питања

Може ли се draw(_:) позвати директно уместо setNeedsDisplay()?

Не, директни позив draw(_:) је забрањен. setNeedsDisplay() исправно означава приказ као dirty, а систем позива draw(_:) у правом тренутку циклуса рендеровања. Директни позив draw(_:) игнорише механизам кеширања и може довести до неконзистентног стања.

Шта се дешава при вишеструким позивима setNeedsDisplay()?

Више позива setNeedsDisplay() за исти приказ систем обједињује у један draw(_:) позив. Ово се дешава зато што метода само поставља dirty flag, која се ресетује тек након стварног извршења draw(_:) на крају run loop-а.

По чему се setNeedsDisplay() разликује од setNeedsLayout()-а?

setNeedsDisplay() захтева прецртавање садржаја приказа (позив draw(_:)). setNeedsLayout() захтева прераспоред subview-а (позив layoutSubviews()). layoutSubviews() може довести до промене димензија, што заузврат може изазвати прецртавање.

Када користити setNeedsDisplay(_:)?

setNeedsDisplay(_:) треба користити при тачкастим изменама: померање курсора, промена боје означене области, ажурирање дела графика. Ово ограничава област прецртавања и повећава перформансе у поређењу с комплетним прецртавањем целог приказа.

Да ли промена frame-а аутоматски позива setNeedsDisplay()?

Подразумевано, промена frame-а или bounds-а не позива setNeedsDisplay(). Систем само помера приказ. Ако је потребно прецртавање при промени величине, поставите својство contentMode на вредност .redraw — тада ће UIKit аутоматски позивати setNeedsDisplay() при свакој промени bounds-а.

Закључак

  • setNeedsDisplay() — асинхрона UIView метода за захтев одложеног прецртавања путем постављања dirty заставице
  • setNeedsDisplay(_:) — преоптерећење са CGRect-ом за делимично прецртавање само наведене области, оптимизује перформансе
  • displayIfNeeded() — синхрона метода за тренутно прецртавање dirty приказа, користи се само када је крајње потребно
  • Run loop — систем обрађује dirty приказе на крају текућег циклуса, обједињујући вишеструке позиве у један draw(_:) пролаз
  • CALayer — за анимирана својства (position, opacity) користите Core Animation уместо draw(_:) — ово је далеко ефикасније
  • CATransaction — групише више измена у један циклус рендеровања, смањујући број draw(_:) позива
  • contentMode = .redraw — укључује аутоматско позивање setNeedsDisplay() при промени bounds-а приказа

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође