---
title: 'Yığıt Görünümleri'
source: 'https://academia.sh/tr/kurslar/uikit/yigit-gorunumleri'
course: 'UIKit ve Köprüleme'
language: tr
updated: '2026-10-21T05:03:15+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Yığıt görünümünün alt görünümleri dizdiğini, gizlenen alt görünümün düzenden düştüğünü ayırır'
    - 'Modelden kalkan bir alanın ekranda gizlenmediği durumu artık olarak ölçer'
    - 'Gizlenmiş alanın iki tarafta da olmadığında ölçüm dışı kaldığını ve sayılmadığını gösterir'
    - 'Gizlemeyle kaldırmanın modelde aynı görünürlük alanını verdiğini, boyut farkının ölçülmediğini doğrular'
---

# Yığıt Görünümleri

Modelden kalkan bir rozetin yığıt görünümünde gizlenmediği durumun artık, gizleme yazıldığında uyumlu sayıldığının ve gizlemenin modelde görünürlük alanı olarak kurulduğunun ölçümü.

Önceki derste, Kısıt Tabanlı Düzen, bir kısıtın sabitini elle yazmanın ekranın modelden geri kalmasına yol açtığını ölçtük. Kısıtları tek tek kurmak görünüm çoğaldıkça ağırlaşır. **Yığıt görünümü** (stack view) bu yükü azaltır: alt görünümleri bir eksen boyunca dizer ve aralarındaki boşluğu kendisi hesaplar. Bu dersin ölçüsü başka bir yere düşer. Yığıt görünümünde ekrandan bir şey kaldırmak, onu gizlemek demektir ve gizlemeyi kod yapar.

Ö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. Model, kodun ekrana ne yazdığını kurar; çerçevenin kendisi değildir.

## Düzenden Düşen Alt Görünüm

Bir sipariş satırı düşünelim: üstte sipariş başlığı, yanında yeni siparişleri işaretleyen bir rozet. İkisi bir yığıt görünümünde yan yana durur. Rozet modelden kalktığında, yani sipariş artık yeni sayılmadığında, rozetin görünümü yığıtta durmaya devam eder.

```swift
import UIKit

final class OrderRowView: UIView {
    let titleLabel = UILabel()
    let badgeLabel = UILabel()
    let rowStack = UIStackView()

    func configure(title: String, badge: String?) {
        titleLabel.text = title
        badgeLabel.text = badge
    }
}
```

`configure(title:badge:)` rozet yoksa metni boşaltmaz, yalnız yeni değeri atar; ayrıca `badgeLabel.isHidden` ayarlanmaz. Bu yüzden rozet modelden kalktığında ekranda eski metin kalır. Ölçümde üç adım var: sipariş yüklenir, rozet modelden kalkar, sonra ekran rozeti gizler.

```swift
// main.swift — modelden kalkan rozet gizlenmezse artik, gizlenince olcum-disi kalir
let row = Screen("OrderRow")
var model = ["title": "Siparis 1042", "badge": "Yeni"]

row.set("title", "Siparis 1042")
row.set("badge", "Yeni")
let loaded = check("siparis-yuklendi", model: model, screen: row, keys: ["title", "badge"])

model["badge"] = nil
let removed = check("rozet-kalkti", model: model, screen: row, keys: ["title", "badge"])

row.clear("badge")
let hidden = check("rozet-gizlendi", model: model, screen: row, keys: ["title", "badge"])

countVerdicts(loaded + removed + hidden)
```

```
siparis-yuklendi | OrderRow.title: model=Siparis 1042 ekran=Siparis 1042 -> uyumlu
siparis-yuklendi | OrderRow.badge: model=Yeni ekran=Yeni -> uyumlu
rozet-kalkti | OrderRow.title: model=Siparis 1042 ekran=Siparis 1042 -> uyumlu
rozet-kalkti | OrderRow.badge: model=- ekran=Yeni -> artik
rozet-gizlendi | OrderRow.title: model=Siparis 1042 ekran=Siparis 1042 -> uyumlu
rozet-gizlendi | OrderRow.badge: model=- ekran=- -> olcum-disi
uyumlu: 4
bayat: 0
eksik: 0
artik: 1
```

`siparis-yuklendi` adımında iki alan uyumlu. `rozet-kalkti` adımında `title` hâlâ uyumlu; `badge` için model `-`, ekran `Yeni` ve sonuç `artik`. Ekranda, modelin artık karşılığı olmayan bir değer kalmış. `rozet-gizlendi` adımında ekran da `-` olunca satır `olcum-disi` basar: iki tarafta da olmayan alan ne uyumlu ne artık sayılır.

