setNeedsDisplay(): メソッドの本質とiOSでの再描画メカニズム

著者: IT Sectr 公開日: 2026-07-20 読了時間: 6 分

setNeedsDisplay() はUIViewのインスタンスメソッドで、ビューを再描画が必要としてマークします。draw(_:)を直接呼び出すのとは異なり、setNeedsDisplay()はビューのdirtyフラグを設定するだけで、即座に制御を返します。システムはこのリクエストを最も近いレンダリングサイクルで処理し、複数の呼び出しを1つの描画操作に結合できます。Apple Documentation(2025)によると、setNeedsDisplay()による遅延再描画は、カスタムUIViewのコンテンツを更新する唯一の正しい方法です。

重要ポイント

  • setNeedsDisplay() — UIViewの遅延再描画リクエスト、draw(_:)を同期的に呼び出さない
  • dirtyフラグ — setNeedsDisplay()で設定され、draw(_:)の後にクリアされる内部ビューフラグ
  • setNeedsDisplay(_:) — 指定された領域のみの部分再描画のためのCGRectオーバーロード
  • displayIfNeeded() — 強制同期的再描画、即時更新に使用
  • レンダリングサイクル — システムはrun loopの終わりにdirtyビューを処理し、複数のリクエストを1つのパスに結合する

setNeedsDisplay()とは

setNeedsDisplay() はUIViewのメソッドで、ビューのコンテンツが古くなり再レンダリングが必要であることをシステムに通知します。このメソッドはパラメーターを取らず、voidを返します。呼び出し後、ビューはdirtyとしてマークされ、最も近い描画サイクルでシステムがこのビューのdraw(_:)を呼び出します。

重要な特徴は非同期性です。同じビューに対する複数の連続したsetNeedsDisplay()呼び出しは、複数のdraw(_:)呼び出しにはなりません — システムはそれらを1つの操作に結合します。これはパフォーマンスにとって非常に重要です:ビューのプロパティが1フレームで複数回変更されても、draw(_:)は1回だけ呼び出されます。

遅延再描画の仕組み

遅延再描画のメカニズムはUIKitのレンダリングサイクルに基づいています。setNeedsDisplay()が呼び出されると、システムは内部フラグlayer.needsDisplayをtrueに設定します。現在のrun loopの終わりに、システムはすべてのdirtyレイヤーを反復処理し、それぞれに対してdraw(_:)を呼び出します。

Run loopとレンダリングステップ

UIKitのrun loopには、dirtyビューが処理される更新フェーズが含まれています。このフェーズでは、必要に応じてlayoutSubviews()が呼び出され、次にマークされたすべてのビューに対してdraw(_:)が呼び出されます。描画が完了すると、結果はCore Animationを介してGPUに渡され、コンポジティングが行われます。

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を更新し、各セッターが自動的にsetNeedsDisplay()を呼び出します。システムはrun loopの終わりを待って、実際のprogress値でdraw(_:)を呼び出します。progressが1フレームで3回変更されても、draw(_:)は最後の値で1回だけ実行されます。

setNeedsDisplay() vs setNeedsDisplay(_:)

setNeedsDisplay(_:)メソッドはCGRect型のrectパラメーターを取り、再描画領域を制限します。これにより、ビュー全体ではなく、変更された部分のみを再描画できます。パフォーマンスの違いは、変更が小さな領域に影響する大きなビューを扱う場合に顕著になります。

特性setNeedsDisplay()setNeedsDisplay(_:)
パラメーターなしCGRect — 再描画領域
再描画領域ビュー全体のbounds指定された矩形のみ
パフォーマンス大きなビューでは低い部分的な変更では高い
draw(_:)のrectパラメーター全体のbounds指定された矩形
使用時コンテンツ全体の変更部分的な変更(カーソル、ハイライト)

複雑なグラフィックの場合、最初にシーン全体をオフスクリーンコンテキストに描画し、その後変更された領域に対してのみsetNeedsDisplay(_:)を呼び出すことをお勧めします。これはiOSの描画アプリケーションやグラフィックエディターでの標準的なテクニックです。

displayIfNeeded() vs setNeedsDisplay()

displayIfNeeded()は同期メソッドで、run loopの終わりを待たずに階層内のすべてのdirtyビューの再描画を即座に開始します。フラグのみを設定するsetNeedsDisplay()とは異なり、displayIfNeeded()はdraw(_:)の即時実行を強制します。

Appleは、描画結果が即座に必要な極端な場合にのみdisplayIfNeeded()を使用することを推奨しています — 例えば、スクリーンショットを撮る前や、UIGraphicsImageRendererの同期画像生成時などです。通常のシナリオでは、メインスレッドをブロックしないsetNeedsDisplay()による非同期モデルが推奨されます。

