---
title: 'Sunum Biçimleri'
source: 'https://academia.sh/tr/kurslar/uikit/sunum-bicimleri'
course: 'UIKit ve Köprüleme'
language: tr
updated: '2026-10-21T05:03:16+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'İtmeli sunum ile kip sunum arasındaki farkı ekranın nereye konduğu üzerinden ayırır'
    - 'Kip sunumda girilen değerin modele yazılıp alttaki ekrana yazılmadığı durumu ölçer'
    - 'Alttaki ekranın mevcut alanlarının sunum sırasında uyumlu kaldığını gösterir'
    - 'Ölçümün yalnız görünür ekrana baktığını ve kapatılan ekranı saymadığını ayırır'
---

# Sunum Biçimleri

Bir ekranın üzerine kip sunumla açılan not ekranında girilen değerin modele yazıldığı hâlde alttaki ekrana ulaşmadığının ve ekranda eksik kaldığının ölçümü.

Önceki derste ekranlar bir yığıtta üst üste konuyordu. Bir ekranı göstermenin bir yolu daha vardır: mevcut ekranın üzerine **kip sunum** (modal presentation) olarak açmak. Kip sunum yığıta eklemez; sunulan ekran, sunan ekranın üzerinde durur ve kapatılınca yerini boş bırakır. Yığıta ekleme ise **itmeli sunum** diye ayrılır.

Bu ayrım ölçüm açısından önemlidir, çünkü kip sunumda iki ekran arasında hazır bir bağ yoktur. Sunulan ekranın girdiği değer, sunan ekrana kendiliğinden ulaşmaz. Bu derste girişin modele yazıldığı ama sunan ekranın onu hiç göstermediği bir durumu ölçeriz.

## Sunmak ve Kapatmak

Aşağıdaki parça bir görev panosunu ve üzerine açılan bir not ekranını gösterir. Platform çerçevesini içe aldığı için çalıştırılmaz.

```swift
import UIKit

final class TaskBoardController: UIViewController {
    let store: TaskStore
    private let titleLabel = UILabel()
    private let openLabel = UILabel()

    init(store: TaskStore) {
        self.store = store
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }

    override func viewDidLoad() {
        super.viewDidLoad()
        titleLabel.text = store.values["title"] ?? ""
        openLabel.text = store.values["open"] ?? ""
    }

    func addNote() {
        let controller = NoteController(store: store)
        controller.modalPresentationStyle = .pageSheet
        present(controller, animated: true)
    }
}

final class NoteController: UIViewController {
    let store: TaskStore
    private let titleLabel = UILabel()
    private let noteLabel = UILabel()

    init(store: TaskStore) {
        self.store = store
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }

    override func viewDidLoad() {
        super.viewDidLoad()
        titleLabel.text = store.values["title"] ?? ""
    }

    func save(_ text: String) {
        store.update("note", text)
        noteLabel.text = text
        dismiss(animated: true)
    }
}
```

`addNote` sunum biçimini seçer ve not ekranını üste açar; `save` hem modele hem kendi etiketine yazar ve kendini kapatır. Panonun denetleyicisinde `note` anahtarını okuyan hiçbir satır yoktur. Sunum biçimi, yani sayfa mı yoksa tam ekran mı olacağı, sunan ekranın kapatmadan sonra yeniden görünme yöntemlerinin çağrılıp çağrılmayacağını da etkileyebilir. Bu ders bunu ölçmez ve ona güvenmez.

## Sunum Biçimleri

Gezinme yığıtına eklemenin yanında ikinci yol, bir denetleyiciyi başka bir denetleyicinin üstünde **sunmaktır**. Kip sunum, yeni ekranı mevcut ekranın üstüne açar ve kullanıcı kapatana kadar altındaki ekranla etkileşimi keser. Sunum biçimi `modalPresentationStyle` özelliğiyle seçilir: `fullScreen` ekranı baştan sona kaplar, `pageSheet` altındaki ekranı kısmen açıkta bırakan bir sayfa gösterir.

```swift
import UIKit

final class TaskBoardController: UIViewController {
    let store: TaskStore

    init(store: TaskStore) {
        self.store = store
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }

    func addNote() {
        let note = NoteController(store: store)
        note.modalPresentationStyle = .pageSheet
        present(note, animated: true)
    }
}

final class NoteController: UIViewController {
    let store: TaskStore

    init(store: TaskStore) {
        self.store = store
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }

    func saveTapped() {
        store.update("note", "Aranacak")
        dismiss(animated: true)
    }
}
```

Sunan denetleyici `present` çağırır, sunulan denetleyici kendini `dismiss` ile kapatır. Sunulan ekran kapandığında sunan ekran yalnızca görünür hale gelir; sunulan denetleyicinin ürettiği sonuç sunana kendiliğinden taşınmaz.

## Ölçü

Çekirdek önceki derslerle aynıdı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)")
    }
}
```

## Not Kaydedilince Pano

Model iki denetleyiciyi saf Swift ile kurar. Model, kodun ekrana ne yazdığını kurar; çerçevenin kendisi değildir. `present` ve `dismiss` yalnız hangi ekranın üstte olduğunu kaydeder. Olaylar şunlardır: pano yüklenir, not ekranı sunulur ve yüklenir, kullanıcı bir not kaydeder ve not ekranı kapanır.

```swift
// main.swift — pano, kip sunum, not
final class TaskStore {
    private(set) var values: [String: String] = [:]
    func update(_ key: String, _ value: String) { values[key] = value }
    func remove(_ key: String) { values[key] = nil }
}

final class TaskBoardController {
    let store: TaskStore
    let screen = Screen("TaskBoardController")
    private(set) var presented: String?

    init(store: TaskStore) { self.store = store }

    func viewDidLoad() {
        screen.set("title", store.values["title"] ?? "")
        screen.set("open", store.values["open"] ?? "")
    }

    func present(_ name: String) { presented = name }
    func dismiss() { presented = nil }
}

final class NoteController {
    let store: TaskStore
    let screen = Screen("NoteController")

    init(store: TaskStore) { self.store = store }

    func viewDidLoad() {
        screen.set("title", store.values["title"] ?? "")
    }

    func save(_ text: String) {
        store.update("note", text)
        screen.set("note", text)
    }
}

let store = TaskStore()
store.update("title", "Rapor")
store.update("open", "4")
let board = TaskBoardController(store: store)
board.viewDidLoad()
let loaded = check("yuklendi", model: store.values, screen: board.screen, keys: ["title", "open"])

let note = NoteController(store: store)
board.present("NoteController")
note.viewDidLoad()
let presented = check("not-sunuldu", model: store.values, screen: note.screen, keys: ["title"])

note.save("Ara")
board.dismiss()
let saved = check("not-kaydedildi", model: store.values, screen: note.screen, keys: ["note"])
let closed = check("not-kapandi", model: store.values, screen: board.screen, keys: ["title", "open", "note"])

countVerdicts(loaded + presented + saved + closed)
```

```
yuklendi | TaskBoardController.title: model=Rapor ekran=Rapor -> uyumlu
yuklendi | TaskBoardController.open: model=4 ekran=4 -> uyumlu
not-sunuldu | NoteController.title: model=Rapor ekran=Rapor -> uyumlu
not-kaydedildi | NoteController.note: model=Ara ekran=Ara -> uyumlu
not-kapandi | TaskBoardController.title: model=Rapor ekran=Rapor -> uyumlu
not-kapandi | TaskBoardController.open: model=4 ekran=4 -> uyumlu
not-kapandi | TaskBoardController.note: model=Ara ekran=- -> eksik
uyumlu: 6
bayat: 0
eksik: 1
artik: 0
```

Not ekranı kendi alanında uyumlu: kullanıcı girdiğini ekranında görüyor ve model aynı değeri taşıyor. Pano ise `title` ve `open` alanlarında uyumlu kaldı, çünkü onları sunum boyunca kimse değiştirmedi. `note` alanı modele yeni girdi ve panonun ekranı onu hiç yazmadı; satır `eksik` çıktı. Sunum kapandığında pano kendiliğinden güncellenmez, ve hangi bilginin kimde olduğunu bilen kod yoktur.

Kip sunum, panoyu çalışır hâlde bırakır ama panoya haber vermez. Haberin ulaşması için sunan ekranın bir araya girmesi gerekir; bunun bilinen yolu bir temsilci protokolüdür. Bu protokol açılış dersinde klasör seçici için kuruldu ve burada aynı biçimde kullanılabilir. Bu derste ölçülen şey eksikliğin kendisidir.

**GD1.** Modelde `save` sunulan ekranın kendi etiketini yazar ve `dismiss` yalnız üstteki ekranı kaldırır. Gerçek çerçevede sunum biçimine göre kapatma sırasında sunan ekranın hangi yaşam döngüsü yöntemlerinin çağrıldığı ölçülmedi; panonun hiçbirinde `note` yazmadığı varsayılır, bu yüzden sonuç çağrılardan bağımsızdır.

## Sık Yapılan Hatalar

- **Sonucun sunana ulaştığını varsaymak.** Sunulan ekran modeli değiştirir, ama sunan ekran, yeniden göründüğünde yazma çağrısı yoksa eski değeri gösterir. Bu dersin ölçtüğü eksik sonucu budur.
- **Sunulan ekranın sunanı gizleyeceğini beklemek.** `pageSheet` gibi kısmi biçimlerde alttaki ekran yaşam döngüsü açısından kalkmış sayılmaz; `viewWillAppear` dönüşte çağrılmayabilir. Tam ekran biçimde ise çağrılır. Güncellemeyi bu yöntemlere bağlayan kod, biçim değişince sessizce çalışmaz hale gelir.
- **Kapatma düğmesine güvenmek.** Sayfa biçiminde kullanıcı ekranı aşağı çekerek de kapatabilir. Kapatmaya bağlı bir kaydetme çağrısı yalnız düğmede duruyorsa bu yoldan atlanır; kapatma `isModalInPresentation` ile engellenebilir ya da çıkış bir temsilciyle ele alınır.
- **Bir sunum sürerken ikincisini başlatmak.** Çerçeve bunu reddeder ve sunum yapılmaz.
- **Sunulan denetleyiciyi sunan tutarken geri çağrı bağını güçlü kurmak.** Temsilci zayıf tutulmazsa iki denetleyici birbirini serbest bırakmaz.

## Ölçümü Okumak

Yedi satırın altısı uyumlu, biri eksiktir. Eksik satır, not kaydedilip ekran kapatıldıktan sonra panoda görünmeyen `note` alanıdır: model notu taşıyor, pano ekranı onu hiç yazmadı. Kayıt yapılmıştır; kaybolan, sonucun panoya taşınmasıdır.

Bu sonuçta uyumlu satırların bir anlamı vardır: not ekranının kendi alanları, sunulduğu sürece modelle uyumludur. Hata sunan ve sunulan ekran arasındaki sınırda ortaya çıkmıştır. Kip sunum, iki ekranı ayrı yaşam döngülerine koyar ve aralarındaki taşıma programcıya bırakılır. Çözüm iki ekranı da modelden okutmak, ya da sunulan ekran kapanınca sunanın alanları yeniden yazmasını sağlamaktır; hangisinin seçileceği ekranın yapısına bağlıdır.

## Sonucu Sunana Taşımak

Sunulan ekranın sonucunu sunana ulaştırmanın olağan yolu bir temsilci protokolüdür. Sunulan denetleyici protokolü çağırır, sunan denetleyici onu uygular. Temsilci referansı zayıf tutulur, çünkü sunan denetleyici sunulanı zaten tutar ve ters yönde güçlü bir bağ bir tutma döngüsü kurardı.

```swift
import UIKit

protocol NoteControllerDelegate: AnyObject {
    func noteController(_ controller: NoteController, didSave text: String)
}

final class NoteController: UIViewController {
    weak var delegate: NoteControllerDelegate?

    func saveTapped() {
        delegate?.noteController(self, didSave: "Aranacak")
        dismiss(animated: true)
    }
}

extension TaskBoardController: NoteControllerDelegate {
    func noteController(_ controller: NoteController, didSave text: String) {
        store.update("note", text)
        // pano alanını burada da yaz
    }
}
```

Temsilci çağrısı iki işi bir araya getirir: sonucu sunana teslim eder ve sunanın ekranı yazması için bir yer açar. Çağrıyı yapan taraf temsilciyi atamadıysa çağrı sessizce atlanır; bu, ilk derste gördüğümüz bağlanmamış temsilci durumunun aynısıdır.

## Sunum Biçimi ve Kapatma Yolları

Tam ekran biçimde sunan ekran görünümden kalkar ve sunulan kapanınca yeniden görünür; sayfa biçiminde ise alttaki ekran arkada görünmeye devam eder. Bu fark, yaşam döngüsü çağrılarının sunanda ne zaman çalışacağını değiştirir. Alttaki ekranın güncellemesini dönüş çağrısına bağlamak biçim değişince bozulur; güncellemeyi temsilci çağrısına bağlamak ise biçimden bağımsızdır.

Kullanıcı sayfayı aşağı çekerek kapattığında düğmenin eylemi çalışmaz. Kaydetme çağrısı yalnız düğmede duruyorsa bu yol kaydı atlar. `isModalInPresentation = true` yapmak aşağı çekmeyi engeller ve kullanıcıyı açık çıkışlara, yani düğmelere yönlendirir. Bir uyarı kutusu da aynı sunum çağrısıyla gösterilir; uyarı kapanınca sunanın ekranı yine kendi yazma çağrısına bağlıdır.

Sunulan denetleyiciye veri vermek gerektiğinde, veri sunmadan önce, `present` çağrısından önce atanır. Çağrıdan sonra atamak, sunulanın ilk gösteriminde boş veriyle açılmasına yol açabilir.

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

Ölçüm yalnız görünür ekrana bakar. Kapatılan not ekranının nesnesi bellekte durabilir ve içinde eski bir değer taşıyabilir, ama o ekran artık gösterilmediği için denetlenmez. Bu, ekranın ölçüm dışında olduğu anlamına gelir; olcum-disi bir alan değildir.

```swift
// main.swift — kapatilan ekran denetlenmez
final class TaskStore {
    private(set) var values: [String: String] = [:]
    func update(_ key: String, _ value: String) { values[key] = value }
    func remove(_ key: String) { values[key] = nil }
}

final class TaskBoardController {
    let screen = Screen("TaskBoardController")
    private(set) var presented: String?
    func present(_ name: String) { presented = name }
    func dismiss() { presented = nil }
}

final class NoteController {
    let store: TaskStore
    let screen = Screen("NoteController")

    init(store: TaskStore) { self.store = store }

    func save(_ text: String) {
        store.update("note", text)
        screen.set("note", text)
    }
}

let store = TaskStore()
let board = TaskBoardController()
let note = NoteController(store: store)
board.present("NoteController")
note.save("Ara")
board.dismiss()
store.remove("note")
print("sunulan: \(board.presented ?? "yok")")
print("kapanan ekranda kalan: \(note.screen.shown["note"] ?? "-")")
let visible = check("gorunen-pano", model: store.values, screen: board.screen, keys: ["note"])
countVerdicts(visible)
```

```
sunulan: yok
kapanan ekranda kalan: Ara
gorunen-pano | TaskBoardController.note: model=- ekran=- -> olcum-disi
uyumlu: 0
bayat: 0
eksik: 0
artik: 0
```

Not ekranı kapandıktan sonra model notu sildi. Kapanan ekranın nesnesinde `Ara` hâlâ duruyor; çekirdek bu ekranı denetlemiş olsaydı satır artık verirdi. Ama sunulan ekran `yok` ve denetlenen tek ekran pano. Ölçüm bu yüzden sıfır sayı verir ve kapanan ekranın sapmasını raporlamaz. Ekranın ölçümden çıkması, ekranın modelle uyuştuğu anlamına gelmez.

## Özet

- Kip sunum sunulan ekranı sunan ekranın üzerine koyar ve yığıta eklemez; kapatılınca sunan ekran olduğu gibi kalır.
- Sunulan ekranın girdiği değer modele yazılabilir ama sunan ekrana kendiliğinden ulaşmaz.
- Sunan ekranın hiç yazmadığı bir alan, modele girdiği anda eksik olur.
- Sunum sırasında değişmeyen alanlar sunan ekranda uyumlu kalır.
- Ölçüm yalnız görünür ekrana bakar; kapatılan ekran sayıma girmez.
- Bu dersin uyumu: **eksik**.

## Sonraki Adım

Şimdiye kadar ekranları kodla kurduk: etiketler denetleyicinin alanlarıydı. Pek çok proje ekranı bir görsel düzenleyicide çizer ve etiketleri dosyadan bağlar. Sıradaki derste görsel düzenleyici ile kodla kurulan arayüzü karşılaştıracağız ve bağlantısı kopan bir etiketin sessizce yazılmadan kaldığı durumu ölçeceğiz.