Altı satırın sayımı beşi kapsar: dört uyumlu, bir artık. Altıncı satır `olcum-disi` olduğu için hiçbir sayıya girmez; sayımın dört adından hiçbiri onu içermez.

**DZ1.** Bu ölçüm rozetin ekranda var olup olmadığını anahtarın değeriyle temsil eder. Yığıt görünümünün alt görünümü gerçekten ağaçta tutup tutmadığı ölçülmedi.

## Gizlemek Bir Alandır

Gizleme, alt görünümün `isHidden` özelliğine yazılan bir değerdir. Yığıt görünümü gizli alt görünümü düzenden çıkarır ve kalan görünümler boşluğu kapatır. Yani ekranın "rozet görünüyor mu" bilgisi ayrı bir değer olarak durur ve model bunu bir görünürlük alanı olarak tutabilir.

```swift
import UIKit

extension OrderRowView {
    func showBadge(_ isVisible: Bool) {
        badgeLabel.isHidden = !isVisible
    }
}
```

Modelde `badgeVisible` anahtarı `evet` ya da `hayir` değerini taşır. Model rozeti gizlemeyi istediğinde ve ekran bu çağrıyı almadığında iki taraf ayrışır.

```swift
// main.swift — gorunurluk alani: model gizle der, ekran gizlemezse bayat kalir
let row = Screen("OrderRow")
var model = ["badgeVisible": "evet"]

row.set("badgeVisible", "evet")
let shown = check("rozet-gorunur", model: model, screen: row, keys: ["badgeVisible"])

model["badgeVisible"] = "hayir"
let stale = check("model-gizledi", model: model, screen: row, keys: ["badgeVisible"])

row.set("badgeVisible", "hayir")
let synced = check("ekran-gizledi", model: model, screen: row, keys: ["badgeVisible"])

countVerdicts(shown + stale + synced)
```

```
rozet-gorunur | OrderRow.badgeVisible: model=evet ekran=evet -> uyumlu
model-gizledi | OrderRow.badgeVisible: model=hayir ekran=evet -> bayat
ekran-gizledi | OrderRow.badgeVisible: model=hayir ekran=hayir -> uyumlu
uyumlu: 2
bayat: 1
eksik: 0
artik: 0
```

İkinci satırda model `hayir`, ekran `evet` ve sonuç `bayat`. Görünürlük bir değer olarak durduğunda, onu atlamak artık değil bayat verir: iki tarafta da bir değer var, yalnız eski. Aynı konu iki biçimde ölçüldü ve iki ayrı kategori çıktı. Alan modelden tümüyle kalktığında artık, değeri değiştiğinde bayat.

**DZ2.** Bu ölçüm `evet` ve `hayir` metinleriyle görünürlüğü temsil eder. `isHidden` değerinin bir `Bool` olduğu ve metin karşılaştırmasının kodun ekrana yazdığını yansıttığı varsayıldı.

## Yığıt Görünümünün Dağıtımı

Yığıt görünümü, alt görünümlerini bir eksen boyunca sıralayan ve aralarındaki boşlukları kendisi hesaplayan bir kapsayıcıdır. Eksen `axis` ile seçilir: yatay ya da dikey. Alt görünümlerin eksen boyunca nasıl yayılacağını **dağıtım** (distribution) belirler, eksene dik yöndeki yerleşimi ise **hizalama** (alignment) belirler. Bu ayrım önemlidir, çünkü iki ayar birbirinden bağımsızdır ve en sık karışan nokta da budur.

Dağıtımın beş değeri vardır. `fill` alt görünümlerden birini uzatarak alanı doldurur; hangisinin uzayacağını büyüme dirençleri söyler. `fillEqually` hepsine eşit boyut verir. `fillProportionally` içsel boyutlarla orantılı dağıtır. `equalSpacing` boyutlara dokunmadan aradaki boşlukları eşitler. `equalCentering` ise görünümlerin merkezleri arasındaki uzaklığı eşitler. Hizalama ise `fill`, `leading`, `center`, `trailing` gibi değerlerle dik yöndeki konumu seçer.

```swift
import UIKit

func makeRow(title: String, badge: UIView) -> UIStackView {
    let label = UILabel()
    label.text = title
    let row = UIStackView(arrangedSubviews: [label, badge])
    row.axis = .horizontal
    row.alignment = .center
    row.distribution = .fill
    row.spacing = 8
    return row
}
```

Bu satırda dağıtım `fill` olduğundan etiket ile rozet arasında bir görünümün uzaması gerekir. Hangisinin uzayacağı açıkça söylenmediyse çerçeve içsel boyuta ve dirençlere bakar. Rozeti sabit boyutta tutmak için rozetin büyüme direncini yükseltmek yeterlidir.

