İçeriğe geç
academia.sh

Ders 05 / 15

Görsel Düzenleyici ve Kodla Arayüz

Görsel düzenleyicide çizilen ekranda bağlantısı kopuk bir etiketin yer tutucu metinle bayat kaldığının, kodla kurulan etiketin ise uyumlu olduğunun ve düzenleyicinin kaydettiği yer tutucu metnin artık bıraktığının ölçümü.

Yayın Güncelleme

Bu derste

  • Bir ekranın görsel düzenleyicide ya da kodla kurulmasının denetleyici açısından ne değiştirdiğini ayırır
  • Bağlantısı kopuk bir etiketin isteğe bağlı zincirle sessizce yazılmadan kaldığını ölçer
  • Kodla kurulan etiketin bu yoldan etkilenmediğini gösterir
  • Düzenleyicinin dosyaya kaydettiği yer tutucu metnin modelde karşılığı olmadığını artık olarak tanır
İçindekiler

Önceki derslerde etiketler denetleyicinin kodla kurduğu alanlardı. Ekran kurmanın ikinci yolu görsel arayüz düzenleyicisinde çizmektir: etiketler, düğmeler ve aralarındaki yerleşim bir dosyaya kaydedilir ve uygulama çalışırken dosyadan yüklenir. Üçüncü yol ikisinin karışımıdır: bazı görünümler dosyada, bazıları kodda.

Hangi yolun seçildiği denetleyiciyi değiştirmez: ekrana yazmak yine denetleyicinin işidir. Değişen, denetleyicinin görünüme nasıl ulaştığıdır. Dosyadan gelen görünüm denetleyiciye bir bağlantı (outlet) üzerinden verilir; kodla kurulan görünümü denetleyici kendi yaratır. Bu derste ilkinin sessizce kırılabildiği bir durumu ölçeriz.

Bağlantı ve Kodla Kurulan Etiket

Aşağıdaki parça bir hava durumu ekranını gösterir. Sıcaklık ve nem etiketleri dosyada çizilmiş ve bağlantıyla verilir; rüzgâr etiketi kodla kurulur. Platform çerçevesini içe aldığı için çalıştırılmaz.

import UIKit

final class WeatherController: UIViewController {
    let store: WeatherStore
    @IBOutlet var temperatureLabel: UILabel?
    @IBOutlet var humidityLabel: UILabel?
    private let windLabel = UILabel()

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

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

    override func viewDidLoad() {
        super.viewDidLoad()
        temperatureLabel?.text = store.values["temperature"] ?? ""
        humidityLabel?.text = store.values["humidity"] ?? ""
        windLabel.text = store.values["wind"] ?? ""
    }
}

Bağlantılar isteğe bağlı değer olarak yazıldı. Dosyada bir etiket çizilip denetleyiciye bağlanmadıysa, ya da bağlantı sonradan silindiyse, alan nil kalır. Zincirin ?. biçimi nil alana yapılan yazmayı sessizce atlar: ne hata verilir ne uyarı çıkar. Alanı örtük açılan türle tanımlamak aynı durumda hata ile durmayı seçerdi; bu dersin ölçümü zincirli biçimin sonucunu ele alır.

Dosyadan Yüklemek

Ekran dosyada çizildiğinde çerçeve dosyayı okur, görünümleri yaratır ve bağlantıları denetleyiciye atar. Atama, viewDidLoad çağrılmadan önce biter; bu yüzden viewDidLoad içinde bağlantıların dolu olması beklenir. Bağlantıların dolu olmadığı durumda alan nil kalır.

Aynı ekran kodla da kurulabilir. Bu durumda görünüm ve etiketler denetleyicinin kendisi tarafından yaratılır ve nil olma ihtimalleri yoktur:

import UIKit

final class WeatherCodeController: UIViewController {
    private let temperatureLabel = UILabel()

    override func loadView() {
        view = UIView()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        temperatureLabel.frame = CGRect(x: 16, y: 80, width: 200, height: 24)
        view.addSubview(temperatureLabel)
    }
}

İki yolun karışımı da yaygındır: ana yerleşim dosyada, dinamik parçalar kodda. Hangi yolun seçildiği ölçüm açısından bir fark yaratmaz; fark, yazma çağrısına ulaşan yolun ne kadar kırılgan olduğundadır.

Ölçü

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

Bağlantı Kopukken ve Bağlıyken

Model, parçadaki denetleyiciyi saf Swift ile kurar. Model, kodun ekrana ne yazdığını kurar; çerçevenin kendisi değildir. Etiket, adı ve ekranı taşıyan küçük bir tiptir; bağlantılar main.swift içinde atanır, çünkü dosyayı yükleyen düzenleyicinin işi budur. Dosyadan gelen iki etiket, düzenleyicide çizildikleri yer tutucu metni (Etiket) taşıyarak yüklenir; bunu loadInterfaceFile kurar. Sıcaklık etiketi başta bağlanmaz. Sonra bağlantı kurulur ve ekran yeniden yüklenir.

