---
title: 'Sunum ve Kapatma'
source: 'https://academia.sh/tr/kurslar/swiftui/sunum-ve-kapatma'
course: 'SwiftUI ile Arayüz'
language: tr
updated: '2026-10-21T05:03:14+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Sunum durumunun sunan görünümde tutulan yerel bir durum olduğunu ölçüyle gösterir'
    - 'Sayfa içeriğinin sunum anahtarını bağlamayla okuduğunda ebeveyn tetiği aldığını ayırır'
    - 'Kapatma eyleminin ortamdan gelen bir eylem olduğunu, değer olmadığını açıklar'
    - 'Kapanmanın sunanın durumunu değiştirdiğini ve bu yüzden sunanı yeniden hesaplattığını ölçer'
---

# Sunum ve Kapatma

Sunum durumunun sunan görünümde durduğunun, sayfa içeriğinin bağlamayla bu durumu okuduğunun ve kapatma eyleminin bir değer olmadığının ölçümü.

Gezinme yığıtı yeni bir ekranı yığıta ekler ve mevcut ekranın yerine geçirir; sunum ise yeni ekranı mevcut ekranın üzerine geçici olarak açar. Bir **sayfa** (sheet), bir **tam ekran tabaka** ya da bir **uyarı** (alert) kendi başına var olmaz: açık olup olmadığını bir değer söyler ve bu değer, tıpkı gezinme yolu gibi, bir saklama yerinde durur. Bu derste bakılan tetik yine **yerel**: sunum durumunu tutan görünüm, değerin sahibi olarak yeniden hesaplanır. Ders ayrıca kapatmanın nasıl işlediğine bakar, çünkü kapatma eylemi başka bir saklama yerinden, ortamdan gelir.

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

```swift
// Invalidation.swift — bir degisikligin hangi gorunumu hangi kaynak yoluyla yeniden hesaplattigini siniflandirir
enum Storage {
    case state(owner: String)
    case model(object: String)
    case environment
}

struct ViewNode {
    let name: String
    let parent: String?
    let reads: Set<String>

    init(_ name: String, parent: String?, reads: Set<String>) {
        self.name = name
        self.parent = parent
        self.reads = reads
    }
}

struct Recompute {
    let view: String
    let cause: String
}

struct ViewTree {
    let nodes: [ViewNode]
    let storage: [String: Storage]

    func ancestors(of name: String) -> [String] {
        var found: [String] = []
        var current = nodes.first { $0.name == name }?.parent
        while let next = current {
            found.append(next)
            current = nodes.first { $0.name == next }?.parent
        }
        return found
    }

    func cause(of node: ViewNode, key: String) -> String {
        guard let kind = storage[key] else { return "olcum-disi" }
        switch kind {
        case .environment:
            return "ortam"
        case .model:
            return "gozlem"
        case .state(let owner):
            if owner == node.name { return "yerel" }
            if ancestors(of: node.name).contains(owner) { return "ebeveyn" }
            return "kural-disi"
        }
    }

    func change(_ label: String, key: String) -> [Recompute] {
        let hits = nodes.filter { $0.reads.contains(key) }
            .map { Recompute(view: $0.name, cause: cause(of: $0, key: key)) }
        let skipped = nodes.filter { !$0.reads.contains(key) }.map(\.name)
        let recomputed = hits.isEmpty ? "yok" : hits.map { "\($0.view) \($0.cause)" }.joined(separator: ", ")
        let rest = skipped.isEmpty ? "yok" : skipped.joined(separator: ",")
        print("\(label): \(key) degisti | yeniden \(recomputed) | atlanan \(rest)")
        return hits
    }
}

func countCauses(_ recomputes: [Recompute]) {
    for cause in ["yerel", "ebeveyn", "gozlem", "ortam"] {
        print("\(cause): \(recomputes.filter { $0.cause == cause }.count)")
    }
}
```

Çekirdekte sunuma özgü bir tip yoktur. Sunum durumu bir `Bool` ya da bir seçenek değeridir ve modelde bir `state` anahtarı olarak yazılır; sayfanın içeriği bu anahtarı okuyorsa `reads` kümesinde taşır.

## Sunum Durumu Sunanda Durur

Bir sayfa iki parçayla açılır: sunan görünümde tutulan bir `Bool` ve `.sheet(isPresented:content:)` değiştiricisi. Değiştirici bağlamayı alır, değer doğru olduğunda içerik kapanışından bir görünüm kurar ve değer yanlışa dönünce onu kaldırır. Aşağıdaki kaynak parçada sunan `InvoiceScreen`'dir.

```swift
import SwiftUI

struct InvoiceScreen: View {
    @State private var isEditorPresented = false

    var body: some View {
        VStack {
            InvoiceHeader()
            Button("Duzenle") { isEditorPresented = true }
        }
        .sheet(isPresented: $isEditorPresented) {
            InvoiceEditor()
        }
    }
}

struct InvoiceHeader: View {
    var body: some View {
        Text("Fatura 2041")
    }
}
```

Durum `InvoiceScreen`'dedir ve değiştirici de aynı görünümün gövdesine yazılır. `InvoiceHeader` sunuma hiç bağlı değildir. `InvoiceEditor` kapanışın içinde kurulur. Ağaçta sunan ve başlık yer alır; sayfa açıkken içerik de ağaca girer. Önce sunumun kendisini, içerik olmadan ölçeriz: açma ve kapama, yazanı değil, sahibi ilgilendirir.

```swift
// main.swift — sunum durumu sunanda durur, acma ve kapama ayni tetigi uretir
let storage: [String: Storage] = [
    "isEditorPresented": .state(owner: "InvoiceScreen"),
]

let screen = ViewTree(nodes: [
    ViewNode("InvoiceScreen", parent: nil, reads: ["isEditorPresented"]),
    ViewNode("InvoiceHeader", parent: "InvoiceScreen", reads: []),
], storage: storage)

let recomputes = [
    screen.change("sayfa-acildi", key: "isEditorPresented"),
    screen.change("sayfa-kapandi", key: "isEditorPresented"),
].flatMap { $0 }

countCauses(recomputes)
```

```
sayfa-acildi: isEditorPresented degisti | yeniden InvoiceScreen yerel | atlanan InvoiceHeader
sayfa-kapandi: isEditorPresented degisti | yeniden InvoiceScreen yerel | atlanan InvoiceHeader
yerel: 2
ebeveyn: 0
gozlem: 0
ortam: 0
```

İki değişiklik, iki satır, ikisi de yalnız sunanı yeniden hesaplattı. `InvoiceHeader` iki seferde de atlandı: sunum anahtarını okumuyor. Sunum durumu görünümün kendi durumudur ve yerel tetik üretir. Sayım yerel iki, ebeveyn, gözlem ve ortam sıfır.

Bu, ikinci dersin kuralının ve yerel durumun yalın hâlidir. Açık bir sayfa, ağacı genişletir ama genişleme bir yeniden hesaplama sayılmaz; sayfanın içeriği kurulduğunda ilk gövdesini hesaplar, bu kuruluştur.

**DR16.** Bu ölçüm iki değişiklik ve tek bir `Bool` anahtarı için. Seçenek tipli sunumun (`item:` kullanan sayfa) ve aynı görünümde birden çok sunumun birlikte kullanıldığı durum ölçülmedi. Sayfanın ekranda belirme süresi de ölçülmedi.

## Sayfa İçeriği Bağlamayla Okur

Sayfa içeriğinin sunum durumunu okuması yaygındır: bir kapat düğmesi, sunum açıkken değişen bir başlık. Bunun için içerik, durumu bir bağlama olarak alır; böylece hem okur hem yazar. Bu, ikinci dersin ebeveyn tetiğidir: durumun sahibi sunan, okuyan ise sayfanın içeriğidir.

```swift
import SwiftUI

struct InvoiceEditor: View {
    @Binding var isPresented: Bool

    var body: some View {
        VStack {
            Text(isPresented ? "Duzenleniyor" : "Kapaniyor")
            Button("Bitti") { isPresented = false }
        }
    }
}
```

Sunan görünüm artık içeriği `InvoiceEditor(isPresented: $isEditorPresented)` olarak kurar. Ölçümde ağaç üç görünümden oluşur: sunan, başlık ve içerik. Aynı iki değişiklik uygulanır.

```swift
// main.swift — sayfa icerigi sunum anahtarini baglamayla okur
let storage: [String: Storage] = [
    "isEditorPresented": .state(owner: "InvoiceScreen"),
]

let screen = ViewTree(nodes: [
    ViewNode("InvoiceScreen", parent: nil, reads: ["isEditorPresented"]),
    ViewNode("InvoiceHeader", parent: "InvoiceScreen", reads: []),
    ViewNode("InvoiceEditor", parent: "InvoiceScreen", reads: ["isEditorPresented"]),
], storage: storage)

let recomputes = [
    screen.change("sayfa-acildi", key: "isEditorPresented"),
    screen.change("sayfa-kapandi", key: "isEditorPresented"),
].flatMap { $0 }

countCauses(recomputes)
```

```
sayfa-acildi: isEditorPresented degisti | yeniden InvoiceScreen yerel, InvoiceEditor ebeveyn | atlanan InvoiceHeader
sayfa-kapandi: isEditorPresented degisti | yeniden InvoiceScreen yerel, InvoiceEditor ebeveyn | atlanan InvoiceHeader
yerel: 2
ebeveyn: 2
gozlem: 0
ortam: 0
```

Her iki değişiklikte sunan yerel, içerik ebeveyn tetiğiyle yeniden hesaplanıyor; başlık atlanıyor. Sayım yerel iki, ebeveyn iki. İçeriğin ağaçta sunanın çocuğu olarak durması, ebeveyn tetiğinin koşuludur: sahip, okuyanın atasıdır.

Burada bir ayrıntıya dikkat etmek gerekir. Sayfanın içeriği sunanın çocuğu olarak modellenmiştir, çünkü içerik kapanışını sunan kurar. Sayfa ekranda ayrı bir katmanda görünse de durumun sahipliği bakımından ağaçta sunanın altındadır.

**DR17.** Bu ölçüm içeriğin sunanın doğrudan çocuğu olduğu tek bir ağaç için. İçeriğin birkaç kat aşağıda başka görünümlere bağlamayı ilettiği durum ve sayfanın kendi içinde ayrıca bir gezinme yığıtı taşıdığı durum ölçülmedi.

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

Sayfayı kapatmanın iki yolu vardır: içeriğin sunum bağlamasına `false` yazması ya da ortamdan gelen **kapatma eylemini** (`dismiss`) çağırması. İkincisi, `@Environment(\.dismiss)` ile okunur. Bu bir değer değil, çağrılabilir bir eylemdir; çağırmak çerçeveye "beni sunan durumu kapat" der ve çerçeve sunanın durumunu yazar. Eylemin kendisi hiçbir değişikliği tek başına üretmez. Ortamdan eylemi okumak, ortam değerinin değişmesini beklemek anlamına gelmez, çünkü eylemin kendisi sabittir.

```swift
import SwiftUI

struct InvoiceEditor: View {
    @Environment(\.dismiss) private var dismiss

    var body: some View {
        Button("Kapat") { dismiss() }
    }
}
```

Ölçüm kapatma çağrısının gerçekte yaptığını modeller. `dismiss` ağaçta içeriğin okuduğu bir ortam anahtarı olarak yer alır ve değiştirilmez, çünkü eylemin kendisi sabittir; çağrıyla değişen şey sunum anahtarıdır.

```swift
// main.swift — dismiss deger degil eylemdir, kapanma sunanin durumunu degistirir
let storage: [String: Storage] = [
    "dismiss": .environment,
    "isEditorPresented": .state(owner: "InvoiceScreen"),
]

let screen = ViewTree(nodes: [
    ViewNode("InvoiceScreen", parent: nil, reads: ["isEditorPresented"]),
    ViewNode("InvoiceEditor", parent: "InvoiceScreen", reads: ["dismiss"]),
], storage: storage)

let recomputes = [
    screen.change("kapat-cagrildi", key: "isEditorPresented"),
].flatMap { $0 }

countCauses(recomputes)
```

```
kapat-cagrildi: isEditorPresented degisti | yeniden InvoiceScreen yerel | atlanan InvoiceEditor
yerel: 1
ebeveyn: 0
gozlem: 0
ortam: 0
```

Tek satır, kapatma çağrısının sunum anahtarını değiştirmesidir. Bu yalnız sunanı yerel tetikle yeniden hesaplatır; içerik bu anahtarı okumadığı için atlanır. `dismiss` anahtarı değişmediği için ortam sayacı sıfırdır. Sayım yerel bir.

Bu satır dersin sonucunu verir. Kapatma eylemini okumak, içeriği sunum durumuna bağlamaz; çağrı yapıldığında değişen şey ortam değil, sunanın durumudur. Eylem ortamdan geldiği için ağaçta bir ortam anahtarı olarak yer alır, ama çağrının sonucu sunanda görülür. Çağrıyı yapan içerik sunum durumunu okumuyorsa, kapanışın yeniden hesaplamasına da katılmaz.

**DR18.** Ölçüm `dismiss` anahtarını değişmeyen bir ortam anahtarı olarak kurar; çerçevede eylem tanıtıcısının değişip değişmediği ölçülmedi. Kapatma çağrısının sunum durumunu yazma sırası ve animasyonla kapanmanın bitmesini beklemek ölçülmedi.

## Özet

- Sunum durumu, sunan görünümde tutulan bir değerdir; açma ve kapama aynı tetiği üretir.
- Sunan, durumun sahibi olarak yerel, sayfa içeriği bağlamayla okuyorsa ebeveyn tetiğiyle yeniden hesaplanır.
- Sunumla ilgisi olmayan kardeş görünümler atlanır.
- `dismiss` ortamdan okunan bir eylemdir; çağırmak sunanın durumunu değiştirir, eylemin kendisi bir değer değildir.
- Bu dersin tetiği: **yerel**.

## Sonraki Adım

Bu derste sunum tek bir `Bool` ile yönetildi ve sahibi sunan görünümdü. Uygulamaların çoğunda ise ekran birden çok bölüme ayrılır: bir sekme çubuğu ve bir araç çubuğu, hangi bölümün görüneceğini ve hangi eylemlerin sunulacağını söyler. Sıradaki derste seçim kökte durur; bölümlerin içeriği ona bağlamayla erişse de onu okuyup okumadığı ayrıca ölçülür.