## Gizlemek ile Çıkarmak Aynı Şey Değildir

Yığıt görünümünün en kullanışlı davranışı, gizlenen bir alt görünümün yerini kapatmasıdır. `isHidden` değeri `true` yapılan görünüm yerleşimden çıkar ve komşuları boşluğu doldurur. Bu davranış yığıt görünümüne özgüdür; yığıt dışında gizlenen bir görünüm yerini korur.

Çıkarma ise ayrı bir işlemdir. `removeArrangedSubview` çağrısı görünümü yalnız dağıtımdan çıkarır; görünüm hâlâ yığıt görünümünün alt görünümüdür ve çizilmeye devam eder. Ekrandan gerçekten kaldırmak için `removeFromSuperview` gerekir. Bu fark, bu dersin üçüncü ölçümünün konusudur: gizleme ve kaldırma modelde aynı görünürlük değerini yazar, ama boyut ve yerleşim açısından farklı sonuç verir.

```swift
import UIKit

func hide(_ badge: UIView, in row: UIStackView) {
    badge.isHidden = true
}

func remove(_ badge: UIView, from row: UIStackView) {
    row.removeArrangedSubview(badge)
    badge.removeFromSuperview()
}
```

İkinci işlev iki adım atar çünkü yalnız birincisi görünümü ekranda bırakır. Yalnız ikincisi bellekten de çıkarır; görünümü sonra yeniden göstermek gerekirse gizlemek daha ucuzdur.

## Yığıt Görünümü mü Kısıt mı

Yığıt görünümü her düzen sorununa cevap değildir. Alt görünümlerin tek bir eksen boyunca sıralandığı durumlarda, yani formlar, satırlar, düğme çubukları ve etiket listelerinde, yığıt görünümü onlarca kısıtı tek bir ayara indirir. Görünümler üst üste biniyorsa, bir görünüm başka bir görünümün köşesine göre konumlanıyorsa ya da oranlar birbirine bağlıysa, kısıtlar daha doğrudur. İkisini birleştirmek de yaygındır: yığıt görünümünün kendisi kısıtlarla konumlanır, içindeki sıralama yığıt görünümüne bırakılır.

Bu seçim ölçüm açısından da önemlidir. Yığıt görünümü yerleşimi kendisi çözdüğü için model ile ekran arasındaki eşleşme alt görünümün varlığına ve görünürlüğüne iner. Kısıt tabanlı düzende ise her sabitin ayrı ayrı güncellenmesi gerekir. Aynı ekranı iki yoldan kurmak, bayat kalma ihtimali olan değer sayısını da değiştirir.

## Sık Yapılan Hatalar

- Yığıt görünümünün alt görünümü yerine doğrudan ekleme yapmak. Yığıt görünümüne doğrudan `addSubview` ile eklenen görünüm dağıtıma katılmaz.
- Gizleme ile kaldırmayı karıştırmak. Rozet modelden kalktığında yalnız `removeArrangedSubview` çağrılırsa rozet ekranda kalır; bu dersin birinci ölçümündeki artık satırı bu durumdur.
- Dağıtım ile hizalamayı karıştırmak. Dikey yığıtta görünümleri yatayda ortalamak isteyen kişi `distribution` değerini değiştirir ve sonuç alamaz.
- İç içe yığıt görünümlerini gereğinden fazla derinleştirmek. Her katman bir kısıt çözümü daha demektir ve hata ayıklamayı zorlaştırır.
- Aralık bırakmayı alt görünümün kendi kısıtlarına yazmak. Yığıt görünümü bunu `spacing` ile yapar; kısıtla da yazılırsa çelişki doğar.

## Ölçülen Sonucun Yorumu

İlk ölçümde rozet modelde yokken ekranda duruyordu ve satır artık sayıldı. Yığıt görünümü bunu kendiliğinden temizlemez, çünkü kapsayıcı hangi alt görünümün modelle eşleştiğini bilmez. Rozet iki yoldan kalkar: gizlenir ya da çıkarılır. Rozet gizlendiğinde ya da çıkarıldığında satır ölçüm dışı kaldı, çünkü iki tarafta da değer yoktu. Üçüncü ölçümde gizleme ve kaldırma aynı görünürlük değerini yazdı ve ikisi de uyumlu çıktı: ölçü ekseni bir değerin ekranda bulunup bulunmadığıyla ilgilenir, görünümün yerinin kapanıp kapanmadığıyla ilgilenmez. İkinci ölçüm görünürlük alanında başka bir kırılmayı gösterdi: model gizle dediği hâlde ekran gizlemediyse satır bayattır. Üç ölçümü birlikte okuyunca yığıt görünümü ile ilgili tek bir sonuç çıkar: kapsayıcı yerleşimi çözer, ama modelle eşleşmeyi geliştirici sağlar.

Pratikte bu, yığıt görünümü kullanan bir ekranda iki ayrı yoklama gerektirir. Önce modelde olan her öğenin ekranda bir alt görünümü olup olmadığına bakılır; eksik varsa eklenmemiştir. Sonra ekranda olan her alt görünümün modelde karşılığı aranır; karşılığı olmayan, yani artık kalan görünüm gizlenmemiş ya da çıkarılmamıştır. Çıkarma sırasında da dağıtımdan çıkarmak ile görünüm ağacından kaldırmak ayrı adımlardır ve ikisi de yapılmadıkça görünüm ekranda kalır. Yığıt görünümü bu işi bizim yerimize yapmaz, çünkü kapsayıcı hangi alt görünümün hangi model öğesine ait olduğunu bilmez. Bu iki yoklamayı bir yardımcı işleve toplamak, ekranın her güncellenişinde aynı denetimin yapılmasını sağlar ve unutulan durumların sayısını belirgin biçimde azaltır; çünkü kural bir kez yazılır, her ekranda yeniden hatırlanmaz. Ayrıca bu denetim, rozet gibi koşullu görünen öğelerde her güncellemeden sonra çalıştırılırsa bayat ve artık satırlar ilk ortaya çıktıkları anda görünür.

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

Bir alt görünümü yığıttan iki yolla çıkarabiliriz: gizleyebiliriz ya da yığıttan kaldırabiliriz. İkisinin düzende bıraktığı boşluk aynı olmayabilir; gizlenen görünüm yığıtın dizilimine yer ayırmasa da hâlâ ağaçtadır, kaldırılan ise ağaçtan çıkmıştır. **Gizli alt görünüm yığıt düzeninden düşer**; bu, düzenin bir sonucudur ve model onu ölçmez.

Model yalnız görünürlüğü alan olarak kurar. İki yol aynı alana aynı değeri yazar, bu yüzden boyut farkı hakkında bir iddiada bulunulmaz.

```swift
// main.swift — gizleme ve kaldirma ayni gorunurluk degerini yazar, boyut farki olculmez
let model = ["badgeVisible": "hayir"]
let hiddenRow = Screen("HiddenRow")
let removedRow = Screen("RemovedRow")

hiddenRow.set("badgeVisible", "hayir")
removedRow.set("badgeVisible", "hayir")

let hiddenResult = check("gizlendi", model: model, screen: hiddenRow, keys: ["badgeVisible"])
let removedResult = check("kaldirildi", model: model, screen: removedRow, keys: ["badgeVisible"])

countVerdicts(hiddenResult + removedResult)
```

```
gizlendi | HiddenRow.badgeVisible: model=hayir ekran=hayir -> uyumlu
kaldirildi | RemovedRow.badgeVisible: model=hayir ekran=hayir -> uyumlu
uyumlu: 2
bayat: 0
eksik: 0
artik: 0
```

İki satır birebir aynı sonuç: uyumlu. Gizlemeyle kaldırmanın düzende bıraktığı fark çıktıya girmedi, çünkü modelde bir boyut alanı yok. Bu bir sınırdır: model alt görünümün düzenden düşüp düşmediğini söylemez, görünürlük alanının ekranda yazılıp yazılmadığını söyler.

**DZ3.** Bu ölçüm iki ekranın aynı değeri taşımasını sınar. Yığıt görünümünün boşluğu nasıl kapattığına dair bir sayı yazılmadı.

## Özet

- Yığıt görünümü alt görünümleri dizer; gizlenen alt görünüm düzenden düşer ve gizlemeyi kod yazar.
- Modelden kalkan bir alan ekranda gizlenmediyse sonuç artıktır.
- İki tarafta da olmayan alan ölçüm dışıdır ve hiçbir sayıya girmez.
- Görünürlük alan olarak tutulduğunda atlanan gizleme bayat verir, alan tümüyle kalktığında artık.
- Gizlemek ile kaldırmak modelde aynı görünürlük değerini yazar; boyut farkı ölçülmez.
- Bu dersin uyumu: **artık**.

## Sonraki Adım

Yığıt görünümleri alt görünümleri bir kez dizer. Tablo görünümü ise aynı hücreyi çok kez kullanır ve kullanılan hücre önceki satırın izini taşıyabilir. Sıradaki derste hücrenin yeniden kullanımı ve eski satırdan kalan değer ölçülecek.
