Ders 07 / 15
Kısıt Tabanlı Düzen
Düzeni besleyen kısıt sabitinin modelde değiştiği ama ekrandaki kısıta yazılmadığı durumun bayat, yeni kısıtın hiç kurulmadığı durumun eksik sayıldığının ölçümü.
Yayın Güncelleme
Bu derste
- Kısıt tabanlı düzende ekranın modelden aldığı değerin kısıtın sabiti olduğunu ayırır
- Modelde değişen bir değerin kısıta yazılmadığı durumu bayat olarak ölçer
- Modelde olup hiç kısıt kurulmamış bir alanı eksik olarak ölçer
- Öncelik ve belirsizliğin modelde karşılığı olmadığını, aynı sabitin aynı uyumu verdiğini doğrular
İçindekiler
Önceki konunun son dersi, Bağlantılar ve Eylemler, ekrandaki bir görünümü koda nasıl bağladığımızı anlattı. Bu konuda bağlanan şey görünümün kendisi değil, yerleşimidir. UIKit’te yerleşimi çoğu zaman kısıt (constraint) verir: bir görünümün kenarının ya da genişliğinin başka bir şeye göre nasıl durduğunu söyleyen kural. Kısıt, bir sabit taşır ve bu sabit kodda bir sayıdır. Model o sayıyı değiştirdiğinde ekrandaki kısıt kendiliğinden yeni sayıyı bilmez.
Bu dersin sorusu hesabın doğruluğu değil, uyumdur: model bir boşluğu ya da genişliği değiştirdi, ekran o değeri aldı mı. Ölçüm çekirdek dosyayla yapılır.
// 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)")
}
}
Çekirdekte Screen, ekrana en son yazılan değerleri anahtar başına tutar. verdict modelin değerini ekranın değeriyle karşılaştırır ve dört sonuçtan birini verir: ikisi de aynıysa uyumlu, farklıysa bayat, model değer taşıyıp ekran taşımıyorsa eksik, ekran taşıyıp model taşımıyorsa artik. Çekirdek bir kısıt çözücü değildir; model, kodun ekrana ne yazdığını kurar, çerçevenin kendisi değildir.
Kısıtın Taşıdığı Sabit
Bir fatura kartının kenar boşluğunu ve genişliğini düşünelim. İkisi de kodda birer kısıtın sabitidir. Kart oluşurken kısıtlar bir kez kurulur ve sabitler o anki değerleri alır.
import UIKit
final class InvoiceCardView: UIView {
let paddingConstraint = NSLayoutConstraint()
let widthConstraint = NSLayoutConstraint()
func apply(padding: CGFloat, width: CGFloat) {
paddingConstraint.constant = padding
widthConstraint.constant = width
}
}
Buradaki apply(padding:width:) yöntemi iki sabiti yazar. Kritik nokta, sabitlerin yalnız bu yöntem çağrıldığında değişmesidir. Model sonradan boşluğu büyütse ve yöntem çağrılmasa, kısıt eski sayıyı taşımaya devam eder.
Ölçümde model iki alan taşır: sidePadding ve cardWidth. Ekran, kısıtlara yazılan sabitleri tutan bir Screen’dir. Üç adım verilir: ilk kurulum, modelin boşluğu değiştirmesi, ve sabitin güncellenmesi.
// main.swift — model boslugu degistirir, kisit guncellenmedikce bayat kalir
let invoice = Screen("InvoiceCard")
var model = ["sidePadding": "16", "cardWidth": "320"]
invoice.set("sidePadding", "16")
invoice.set("cardWidth", "320")
let first = check("ilk-kurulum", model: model, screen: invoice, keys: ["sidePadding", "cardWidth"])
model["sidePadding"] = "24"
let second = check("bosluk-degisti", model: model, screen: invoice, keys: ["sidePadding", "cardWidth"])
invoice.set("sidePadding", "24")
let third = check("kisit-guncellendi", model: model, screen: invoice, keys: ["sidePadding", "cardWidth"])
countVerdicts(first + second + third)
ilk-kurulum | InvoiceCard.sidePadding: model=16 ekran=16 -> uyumlu ilk-kurulum | InvoiceCard.cardWidth: model=320 ekran=320 -> uyumlu bosluk-degisti | InvoiceCard.sidePadding: model=24 ekran=16 -> bayat bosluk-degisti | InvoiceCard.cardWidth: model=320 ekran=320 -> uyumlu kisit-guncellendi | InvoiceCard.sidePadding: model=24 ekran=24 -> uyumlu kisit-guncellendi | InvoiceCard.cardWidth: model=320 ekran=320 -> uyumlu uyumlu: 5 bayat: 1 eksik: 0 artik: 0
ilk-kurulum satırlarında iki alan da uyumlu: model ve ekran aynı sayıyı taşıyor. bosluk-degisti satırında sidePadding için model 24, ekran 16 ve sonuç bayat. Ekran hâlâ eski sabiti tutuyor; cardWidth ise hiç değişmediği için uyumlu kalıyor. kisit-guncellendi adımında sabit yazılınca iki alan yeniden uyumlu.
Sayım altı denetimden çıkar: beş uyumlu, bir bayat. Bayat olan tek satır, modelin değiştiği ama kısıta yazılmadığı adımdır. Hiçbir satırda eksik ya da artık yok, çünkü iki tarafta da iki anahtar var.
DZ1. Bu ölçüm sabitin metin olarak eşitliğini karşılaştırır. Kısıtın etkinleştirilip etkinleştirilmediği, başka bir kısıtla çakışıp çakışmadığı ölçülmedi.
Hiç Kurulmayan Kısıt
Bayat, ekranın bir değeri taşıdığı ama eskidiği durumdur. Daha sessiz bir durum, modelin bir değer taşıması ve ekranda bu değeri karşılayan kısıtın hiç kurulmamasıdır. Kartın altına bir dipnot alanı eklendiğini düşünelim: model yüksekliğini söylüyor, ama kod henüz o yükseklik kısıtını oluşturmadı.
// main.swift — model yukseklik verir, kisit kurulmadikca eksik sayilir
let card = Screen("InvoiceCard")
let model = ["sidePadding": "24", "footerHeight": "44"]
card.set("sidePadding", "24")
let before = check("dipnot-eklendi", model: model, screen: card, keys: ["sidePadding", "footerHeight"])
card.set("footerHeight", "44")
let after = check("kisit-kuruldu", model: model, screen: card, keys: ["sidePadding", "footerHeight"])
countVerdicts(before + after)
dipnot-eklendi | InvoiceCard.sidePadding: model=24 ekran=24 -> uyumlu dipnot-eklendi | InvoiceCard.footerHeight: model=44 ekran=- -> eksik kisit-kuruldu | InvoiceCard.sidePadding: model=24 ekran=24 -> uyumlu kisit-kuruldu | InvoiceCard.footerHeight: model=44 ekran=44 -> uyumlu uyumlu: 3 bayat: 0 eksik: 1 artik: 0
dipnot-eklendi adımında footerHeight için model 44, ekran - ve sonuç eksik. Ekranın bu anahtar için hiçbir değeri yok. kisit-kuruldu adımında kısıt yazılınca satır uyumlu oluyor. Toplam üç uyumlu ve bir eksik; bayat yok, çünkü ekranda yanlış bir sayı hiç görünmedi, yalnızca sayı hiç yoktu.
Eksik ile bayat kodda birbirine benzer görünür: ikisinde de kullanıcı yanlış bir yerleşim görür. Ayrım nedeninde yatar. Bayat, yazma adımının atlandığını, eksik ise kurma adımının hiç yazılmadığını söyler.
DZ2. Bu ölçüm, dipnot alanının yalnız tek bir anahtarla temsil edildiği durum için. Bir kısıt grubunun bir kısmının kurulup bir kısmının kurulmadığı ölçülmedi.
Kısıtlar Gerçekte Nasıl Çözülür
Ölçümde bir kısıt tek bir sayıya indirgendi, ama çerçevenin kendisi kısıtları bir denklem sistemi olarak çözer. Her görünümün konumu ve boyutu dört değerle belirlenir: sol kenar, üst kenar, genişlik ve yükseklik. Kısıtlar bu değerler arasındaki ilişkileri yazar. Bir görünüm için bu dört değeri tek anlamlı biçimde belirleyen kısıt kümesi eksiksizdir. Eksik kalırsa belirsizlik (ambiguity) oluşur: çerçeve görünümü iki ya da daha fazla geçerli yere koyabilir ve hangisini seçeceği kuralla belirlenmemiştir. Fazla kalırsa çelişki (conflict) oluşur: iki kısıt aynı değeri farklı istemektedir ve biri çalışma anında kırılır.
Kırılacak kısıtı öncelik (priority) belirler. Öncelik 1 ile 1000 arasında bir sayıdır; 1000 zorunlu kısıttır ve çerçeve onu kırmadan çözüm arar. Daha düşük öncelikli bir kısıt, yüksek öncelikli kısıtlarla çatıştığında gevşer. Çerçeve kırdığı kısıtı çıktıya yazar, ama ekran yine de çizilir; bu yüzden çatışma çoğu zaman yalnız bir günlük satırı olarak kalır ve fark edilmez.
import UIKit
func pinCard(_ card: UIView, in container: UIView) {
card.translatesAutoresizingMaskIntoConstraints = false
let width = card.widthAnchor.constraint(equalToConstant: 320)
width.priority = .defaultHigh
NSLayoutConstraint.activate([
card.topAnchor.constraint(equalTo: container.topAnchor, constant: 16),
card.centerXAnchor.constraint(equalTo: container.centerXAnchor),
card.leadingAnchor.constraint(greaterThanOrEqualTo: container.leadingAnchor, constant: 16),
width
])
}
Bu parçada genişlik için öncelik 750 verilmiştir. Kapsayıcı dardır ve sol kenar kısıtı zorunlu kaldığı sürece genişlik kısıtı gevşer; kapsayıcı genişse 320 olarak uygulanır. Böylece tek bir kısıt kümesi iki duruma da uyar. Dikkat edilecek nokta, translatesAutoresizingMaskIntoConstraints değerinin kapatılmasıdır: kodla kurulan görünümde bu değer açık bırakılırsa çerçeve görünümün eski biçimdeki otomatik kısıtlarını da ekler ve bunlar yazdığınız kısıtlarla çelişir.
İçsel Boyut ve Sıkıştırma Dirençleri
Her görünüm kısıtsız bir boyut da taşıyabilir. Bir etiketin metni kadar, bir düğmenin başlığı kadar yer istemesi buna örnektir; bu isteğe içsel boyut (intrinsic content size) denir. İçsel boyutu olan bir görünüm için genişlik ya da yükseklik kısıtı yazmak çoğu zaman gerekmez. Alan yetmediğinde ya da fazla geldiğinde görünümün ne yapacağını iki değer söyler: büyüme direnci (content hugging) görünümün içsel boyutundan büyümeye ne kadar karşı koyduğunu, sıkışma direnci (compression resistance) küçülmeye ne kadar karşı koyduğunu bildirir. İki etiket yan yana ve alan dar olduğunda, sıkışma direnci düşük olan etiket kısalır; ikisi eşitse çerçeve hangisini seçeceğini bilemez ve belirsizlik doğar.
Bu yüzden yan yana duran iki değişken uzunluklu metin için bir etikete açıkça daha yüksek direnç vermek gerekir. Ölçümdeki FlexibleCard bu esnekliğin modele yansımamasını gösteriyordu: direnç değerleri modelde yoktur, ekranda ise belirleyicidir.
Kısıtı Kodla mı Düzenleyiciyle mi Kurmalı
Kısıt, görsel düzenleyicide de kodda da kurulabilir ve ikisi aynı sonucu verir. Düzenleyici, değişmeyen yapılar için kısadır: kenar boşlukları, sabit yükseklikler ve hizalamalar bir kez çizilir ve dosyada saklanır. Kod ise çalışma anında değişen düzenlerde işe yarar: bir bölümün koşullu görünmesi, içeriğe göre değişen yükseklik ya da kullanıcı ayarına göre kayan boşluk bunlardandır. Kodla kurulan kısıt bir değişkende saklanırsa sonradan constant değeri değiştirilebilir ve çerçeve düzeni sonraki turda yeniden çözer. Bu dersin ilk iki ölçümü tam olarak bu değişkenin güncellenmesi ile ilgilidir. Kararı basit tutmak için şu ölçü yeterlidir: değer kurulduktan sonra değişmeyecekse düzenleyici, model ile birlikte değişecekse kod seçilir.
Karma kullanımda tek kural, bir görünümün kısıtlarının tek kaynaktan gelmesidir. Düzenleyicide kurulmuş bir kısıtı kodda değiştirmek gerekirse, kısıt bir bağlantıyla (outlet) koda bağlanır ve yeni bir kısıt eklemek yerine mevcut kısıtın sabiti güncellenir.
Sık Yapılan Hatalar
- Kısıtı kurup etkinleştirmeyi unutmak. Oluşturulan bir kısıt, etkinleştirilmediği sürece hiçbir şey yapmaz. Etkinleştirme
isActiveile ya daNSLayoutConstraint.activateile yapılır. - Aynı görünüm için iki ayrı yerde kısıt kurmak. Biri kod, biri görsel düzenleyici kaynaklıysa çelişki doğar ve hangisinin kırıldığı çalıştırmaya göre değişebilir.
- Model değişince kısıtın sabitini güncellemeyi unutmak. Bu dersin ilk ölçümü tam bu durumdur: sabit kurulduğu andaki değeri taşır, model sonradan değişse de kendiliğinden izlemez.
- Gizlenen bir görünümün kısıtlarını bırakmak. Görünüm gizlense de kısıtları yerinde kalır ve boşluk korunur; boşluğun kapanmasını istiyorsanız kısıtı ya da yığıt görünümünü kullanmak gerekir.
- Öncelik sayısını tahmini bir değere bağlamak. Bire bir aynı öncelikli iki kısıt çatışırsa sonuç tanımlı değildir; öncelikleri birbirinden net ayırmak gerekir.
Ölçülen Sonucun Yorumu
İlk ölçümün bayat sonucu çerçevenin bir hatası değildir; kısıtın tanımı gereği böyledir. Kısıt bir ilişki değil bir değer olarak saklandığında, ilişkiyi güncelleyecek kodu yazmak geliştiricinin işidir. İkinci ölçümün eksik sonucu da aynı kökten gelir, ama ters yönden: model bir değer verir, ekranda onu taşıyacak kısıt hiç kurulmamıştır. Üçüncü ölçüm ise önceliğin modelde olmadığını gösterir; iki ekranda aynı sabit aynı uyumu verir ve önceliğin farkı çıktıya girmez. Üç sonuçtan çıkarılacak ders şudur: modelle ekranı karşılaştırırken kısıtın değeri görülebilir, ama onun çerçevedeki ağırlığı görülemez.
Sınırlayıcı Ölçüm
Kısıtın iki özelliği, öncelik ve belirsizlik, çözücünün çatışmaları nasıl çözeceğini belirler. Öncelik düşük bir kısıt, gerekirse esner. UIKit’te bu iki özellik koddan okunur.
import UIKit
func makeWidthConstraint(for card: UIView, priority: UILayoutPriority) -> NSLayoutConstraint {
let constraint = card.widthAnchor.constraint(equalToConstant: 320)
constraint.priority = priority
return constraint
}
Modelin böyle bir alanı yok. Model yalnız sabiti karşılaştırır: kısıt zorunlu da olsa, düşük öncelikli de olsa sabit aynıysa sonuç aynıdır. Bunu aynı sabite sahip iki ekranla gösterelim; biri zorunlu, öteki düşük öncelikli bir kısıtla kurulmuş gibi düşünülür. Önceliği taşıyacak bir anahtar yok, bu yüzden ikisi de yalnız cardWidth yazar.
// main.swift — oncelik modelde yok, ayni sabit iki ekranda da ayni uyumu verir
let model = ["cardWidth": "320"]
let required = Screen("RequiredCard")
let flexible = Screen("FlexibleCard")
required.set("cardWidth", "320")
flexible.set("cardWidth", "320")
let requiredResult = check("zorunlu", model: model, screen: required, keys: ["cardWidth"])
let flexibleResult = check("esnek", model: model, screen: flexible, keys: ["cardWidth"])
countVerdicts(requiredResult + flexibleResult)
zorunlu | RequiredCard.cardWidth: model=320 ekran=320 -> uyumlu esnek | FlexibleCard.cardWidth: model=320 ekran=320 -> uyumlu uyumlu: 2 bayat: 0 eksik: 0 artik: 0
İki satır da uyumlu ve sayım iki uyumlu. Önceliğin farkı çıktıda görünmedi. Bu bir sınırdır: kısıt çözücü ölçülmez. Model kısıtın taşıdığı sabiti karşılaştırır, ekranın çözücüden sonra aldığı boyutu değil. Bir kısıt düşük öncelikle ezilmiş olabilir ve ekrandaki boyut yine de modelden farklı çıkabilir; bu fark bu dersin ölçüsünün dışındadır.
DZ3. Bu ölçüm yalnız sabitin eşitliği için. Öncelik ve belirsizlik KAYNAK parçada gösterildi, çıktıya girmedi.
Özet
- Kısıt tabanlı düzende ekranın modelden aldığı şey kısıtın sabitidir; model değişince sabit kodla yeniden yazılmalıdır.
- Modelde değişen ama kısıta yazılmayan değer bayattır: ekran eski sayıyı taşır.
- Modelde olup hiç kurulmamış bir kısıt eksiktir: ekranın o anahtar için değeri yoktur.
- Öncelik ve belirsizlik modelde yoktur; aynı sabit zorunlu ya da esnek kısıtta aynı uyumu verir.
- Model kısıt çözücüyü ölçmez; sabiti karşılaştırır, çözülmüş boyutu değil.
- Bu dersin uyumu: bayat.
Sonraki Adım
Kısıtları tek tek kurmak, görünüm çoğaldıkça ağırlaşır. Sıradaki derste bir yığıt görünümü kısıtları kendi üstlenir; yazılan şey bir alt görünümün gizlenip gizlenmediği olur. O derste uyum yeni bir adla anılır: artık.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.