Como melhorar a performance de drag-and-drop em apps macOS

Mário
Mário
18 de janeiro de 2026

O problema

Durante operações de drag-and-drop, o sistema precisa atualizar a interface com frequência: destacar alvos de drop, animar elementos, redesenhar células.

Se suas views customizadas usam draw(_ dirtyRect:) para desenhar backgrounds, bordas ou separadores, cada atualização visual dispara uma re-execução desse método na CPU.

O resultado: uso elevado de CPU e possível stuttering durante o drag.


Por que isso acontece

O método draw() é processado na CPU. Cada chamada a needsDisplay = true força o sistema a re-executar todo o código de desenho. Durante drag-and-drop, isso pode acontecer dezenas de vezes por segundo.

// Cada needsDisplay = true re-executa este código na CPU
override func draw(_ dirtyRect: NSRect) {
    super.draw(dirtyRect)

    // Desenho do background
    NSColor.quaternaryLabelColor.withAlphaComponent(0.08).setFill()
    NSBezierPath(roundedRect: bounds, xRadius: 8, yRadius: 8).fill()

    // Desenho do separador
    NSColor.separatorColor.setStroke()
    let path = NSBezierPath()
    path.move(to: NSPoint(x: 12, y: bounds.height - 12))
    path.line(to: NSPoint(x: bounds.width - 12, y: bounds.height - 12))
    path.lineWidth = 1
    path.stroke()
}

Multiplique isso por 42 células em um calendário mensal e você tem um problema.


A solução: CALayer

Propriedades de CALayer são gerenciadas pela GPU via Core Animation. O sistema faz composição, batching e otimizações automáticas. A CPU fica livre para outras tarefas.

init(...) {
    super.init(frame: .zero)

    // Habilita layer-backing
    wantsLayer = true

    // Background via layer (GPU)
    layer?.backgroundColor = NSColor.quaternaryLabelColor
        .withAlphaComponent(0.08).cgColor

    // Corner radius via layer (GPU)
    layer?.cornerRadius = 8
}

override func draw(_ dirtyRect: NSRect) {
    super.draw(dirtyRect)
    // Agora só desenha o que realmente precisa de desenho customizado
}

O que você pode mover para CALayer

+--------------------------------+----------------------------------+
| Antes (CPU)                    | Depois (GPU)                     |
+--------------------------------+----------------------------------+
| NSBezierPath.fill() com cor    | layer?.backgroundColor           |
| NSBezierPath(roundedRect:)     | layer?.cornerRadius              |
| Desenho de bordas              | layer?.borderWidth/borderColor   |
| Sombras manuais                | layer?.shadow*                   |
+--------------------------------+----------------------------------+

Para arredondar apenas alguns cantos, use layer?.maskedCorners:

// Arredonda apenas o canto superior esquerdo
layer?.cornerRadius = 8
layer?.maskedCorners = [.layerMinXMaxYCorner]

Quando manter o draw()

Reserve draw() para formas verdadeiramente customizadas que não podem ser expressas como propriedades de layer:

  • Gráficos vetoriais complexos
  • Gradientes não-lineares
  • Paths com curvas bezier customizadas
  • Desenhos que dependem de dados dinâmicos

Resultado prático

Em um app de calendário com 42 células (6 semanas x 7 dias), a mudança de draw() para CALayer reduziu visivelmente o uso de CPU durante drag-and-drop. O Instruments confirmou: menos tempo em rendering, mais tempo ocioso.

A regra é simples: se você consegue expressar algo como propriedade de layer, faça isso.


Referências