// main.swift — kopuk baglanti, kodla kurulan etiket
final class WeatherStore {
    private(set) var values: [String: String] = [:]
    func update(_ key: String, _ value: String) { values[key] = value }
}

final class ConnectedLabel {
    let key: String
    let screen: Screen

    init(_ key: String, screen: Screen) {
        self.key = key
        self.screen = screen
    }

    func setText(_ text: String) { screen.set(key, text) }
}

final class WeatherController {
    let store: WeatherStore
    let screen = Screen("WeatherController")
    var temperatureLabel: ConnectedLabel?
    var humidityLabel: ConnectedLabel?
    let windLabel: ConnectedLabel

    init(store: WeatherStore) {
        self.store = store
        windLabel = ConnectedLabel("wind", screen: screen)
    }

    func loadInterfaceFile() {
        screen.set("temperature", "Etiket")
        screen.set("humidity", "Etiket")
    }

    func viewDidLoad() {
        temperatureLabel?.setText(store.values["temperature"] ?? "")
        humidityLabel?.setText(store.values["humidity"] ?? "")
        windLabel.setText(store.values["wind"] ?? "")
    }
}

let store = WeatherStore()
store.update("temperature", "21")
store.update("humidity", "60")
store.update("wind", "12")
let controller = WeatherController(store: store)
controller.humidityLabel = ConnectedLabel("humidity", screen: controller.screen)
controller.loadInterfaceFile()
controller.viewDidLoad()
let broken = check("yuklendi", model: store.values, screen: controller.screen, keys: ["temperature", "humidity", "wind"])

controller.temperatureLabel = ConnectedLabel("temperature", screen: controller.screen)
controller.loadInterfaceFile()
controller.viewDidLoad()
let connected = check("baglandi", model: store.values, screen: controller.screen, keys: ["temperature", "humidity", "wind"])

countVerdicts(broken + connected)
yuklendi | WeatherController.temperature: model=21 ekran=Etiket -> bayat
yuklendi | WeatherController.humidity: model=60 ekran=60 -> uyumlu
yuklendi | WeatherController.wind: model=12 ekran=12 -> uyumlu
baglandi | WeatherController.temperature: model=21 ekran=21 -> uyumlu
baglandi | WeatherController.humidity: model=60 ekran=60 -> uyumlu
baglandi | WeatherController.wind: model=12 ekran=12 -> uyumlu
uyumlu: 5
bayat: 1
eksik: 0
artik: 0

İlk yüklemede sıcaklık bayat çıktı: model 21 diyor, ekran dosyadan gelen Etiket metnini göstermeye devam ediyor. temperatureLabel?.setText(...) çağrısı alan nil olduğu için atlandı; etiket ekranda durur, ama kod ona ulaşamaz. Nem bağlı olduğundan ve rüzgâr kodla kurulduğundan uyumlu. Bağlantı kurulup ekran yeniden yüklenince üçü de uyumlu.

Dikkat edilecek nokta, bu hatanın görünürlüğüdür. Kopuk bağlantı derleme hatası vermez, çalışma anında hata vermez, test çıktısında iz bırakmaz; ekranda yalnızca düzenleyicideki yer tutucu metin görünür. Kodla kurulan etiket ise bu yoldan etkilenmez, çünkü onu denetleyici kendi yaratır ve nil olamaz. Bu bir yöntemin ötekinden iyi olduğu anlamına gelmez: kodla kurmak bağlantı hatasını ortadan kaldırır, ama yerleşimi düzenleyicide görmenin sağladığı hızı da bırakır.

GD1. Modelde bağlantıların atanması main.swift içindedir. Gerçek çerçevede bağlantıları dosyanın yüklenmesi sırasında çerçeve atar ve bu atamanın viewDidLoad öncesinde bittiği varsayılır; ders bunun sırasını ölçmez.

Sık Yapılan Hatalar

  • Bağlantıyı dosyada kopuk bırakmak. Etiket çizilir, ama denetleyiciye bağlanmaz. Zincirli yazma nil alana sessizce hiçbir şey yazmaz ve etikette düzenleyicideki yer tutucu metin kalır.
  • Bağlantı adını kodda değiştirip dosyada güncellememek. Dosya eski adı hâlâ arar; yükleme sırasında çalışma anında hata alınır. Bu hata kopuk bağlantının tersidir: gürültülüdür ve erken fark edilir.
  • Bağlantıyı kodda silip dosyada bırakmak. Aynı türden bir uyuşmazlık, aynı sonuç.
  • Yer tutucu metni bırakmak. Düzenleyicinin etikete yazdığı örnek metin, kod değeri yazana kadar ekranda kalır. Bu dersin sınırlayıcı ölçümü budur.
  • Zayıf ya da güçlü bağlantıyı kural sanmak. Görünüm zaten kök görünümün ağacında tutulduğu için bağlantı iki biçimde de tanımlanabilir; bu derste ikisi de geçer. Tercih bir hata değildir. Görünümü ağaçtan çıkarıp sonra geri ekleyen kod güçlü bağlantı ister, çünkü zayıf bağlantı ağaçtan çıkan görünümü tutmaz.

Dosyadan gelen alanlar için bir koruma, yazma çağrısını isteğe bağlı zincirle sessizce geçmek yerine, nil durumunu ayrıca ele almaktır; bu kursun ölçümü hatanın görünmediğini gösterir, görülmesini sağlamaz.

Ölçümü Okumak

Altı satırın beşi uyumlu, biri bayattır; bayat satır bağlantısı kopuk sıcaklık etiketidir. Bağlantı kurulup ekran yeniden yüklenince satır uyumlu olur. Yani alan modelde var, kod onu yazmaya çalışıyor ve ekran hâlâ dosyadaki yer tutucuyu gösteriyor: atlanan yazma, hiç yapılmamış bir yazmadan ayırt edilemiyor.

Bu, kodla kurulan etikete göre bir fark getirir ama bir üstünlük anlamına gelmez. Kodla kurulan etiketin hatası başka yerde çıkar; örneğin görünüme eklenmemişse ekranda görünmez ve eksik sonucu verir. Ölçüm yalnız ekrandaki sonucu sayar.

Dosyanın İçinde Neler Kayıtlıdır

Düzenleyicinin kaydettiği dosya yalnızca görünümlerin konumlarını tutmaz. İçinde görünüm ağacı, her görünümün ayarları, kısıtlar, etiketlerin örnek metinleri, denetleyicinin sınıf adı ve bağlantılarla eylemlerin kayıtları bulunur. Bağlantılar ve eylemler özellik ve yöntem adlarıyla dosyada saklanır; kod bu adlardan birini değiştirirse dosya eski adı aramaya devam eder.

Dosyayı belirli bir ad ile yüklemek de mümkündür:

import UIKit

final class WeatherController: UIViewController {
    @IBOutlet weak var temperatureLabel: UILabel?

    init() {
        super.init(nibName: "WeatherController", bundle: nil)
    }

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

Denetleyicinin sınıf adı dosyada başka bir sınıfa ayarlanmışsa, dosyada kayıtlı bağlantılar yükleme sırasında o sınıfta bulunmayan adlara atanmaya çalışılır ve uygulama çalışma anında bir anahtar hatasıyla çöker. Bu belirti sessiz değildir. Dosyada hiç bağlantı yoksa çökme olmaz; o zaman yalnız kendi sınıfınızın kodu çalışmaz.

Hangi Yolu Seçmeli

Seçim, ekibin ve ekranın özelliklerine bağlıdır ve bu derste ölçülmez. Yine de kodun ve dosyanın sınırlarını bilmek gerekir:

  • Dosya, yerleşimi çizerek görmeyi sağlar; yerleşim değişiklikleri dosyada metin farkı olarak gelir ve iki kişi aynı ekranı değiştirdiğinde birleştirmesi zordur.
  • Kod, her değişikliği satır satır gözden geçirmeyi kolaylaştırır; yerleşimi görmek için çalıştırmak gerekir.
  • Ekranın parçaları duruma göre çıkıp giriyorsa, bu parçalar çoğunlukla kodla kurulur.

İki yol bir ekranda birlikte de kullanılabilir. Bu durumda, hangi alanın dosyadan hangisinin kodla geldiğini denetleyicide belirgin tutmak, bir alanın neden nil kaldığını aramayı kısaltır.

Yaygın Sorunlar ve Belirtileri

Bir ekran dosyayla geldiğinde çıkan sorunların bir kısmı çalışma anında durur, bir kısmı sessizdir: bir alan nil kalır ya da yazma çağrısı ekranda olmayan bir nesneye gider. Nedenleri birkaç başlıkta toplanır:

  • Bağlantı dosyada vardır ama denetleyicideki özellik silinmiş ya da adı değişmiştir. Dosya yüklenirken adı bulamaz ve çalışma anında bir anahtar hatasıyla durabilir.
  • Denetleyicinin sınıfı dosyada ayarlanmamıştır. Sistem düz bir UIViewController oluşturur ve kendi sınıfınızın kodu hiç çalışmaz.
  • Alan yaşam döngüsünün erken bir noktasında okunur. Bağlantılar görünüm yüklendikten sonra atanır; init içinde okunan bir alan nil döner.
  • Aynı alan hem dosyada hem kodda oluşturulmuştur. İkisinden biri ekrana girer, öteki ise hiç görünmeyen ayrı bir nesne olarak kalır ve yazma çağrıları ona gider.

Bu belirtilerin dördü de modelle ekran arasındaki ayrışmanın kaynaklarıdır. Sessiz olanlarda etikette dosyadaki yer tutucu kalır ve ayrışma ölçümde bayat olarak görünür: model bir değer taşır, ekran başka bir değer gösterir. Ölçüm hatanın nedenini söylemez, yalnız ekranın modele ulaşmadığını gösterir.

Önizlemeyi Yorumlamak

Düzenleyicideki önizleme, ekranın dosyadaki örnek metinlerle nasıl göründüğünü gösterir. Gerçek veri o sırada yoktur: etiketlerde yazan metin yer tutucudur. Ekran çalışınca model değerleri bu yer tutucuların üstüne yazılır. Yazma çağrısı hiç çalışmazsa, kullanıcı yer tutucu metni görmeye devam eder. Bu durumun ölçümdeki karşılığı alana göre değişir: model o alanı taşıyorsa satır bayat olur (ilk ölçümdeki sıcaklık), taşımıyorsa artık (aşağıdaki sınırlayıcı ölçümde caption).

Bu yüzden yer tutucu metinler gerçek veriye benzemeyecek biçimde seçilir. Etikete gerçek bir ad yazılırsa, yazma çağrısının atlandığı hata önizlemede ve ilk bakışta fark edilmez. Ekran her açıldığında önizlemeye değil, çalışan modele bakmak gerekir; iki kaynağın farkı da ölçümün konusudur. Bu ayrım küçük görünür, ama ekranı yalnız önizlemeye bakarak onaylayan ekiplerde hatanın sürüme kadar taşınmasının başlıca nedenidir.

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

Düzenleyici, bir etiketin metnini dosyaya kaydeder. Bir etiket çizildiğinde içinde yer tutucu bir metin bulunur ve dosyadan yüklenince ekranda bu metin vardır. Kod o etiketi yazmazsa metin ekranda kalır, ve modelde o alanın karşılığı yoktur.

// main.swift — dosyadan gelen yer tutucu metin
final class WeatherStore {
    private(set) var values: [String: String] = [:]
    func update(_ key: String, _ value: String) { values[key] = value }
}

final class WeatherController {
    let store: WeatherStore
    let screen = Screen("WeatherController")

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

    func loadInterfaceFile() {
        screen.set("caption", "Etiket")
    }

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

let store = WeatherStore()
store.update("temperature", "21")
let controller = WeatherController(store: store)
controller.loadInterfaceFile()
controller.viewDidLoad()
let placeholder = check("yer-tutucu", model: store.values, screen: controller.screen, keys: ["temperature", "caption"])
countVerdicts(placeholder)
yer-tutucu | WeatherController.temperature: model=21 ekran=21 -> uyumlu
yer-tutucu | WeatherController.caption: model=- ekran=Etiket -> artik
uyumlu: 1
bayat: 0
eksik: 0
artik: 1

caption ekranda Etiket gösteriyor ve model bu alanı taşımıyor; satır artik. Kodu yazan hiçbir şey yanlış yapmadı: etiket dosyada kaldı ve kimse onu silmedi. Kural bunu kaza ile kasıt arasında ayıramaz. Yer tutucu bilerek bırakılmış bir başlık da olabilirdi; kural neden bakmaz.

Özet

  • Ekran görsel düzenleyicide ya da kodla kurulabilir; her iki yolda da ekrana yazmak denetleyicinin işidir.
  • Dosyadan gelen etiket bağlantıyla verilir; bağlantı kopuksa isteğe bağlı zincir yazmayı sessizce atlar ve etiket yer tutucu metinle bayat kalır.
  • Kodla kurulan etiket nil olamaz ve bu yoldan etkilenmez.
  • Bağlantı kurulup ekran yeniden yüklenince alan uyumlu olur.
  • Düzenleyicinin dosyaya kaydettiği yer tutucu metin, kod yazmazsa ekranda artık olarak kalır.
  • Bu dersin uyumu: bayat.

Sonraki Adım

Bağlantı denetleyicinin ekrandaki görünüme uzanan elidir. Ters yönde de bir bağ vardır: düğmeye basılması denetleyicideki bir yöntemi çağırır. Sıradaki derste bağlantıları ve eylemleri birlikte ele alacağız ve eylemin modeli güncelleyip etiketi unuttuğu durumu ölçeceğiz.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat