---
title: 'Çizim ve Katman Animasyonu'
source: 'https://academia.sh/tr/kurslar/uikit/cizim-ve-katman-animasyonu'
course: 'UIKit ve Köprüleme'
language: tr
updated: '2026-10-21T05:03:15+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Elle çizilen bir görünümün yeniden çizim isteği olmadan eski değerde kaldığını ayırır'
    - 'Yeniden çizim istenmediğinde modelin değiştiği ama ekranın eski kaldığı durumu bayat olarak ölçer'
    - 'Katman animasyonunda model katmanının son değeri hemen taşıdığını, sunum katmanının ara değer gösterdiğini ayırır'
    - 'Animasyon süresinin modelde karşılığı olmadığını, ölçümün yalnız model katmanını kurduğunu doğrular'
---

# Çizim ve Katman Animasyonu

draw(_:) ile çizilen bir halkanın yeniden çizim istenmediğinde bayat kaldığının, katman animasyonunda model katmanı yazılmadığında değerin eski haline döndüğünün ve sürenin ölçülmediğinin ölçümü.

Önceki derste, Temsilci Kalıbı, bir bağın kopmasının ekranı sessizce bayat bıraktığını ölçtük. Bu derste kopma, bir görünümün kendi yüzeyiyle ilgili. Bazı görünümler hazır bileşenlerle kurulmaz; kendi içeriğini kendisi **çizer** (drawing). Çizen kod yalnız çağrıldığında koşar, ve çağrılması için bir **yeniden çizim isteği** gerekir. Model değişse de istek gelmediği sürece çizim eski kalır.

Ölçüm çekirdek dosyayla yapılır.

```swift
// Sync.swift — ekranda gosterilen her alani modelin o anki degeriyle karsilastirir
final class Screen {
    let name: String
    private(set) var shown: [String: String] = [:]

    init(_ name: String) { self.name = name }

    func set(_ key: String, _ value: String) { shown[key] = value }
    func clear(_ key: String) { shown[key] = nil }
}

func verdict(model: String?, shown: String?) -> String {
    switch (model, shown) {
    case (nil, nil): return "olcum-disi"
    case (nil, _?): return "artik"
    case (_?, nil): return "eksik"
    case let (wanted?, displayed?): return wanted == displayed ? "uyumlu" : "bayat"
    }
}

func check(_ label: String, model: [String: String], screen: Screen, keys: [String]) -> [String] {
    keys.map { key in
        let result = verdict(model: model[key], shown: screen.shown[key])
        print("\(label) | \(screen.name).\(key): model=\(model[key] ?? "-") ekran=\(screen.shown[key] ?? "-") -> \(result)")
        return result
    }
}

func countVerdicts(_ verdicts: [String]) {
    for name in ["uyumlu", "bayat", "eksik", "artik"] {
        print("\(name): \(verdicts.filter { $0 == name }.count)")
    }
}
```

Çekirdek, ekranda gösterilen her alanı modelin o anki değeriyle karşılaştırır. Bu derste ekrandaki değer, çizimin en son çizdiği değerdir. Model, kodun ekrana ne yazdığını kurar; çerçevenin kendisi değildir.

## Çizim Yeniden İstenmedikçe Eski Kalır

Bir yükleme ekranında ilerleme halkası olsun. Halkanın dolu yayı yüzde değerine göre çizilir. Görünüm, çizimini `draw(_:)` yöntemini yeniden tanımlayarak yapar; yöntem, görünüm yeniden çizilmesi gerektiğinde çağrılır.

```swift
import UIKit

final class ProgressRingView: UIView {
    var progress: Int = 0

    override func draw(_ rect: CGRect) {
        let path = UIBezierPath(arcCenter: CGPoint(x: rect.midX, y: rect.midY),
                                radius: rect.width / 2,
                                startAngle: -.pi / 2,
                                endAngle: -.pi / 2 + 2 * .pi * CGFloat(progress) / 100,
                                clockwise: true)
        path.stroke()
    }
}
```

`progress` bir sayıdır ve `draw(_:)` onu okur. Ama `progress` değiştiğinde `draw(_:)` kendiliğinden çağrılmaz. Görünümün yeniden çizilmesi için `setNeedsDisplay()` çağrılmalıdır; bu çağrı çerçeveye bir **yeniden çizim isteği** (redraw request) bırakır ve çizim sonraki turda koşar. Ölçümde üç adım vardır: halka çizilir, model yüzdeyi değiştirir, istek yapılır.

```swift
// main.swift — yeniden cizim istenmedikce halka eski degerde kalir
let ring = Screen("ProgressRing")
var model = ["progress": "40"]

ring.set("progress", "40")
let drawn = check("ilk-cizim", model: model, screen: ring, keys: ["progress"])

model["progress"] = "70"
let stale = check("model-degisti", model: model, screen: ring, keys: ["progress"])

ring.set("progress", "70")
let redrawn = check("yeniden-cizim-istendi", model: model, screen: ring, keys: ["progress"])

countVerdicts(drawn + stale + redrawn)
```

```
ilk-cizim | ProgressRing.progress: model=40 ekran=40 -> uyumlu
model-degisti | ProgressRing.progress: model=70 ekran=40 -> bayat
yeniden-cizim-istendi | ProgressRing.progress: model=70 ekran=70 -> uyumlu
uyumlu: 2
bayat: 1
eksik: 0
artik: 0
```

`ilk-cizim` adımında uyumlu. `model-degisti` adımında model `70`, ekran `40` ve sonuç `bayat`: halka eski yüzdeyi çizmeye devam ediyor. `yeniden-cizim-istendi` adımında çizim yeniden koştu ve satır uyumlu. Sayım iki uyumlu, bir bayat.

KAYNAK tarafta istek, değerin kendi içinde de verilebilir. Özelliğe bir gözlemci eklenir ve değer her değiştiğinde istek bırakılır.

```swift
import UIKit

extension ProgressRingView {
    func update(to percent: Int) {
        progress = percent
        setNeedsDisplay()
    }
}
```

Bu biçimde `update(to:)` çağıran her yol isteği de bırakmış olur. Burada ölçülen, çizimin kendisi değil, isteğin yazılıp yazılmadığıdır.

**DZ1.** Bu ölçüm yeniden çizim isteğini ekrana yazma adımıyla temsil eder. İsteğin çerçevede bir sonraki turda mı koştuğu ya da iki isteğin birleşip birleşmediği ölçülmedi.

## Model Katmanı ve Sunum Katmanı

Bir görünümün altında bir **katman** (layer) vardır. Katman animasyonları, bir değerin eskisinden yenisine ara değerlerle geçmesini sağlar. Burada iki katman karşımıza çıkar: **model katmanı** değerin son halini hemen taşır, **sunum katmanı** ise ekranda o an görünen ara değeri gösterir. Animasyon eklenir, ama model katmanının değeri yazılmazsa animasyon bitince katman eski değerine döner.

```swift
import UIKit

func fadeIn(_ ring: UIView) {
    let animation = CABasicAnimation(keyPath: "opacity")
    animation.fromValue = 0
    animation.toValue = 1
    animation.duration = 0.4
    ring.layer.add(animation, forKey: "fade")
}

func fadeInAndCommit(_ ring: UIView) {
    ring.layer.opacity = 1
    fadeIn(ring)
}
```

`fadeIn` yalnız animasyonu ekler; model katmanındaki `opacity` eski kalır. `fadeInAndCommit` ise önce model katmanını son değerine yazar, sonra animasyonu ekler. Model burada yalnız model katmanını kurar: animasyonun ortasında ekranda görünen ara değer ölçülmez. İki adımı ölçelim.

```swift
// main.swift — animasyon eklenip model katmani yazilmazsa deger eski haline doner
let layer = Screen("RingLayer")
let model = ["ringOpacity": "1"]

layer.set("ringOpacity", "0")
let added = check("animasyon-eklendi", model: model, screen: layer, keys: ["ringOpacity"])

layer.set("ringOpacity", "1")
let committed = check("model-katmani-yazildi", model: model, screen: layer, keys: ["ringOpacity"])

countVerdicts(added + committed)
```

```
animasyon-eklendi | RingLayer.ringOpacity: model=1 ekran=0 -> bayat
model-katmani-yazildi | RingLayer.ringOpacity: model=1 ekran=1 -> uyumlu
uyumlu: 1
bayat: 1
eksik: 0
artik: 0
```

İlk adımda model `1`, ekran `0` ve sonuç `bayat`: animasyon eklendi, ama katmanın değeri yazılmadı. İkinci adımda model katmanı yazılınca satır uyumlu. Sayım bir uyumlu, bir bayat. Animasyonun ortasında sunum katmanının gösterdiği ara değer bu iki satırın hiçbirinde yok; çıktı yalnız animasyon bittikten sonra katmanda kalan değeri karşılaştırır.

**DZ2.** Bu ölçüm model katmanını tek bir değer olarak temsil eder. Sunum katmanının ara değerleri, animasyonun tamamlanma anı ve animasyonun iptal edilmesi ölçülmedi.

## Çizimi Çerçeve Çağırır

Elle çizilen görünümde `draw(_:)` yordamını kodunuz hiçbir zaman doğrudan çağırmaz. Çağıran çerçevedir ve bunu görünümün çizime ihtiyacı olduğu işaretlendiğinde, ekran sonraki turu hazırlarken yapar. Bu işaretleme `setNeedsDisplay()` ile yapılır. Çağrı hemen çizmez; yalnız bir istek bırakır. Aynı turda birden çok istek gelirse çerçeve bunları birleştirir ve çizimi tek kez yapar. Bu yüzden bir döngüde yüz kez `setNeedsDisplay()` çağırmak yüz çizim demek değildir.

İsteğin kapsamını daraltmak da mümkündür. `setNeedsDisplay(_:)` bir dikdörtgen alır ve yalnız o bölgenin yeniden çizilmesini ister; `draw(_:)` yordamının aldığı `rect` parametresi o bölgeyi taşır. Büyük bir görünümde yalnız küçük bir parça değiştiyse çizim maliyeti bu sayede düşer.

## Yerleşim ile Çizim Ayrı İsteklerdir

`setNeedsDisplay()` çizim içindir; boyut ya da konum değişikliği ise `setNeedsLayout()` ile istenir. İkisini karıştırmak sık yapılan bir hatadır. Halkanın yüzdesi değişince çizimin yenilenmesi gerekir, boyutu değişmez; bu yüzden doğru çağrı `setNeedsDisplay()`'dir. Bir kısıtın sabiti değişince ise çizim değil yerleşim gerekir. Görünümün sınırları değiştiğinde çizim gerekip gerekmediğini `contentMode` belirler: varsayılan değer görünümü esnetir, `redraw` değeri ise her sınır değişikliğinde çizimi yeniden ister.

```swift
import UIKit

final class RingContainer: UIView {
    var progress: Int = 0 {
        didSet { setNeedsDisplay() }
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        // Boyut degisti, yerlesim guncellendi; cizim ayri bir istektir.
    }
}
```

`didSet` gözlemcisi, değer her değiştiğinde isteği kendiliğinden bırakır. Böylece isteği unutmak imkansız hale gelir. Gözlemci yerine ayrı bir güncelleme yordamı yazılırsa, o yordamı çağırmayan her yol bayat bırakır.

## Katman Animasyonunda İki Ağaç

Görünümün altındaki katman iki ayrı katman ağacıyla çalışır. Model katmanı kodun yazdığı değeri taşır. Sunum katmanı ise ekranda o an görünen değeri taşır ve animasyon sırasında ara değerleri gezer. Sunum katmanına `presentation()` yordamıyla erişilir ve değerleri okunabilir, ama yazılmaz.

Animasyon eklendiğinde model katmanı değişmez. Animasyon yalnız sunum katmanını sürer; bittiğinde sunum katmanı model katmanının değerine döner. Bu yüzden animasyonun sonunda kalmasını istediğiniz değer önce model katmanına yazılmalıdır. Animasyonu bitiş durumunda tutmak için `isRemovedOnCompletion` ve `fillMode` ayarları da vardır, ama bunlar model ile sunum arasındaki ayrımı gizler ve sonraki okumada yanlış değer verir. Doğru yol, model katmanını önce yazıp animasyonu başlangıç değerinden başlatmaktır.

## Çizim mi Hazır Bileşen mi

Elle çizim her ekranda gerekmez. Etiket, görsel ve düğme gibi hazır bileşenler metni, resmi ve dokunmayı kendisi çizer. `draw(_:)` yordamı, ilerleme halkası, grafik ya da özel bir rozet gibi hazır bileşenin karşılamadığı biçimlerde kullanılır. Çizim yolu sabit ve değer yalnız bir sayıysa, katman nesnelerinden oluşan bir çözüm de mümkündür: bir şekil katmanı yolu bir kez kurar ve yüzde değeri yalnız bir özelliği değiştirerek güncellenir. Bu, her değişimde tüm görünümü yeniden çizmekten daha ucuzdur ve katman animasyonuyla doğal biçimde birleşir.

Seçimin ölçü eksenine etkisi şudur: elle çizimde ekranın değeri çizimin son çalıştığı andaki değerdir ve isteğin bırakılması bir yazma adımı sayılır. Katman çözümünde ise ekranın değeri doğrudan katmandadır.

## Sık Yapılan Hatalar

- `draw(_:)` yordamını doğrudan çağırmak. Geçerli bir çizim bağlamı yoktur ve sonuç çizilmez.
- `setNeedsDisplay()` yerine yerleşim istemek. Yerleşim çizimi yenilemez.
- Animasyon ekleyip model katmanını yazmamak. Animasyon bitince katman eski değerine döner; bu dersin ikinci ölçümündeki bayat satırdır.
- Sunum katmanına yazmak. Değerler sonraki turda silinir.
- `draw(_:)` içinde yeni nesne yaratmak. Her çizimde ayrılan bellek akıcılığı bozar; çizim yolu ve renkler dışarıda hazırlanır.
- Çizim içinde durum değiştirmek. Çizim yalnız okur; durumu yazan çağrı çizimi yeniden istediğinde döngü oluşur.

## Ölçülen Sonucun Yorumu

İlk ölçümde yüzde `40` iken halka çizildi ve uyumluydu. Model `70` olunca ekran `40` kaldı ve satır bayat oldu; çizim isteği bırakılınca satır uyumlu döndü. Aradaki fark tek bir çağrıdır, ama çıktıda yalnız ekranın son değeri görünür. İkinci ölçümde aynı yapı katman değerinde tekrarlandı: animasyon eklendi, model katmanı yazılmadı ve sonuç bayat oldu. Üçüncü ölçüm sürenin modelde olmadığını gösterdi; iki katman farklı sürelerle aynı son değere vardığı için ikisi de uyumlu çıktı. İki ölçüm birbirini tamamlar. Çizimde yazılması gereken şey bir istektir, animasyonda yazılması gereken şey bir değerdir; ikisi de yazılmazsa ekran modelin gerisinde kalır.

Pratikte iki yoklama yeterlidir. Elle çizilen her değer için, değeri yazan her yolun çizim isteğini de bıraktığından emin olunur; en güvenli biçim değerin kendisine bir gözlemci bağlamaktır. Animasyonlu her değer için ise animasyonu eklemeden önce model katmanına son değerin yazıldığı doğrulanır. İkisi de koda bakarak görülebilir, ama ekranda yalnız test sırasında, belirli bir değişimden sonra ortaya çıkar. Ölçüm bu iki yoklamayı birer satıra çevirir. Bu yaklaşım, ekranın modelin gerisinde kalmasına yol açan iki yaygın nedeni kod incelemesinde yakalanabilir hale getirir; çünkü aranan şey her iki durumda da tek bir satırdır ve satırın yokluğu, ekranda görünmeden önce de fark edilir. Üstelik bu denetimler ekran sayısıyla büyümez; her yeni ekran aynı iki soruyla incelenir ve cevap her seferinde koda bakılarak verilir.

## Sınırlayıcı Ölçüm

Çizim ve animasyon zamanla ilgilidir, ama bu dersin ölçüsü zamanı kapsamaz. **Model katmanı son değeri hemen taşır, sunum katmanı ara değer gösterir; model yalnız model katmanını kurar.** Animasyonun süresi modelde bir alan değildir.

Aynı son değeri hızlı ve yavaş iki animasyonla yazdığımızı düşünelim. KAYNAK parçada süre bir parametreydi; modelde karşılığı yoktur, bu yüzden iki ekran aynı sonucu verir.

```swift
// main.swift — animasyon suresi modelde yok, iki katman ayni sonucu verir
let model = ["ringOpacity": "1"]
let fast = Screen("FastRing")
let slow = Screen("SlowRing")

fast.set("ringOpacity", "1")
slow.set("ringOpacity", "1")

let fastResult = check("kisa-sure", model: model, screen: fast, keys: ["ringOpacity"])
let slowResult = check("uzun-sure", model: model, screen: slow, keys: ["ringOpacity"])

countVerdicts(fastResult + slowResult)
```

```
kisa-sure | FastRing.ringOpacity: model=1 ekran=1 -> uyumlu
uzun-sure | SlowRing.ringOpacity: model=1 ekran=1 -> uyumlu
uyumlu: 2
bayat: 0
eksik: 0
artik: 0
```

İki satır uyumlu, sayım iki uyumlu. Sürenin farkı çıktıya girmedi. Bu bir sınırdır: model, ekrana hangi değerin yazıldığını söyler; o değere ne kadar sürede varıldığını söylemez.

**DZ3.** Bu ölçüm iki katmanın aynı son değeri taşımasını sınar. Kare süresi ya da animasyon eğrisine dair bir sayı yazılmadı.

## Özet

- Elle çizilen görünüm, çizimini yalnız yeniden çizim isteği geldiğinde yenilenir; istek yazılmazsa ekran bayat kalır.
- İstek yazılınca çizim yeniden koşar ve ekran uyumlu olur.
- Katman animasyonunda model katmanı son değeri hemen taşır, sunum katmanı ara değer gösterir.
- Animasyon eklenip model katmanı yazılmazsa değer eski haline döner ve ekran bayat kalır.
- Animasyonun süresi modelde yoktur; aynı son değer iki süreyle de aynı uyumu verir.
- Bu dersin uyumu: **bayat**.

## Sonraki Adım

Bu konuda arayüzü buyurgan biçimde kurduk: kısıtlar, hücreler ve çizim kodla yönetildi. Sıradaki konu, köprüleme, buyurgan ve bildirimsel arayüzü aynı ekranda bir araya getirecek. Barındırma denetleyicisi ve sarmalama, iki tarafın birbirine değer aktarmasını sağlayacak; ekranın modelle uyumunu orada da aynı çekirdekle ölçeceğiz.