swift
    // 非同期 — 推奨
someView.setNeedsDisplay()

    // 同期 — 必要な場合のみ
someView.setNeedsDisplay()
someView.displayIfNeeded() // draw(_:) が即座に呼ばれる

事前のsetNeedsDisplay()なしでdisplayIfNeeded()を呼び出しても効果はありません — このメソッドは既にdirtyとしてマークされたビューのみを処理します。再描画が必要なビューがない場合、displayIfNeeded()はdraw(_:)を呼び出さずに完了します。

setNeedsDisplay()呼び出しの最適化

頻繁なsetNeedsDisplay()呼び出しは、特にアニメーション中にパフォーマンスを低下させる可能性があります。主な最適化戦略を見てみましょう。

モデルによる変更のバッチ化

個々のプロパティが変更されるたびにsetNeedsDisplay()を呼び出す代わりに、変更を蓄積し、すべての更新を適用した後にメソッドを1回呼び出します。例えば、要素の色、サイズ、位置を変更する場合 — すべてのプロパティを設定した後にsetNeedsDisplay()を1回呼び出します。

アニメーション可能プロパティへのCALayerの使用

プロパティがCALayer(backgroundColor、opacity、position、transform)を介してアニメーション可能な場合、draw(_:)の代わりにCore Animationを使用します。CALayerプロパティはdraw(_:)を呼び出さずにGPUで更新され、CPU負荷なしで60 FPSを提供します。

CATransactionによるバッチ更新

複数の変更を1つのレンダリングパスにグループ化するには、CATransactionを使用します。これにより、トランザクションの最後に複数のsetNeedsDisplay()呼び出しを1つのdraw(_:)操作に結合できます。

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

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

CATransaction.commit()

displayIfNeeded()を多用しないでください — 同期再描画はメインスレッドをブロックし、フレーム落ちの原因になります。現在のrun loopが終了する前に描画結果が重要な操作にのみ使用してください。

よくある質問

setNeedsDisplay()の代わりに直接draw(_:)を呼び出せますか?

いいえ、draw(_:)を直接呼び出すことは禁止されています。setNeedsDisplay()はビューを正しくdirtyとしてマークし、システムはレンダリングサイクルの適切なタイミングでdraw(_:)を呼び出します。draw(_:)を直接呼び出すとキャッシュメカニズムが無視され、一貫性のない状態になる可能性があります。

setNeedsDisplay()を複数回呼び出すとどうなりますか?

同じビューに対するsetNeedsDisplay()の複数回の呼び出しは、システムによって1つのdraw(_:)呼び出しに結合されます。これは、メソッドがdirtyフラグを設定するだけで、run loopの終わりにdraw(_:)が実際に実行された後にのみクリアされるためです。

setNeedsDisplay()とsetNeedsLayout()の違いは?

setNeedsDisplay()はビューのコンテンツの再描画を要求します(draw(_:)の呼び出し)。setNeedsLayout()はサブビューの再レイアウトを要求します(layoutSubviews()の呼び出し)。layoutSubviews()はサイズ変更を引き起こす可能性があり、それが再描画をトリガーすることがあります。

いつsetNeedsDisplay(_:)を使用すべきですか?

setNeedsDisplay(_:)は部分的な変更に使用すべきです:カーソルの移動、選択領域の色の変更、グラフの一部の更新など。これにより再描画領域が制限され、ビュー全体を再描画するよりもパフォーマンスが向上します。

frameの変更は自動的にsetNeedsDisplay()を呼び出しますか?

デフォルトでは、frameやboundsの変更はsetNeedsDisplay()を呼び出しません。システムは単にビューを移動します。サイズ変更時に再描画が必要な場合は、contentModeプロパティを.redrawに設定してください — そうするとUIKitがboundsが変更されるたびに自動的にsetNeedsDisplay()を呼び出します。

まとめ

  • setNeedsDisplay() — dirtyフラグを設定して遅延再描画を要求するUIViewの非同期メソッド
  • setNeedsDisplay(_:) — 指定された領域のみの部分再描画のためのCGRectオーバーロード、パフォーマンスを最適化
  • displayIfNeeded() — dirtyビューの即時再描画のための同期メソッド、絶対に必要な場合のみ使用
  • Run loop — システムは現在のサイクルの終わりにdirtyビューを処理し、複数の呼び出しを1つのdraw(_:)パスに結合
  • CALayer — アニメーション可能なプロパティ(position、opacity)にはdraw(_:)の代わりにCore Animationを使用 — 桁違いに効率的
  • CATransaction — 複数の変更を1つのレンダリングサイクルにグループ化し、draw(_:)呼び出しを削減
  • contentMode = .redraw — ビューのbounds変更時に自動的にsetNeedsDisplay()を呼び出す

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください