NSTextFieldDelegate atayın, controlTextDidEndEditing(_:) metodunu uygulayın ve alanın stringValue değerini bir SwiftUI bağlamasına kopyalayın. Bir koordinatör bu geri çağrıyı görünüm durumuna bağlar; böylece düzenleme tamamlandıktan sonra bir etiket güncellenebilir.

Bu eğitimde, düzenlenebilir iki etiket ve bunların kaydedilmiş değerlerini gösteren bir özet içeren küçük bir Mac uygulaması oluşturacağız. Amaç, biri yazarken özeti değiştirmemek ve AppKit düzenleme oturumunu bitirdiğinde güncellemektir. Apple, düzenleme sonu geri çağrısının denetim değişikliklerini kaydettikten sonra gerçekleştiğini belgeliyor. Apple’ın controlTextDidEndEditing(_:) belgeleri

Görünüm hangi olayda güncellenmeli?

Mevcut bir NSTextField için denetimin düzenleme temsilcisi geri çağrısını kullanın. Temsilci, object özelliği bildirimi gönderen denetim olan bir bildirim alır; böylece işleyici alanı tanımlayıp değerini okuyabilir. Apple’ın textDidEndEditingNotification belgeleri

İhtiyacınız olan arayüz davranışına göre olayı seçin:

İstenen davranış Uygun yol
AppKit metin alanı düzenlemeyi bitirdiğinde kaydedilmiş bir değeri güncellemek Temsilcisinde controlTextDidEndEditing(_:) metodunu uygulayın.
Düzenleme tamamlandığında mevcut bir AppKit target-action işleyicisini çağırmak Hücrenin sendsActionOnEndEditing özelliğini yapılandırın.
Yerel bir SwiftUI TextField gönderimini işlemek Belgelenmiş gönderim davranışı için .onSubmit ekleyin.

Bu yolların sözleşmeleri farklıdır. Apple, sendsActionOnEndEditing etkinleştirildiğinde Return tuşuna basıldıktan, alandan Tab ile çıkıldıktan veya başka bir metin alanına tıklandıktan sonra eylemin gönderildiğini; devre dışı bırakıldığında ise eylemin yalnızca Return tuşuyla gönderildiğini belgeliyor. SwiftUI, TextField için .onSubmit yönteminin Return tuşuna yanıt verdiğini belgeliyor. Bu nedenle bu yöntem, gömülü bir AppKit alanı için genel bir odak kaybı geri çağrısı sağlamaz. Apple’ın sendsActionOnEndEditing belgeleri, Apple’ın onSubmit(of:_:) belgeleri

Bu uygulamada temsilci, alan metnini kaydeden tek yerdir. Daha sonra bir target-action işleyicisi de eklerseniz, aynı kaydetme yan etkisini iki işleyicide birden gerçekleştirmeyin.

Eksiksiz bir SwiftUI ve AppKit örneği oluşturma

Xcode’da Swift ve SwiftUI arayüzünü kullanarak bir macOS uygulama projesi oluşturun. Bu örnek için güncel Swift 6 destekli bir araç zinciri kullanın ve dağıtım hedefini macOS 13 veya üzeri olarak ayarlayın. Oluşturulan uygulama giriş dosyasını aşağıdaki eksiksiz programla değiştirin ve başka bir ContentView bildiriyorsa oluşturulan ContentView.swift dosyasını kaldırın. Tam olarak bir @main giriş noktası bırakın. Apple’ın App belgeleri bu giriş noktası yapısını açıklıyor. Apple’ın App belgeleri

Örnekteki macOS 13 hedefi bir proje tercihidir; köprünün ilk kullanılabildiği sürüm değildir: Apple, NSViewRepresentable kullanılabilirliğini macOS 10.15’ten itibaren listeliyor. AppKit, bu uygulamayı macOS hedeflerine özgü kılar. Apple’ın NSViewRepresentable belgeleri

import SwiftUI
import AppKit

@main
@MainActor
struct EditingDemoApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

@MainActor
struct ContentView: View {
    @State private var cardTitle = "Blue Card"
    @State private var shelfLabel = "Shelf A"

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Card title")
            CommitTextField(value: $cardTitle, label: "Card title")
                .frame(height: 24)

            Text("Shelf label")
            CommitTextField(value: $shelfLabel, label: "Shelf label")
                .frame(height: 24)

            Divider()

            Text("Committed values")
                .font(.headline)
            Text("Card: \(cardTitle)")
            Text("Shelf: \(shelfLabel)")
        }
        .padding(20)
        .frame(minWidth: 420)
    }
}

@MainActor
struct CommitTextField: NSViewRepresentable {
    @Binding var value: String
    let label: String

    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }

    func makeNSView(context: Context) -> NSTextField {
        let field = NSTextField(string: value)
        field.placeholderString = label
        field.delegate = context.coordinator
        return field
    }

    func updateNSView(_ field: NSTextField, context: Context) {
        context.coordinator.parent = self
        field.placeholderString = label
        field.isEnabled = context.environment.isEnabled

        // Leave the active editing buffer alone.
        if field.currentEditor() == nil, field.stringValue != value {
            field.stringValue = value
        }
    }

    static func dismantleNSView(
        _ field: NSTextField,
        coordinator: Coordinator
    ) {
        field.delegate = nil
    }

    @MainActor
    final class Coordinator: NSObject, NSTextFieldDelegate {
        var parent: CommitTextField

        init(parent: CommitTextField) {
            self.parent = parent
            super.init()
        }

        func controlTextDidEndEditing(_ notification: Notification) {
            guard let field = notification.object as? NSTextField else {
                return
            }

            let committedValue = field.stringValue
            if parent.value != committedValue {
                parent.value = committedValue
            }
        }
    }
}

Bu, sentetik yerel değerler kullanan eksiksiz bir uygulama örneğidir. Erişilen API belgelerine göre incelendi, ancak burada derlenmedi veya çalıştırılmadı: kullanılabilir yürütme ortamı Linux ve Swift araç zinciri ya da macOS SDK’sı içermiyor. Mac’te Xcode ile derleyip deneyin.

Değer özete nasıl ulaşır?

Üst görünümdeki her @State değeri, alan sarmalayıcısına bir bağlama sağlar. Koordinatör, düzenleme bittiğinde bu bağlama üzerinden yazar. SwiftUI, değişen durumu okuduğu için özeti günceller. Apple, hem durumun yönlendirdiği görünüm güncellemesini hem de alt görünümlerin üst görünüme ait durumu değiştirmesine olanak tanıyan bağlamaların kullanımını belgeliyor. Apple’ın State belgeleri

makeNSView, düzenlenebilir AppKit denetimini oluşturur ve koordinatörü temsilcisi olarak atar. updateNSView, SwiftUI’den bu denetime gelen değişiklikleri işler. Temsilci geri çağrısı ise ters yöndeki akışı işler. Apple’ın entegrasyon yönergeleri, AppKit görünümlerini barındırmak için aynı koordinatör ve bağlama düzenini kullanıyor. Apple’ın “SwiftUI’yi AppKit ile kullanma” oturumu

context.coordinator.parent = self ataması, koordinatörün saklanan temsilci değerini güncel tutar. Bir koordinatör, barındırılan görünümün ömrü boyunca varlığını sürdürürken temsilcinin değerleri değişebilir. Apple, updateNSView sırasında bu saklanan değerin yenilenmesini açıkça gösteriyor. Apple’ın koordinatör yaşam döngüsü açıklaması

SwiftUI güncellemeleri sırasında taslağı koruma

Temel koruma field.currentEditor() == nil koşuludur. Bu örnekte, tamamlanmamış metin AppKit içinde tutulurken SwiftUI son kaydedilmiş değeri korur. Düzenleme koruması olmazsa SwiftUI güncellemesi sırasında kaydedilmiş değerin alana kopyalanması, tamamlanmamış taslağın üzerine yazabilir.

Apple, currentEditor() yönteminin denetimin alan düzenleyicisini, yoksa nil değerini döndürdüğünü belgeliyor. Alan düzenleyicisi, penceredeki denetimler arasında paylaşılan bir NSTextView nesnesidir. Bu nedenle yalnızca pencerenin firstResponder değerinin NSTextField olup olmadığını denetlemek, denetime kendi düzenleyicisini sormanın iyi bir alternatifi değildir. Apple’ın currentEditor() belgeleri

Ortaya çıkan davranış açık bir tasarım ilkesidir:

Durum Örnek davranış
Alan boşta ve bağlaması değişiyor Bir sonraki temsilci güncellemesinde bağlamayı alana kopyalayın.
Alanın etkin bir düzenleyicisi var Düzenleme arabelleğini koruyun.
Düzenleme farklı bir metinle bitiyor Tamamlanan metni bağlamaya yazın.
Düzenleme değişmemiş metinle bitiyor Gereksiz durum atamasını atlayın.

Bu ilke, düzenleme oturumu sırasında yerel taslağa öncelik verir. Kişi düzenleme yaparken uygulamanın başka bir kodu aynı bağlamayı değiştirirse daha sonra yapılan kayıt bu değişikliği geçersiz kılabilir. Bu örnekte başka bir yazıcı yoktur. Daha büyük bir düzenleyicide eşzamanlı değişikliklerin nasıl çözüleceğine karar verin; örneğin seçili öğeyi değiştirmeden önce geçerli oturumu bitirin ya da ayrı taslak ve kaydedilmiş değerleri saklayıp açık bir seçim sunun. Bir yeniden çizimi eşitlemek uğruna tamamlanmamış metni atmayın.

Bu görev için TextKit düzen özelleştirmesi gerekmez. İlgili sınır, denetimin düzenleme oturumu ve alan düzenleyicisidir. Apple’ın metin mimarisi kılavuzu NSTextField, temsilcisi ve paylaşılan NSTextView alan düzenleyicisi arasındaki ilişkiyi açıklıyor; bu kılavuz arşivlenmiştir, güncel currentEditor() başvurusu ise paylaşılan düzenleyici modelini doğruluyor. Apple’ın “Metin Düzenleme” kılavuzu, Apple’ın güncel alan düzenleyicisi başvurusu

Uygulamada tamamlanma davranışını doğrulama

Örneği derledikten sonra denetlemek için şu adımları izleyin. Bunlar bildirilen yürütme sonuçları değil, uygulamadan ve belgelenmiş düzenleme davranışından çıkarılan beklenen kontrollerdir.

  1. Blue Card ifadesini Green Card ile değiştirin. Yazarken özet hâlâ Card: Blue Card göstermelidir.
  2. Return tuşuna basın. Özet Card: Green Card olarak değişmelidir.
  3. Kart başlığını yeniden düzenleyin, ardından Shelf label alanına tıklayın. Tamamlanan kart başlığı özette görünmelidir.
  4. Raf değerini düzenleyin, ardından Tab tuşuna basın. Raf özetinin güncellendiğini doğrulayın ve klavye gezinmesini inceleyin.
  5. Boş bir değer kaydedin. İlgili özet, değeri olmadan yalnızca önekini göstermelidir; örnek boş dizeleri kabul eder.
  6. Metni değiştirmeden düzenlemeyi bitirin. Eşitlik denetimi, başka bir durum ataması yapılmasını önlemelidir.

Return tuşu, beklediğiniz odak hareketini oluşturmadan düzenlemeyi bitirebilir. Apple, düzenleme sonu bildiriminin ardından NSTextField öğesinin eylemini ve Return ile ilgili diğer işlemleri gerçekleştirmeye çalıştığını ve metnini seçebileceğini belgeliyor. Tab ve Shift-Tab, sırasıyla sonraki veya önceki tuş görünümüne geçmeye çalışır. Arayüzü denetlerken tamamlanma ve odak hedefini ayrı davranışlar olarak ele alın. Apple’ın textDidEndEditing(_:) belgeleri

Geri çağrı gelmiyorsa sorunu tanılama

Önce field.delegate değerinin hâlâ koordinatörü gösterdiğini doğrulayın. Bu özellik zayıf bir başvuru tutar; dolayısıyla ayrı bir sahibi olmayan geçici bir temsilci nesnesi kullanılabilir durumda kalmaz. Temsilci koordinatörü burada gereken yaşam süresini sağlar. Apple’ın delegate belgeleri, Apple’ın koordinatör yaşam döngüsü açıklaması

Ardından düzenlemenin gerçekten bittiğini doğrulayın. Rastgele bir arka plana tıklamak bu koşul için güvenilir bir deneme değildir. Bu örnekte Return tuşunu veya düzenlenebilir başka bir alanı kullanın; sonuç farklıysa belirli yanıtlayıcı geçişini inceleyin.

Uygulamanız doğrulama ekliyorsa control(_:textShouldEndEditing:) değerini denetleyin. Belgelenen dönüş değeri, düzenlemenin bitip bitmeyeceğini belirler. Alandan çıkma girişimi reddedildiyse bunu başarılı bir kayıt olarak değerlendirmeyin. Apple’ın control(_:textShouldEndEditing:) belgeleri

Ayrıca iptal yollarını inceleyin. Apple, bekleyen düzenlemeleri atmak için abortEditing() çağrıldığında controlTextDidEndEditing(_:) metodunun çağrılmadığını açıkça belirtiyor. Bu nedenle bir iptal işlemi, durum için kendi davranışını gerektirir. Apple’ın düzenleme sonu geri çağrısı belgeleri

Açık bir uygulama komutuyla tamamlanma isteme

Bu alana başvurusu olan gelecekteki bir Done komutu için desteklenen yanıtlayıcı yolu, alanın kendi penceresinden makeFirstResponder(nil) çağrısını istemektir. Devam etmeden önce döndürülen Boolean değeri denetleyin: Apple, geçerli yanıtlayıcının odaktan ayrılmayı reddedebileceğini ve bu durumda metodun false döndüreceğini belgeliyor. Uygulama genelindeki anahtar pencerenin bu düzenleyiciye ait olduğunu varsaymak yerine alanın gerçek penceresini kullanın. Apple’ın makeFirstResponder(_:) belgeleri

Bu köprüdeki AppKit erişimini ve kullanıcı arayüzü geri çağrılarını ana aktörde tutun. Güncel temsilci bildirimi ana aktöre yalıtılmıştır ve Swift, MainActor öğesinin ana gönderim kuyruğuna eşdeğer bir yürütücü kullandığını belgeliyor. Örnek, bu yalıtımı doğrudan @MainActor ile ifade eder; tamamlanma ile bağlamaya yazma arasına ertelenmiş bir görev eklemez. Apple’ın geri çağrı bildirimi, Swift’in MainActor belgeleri

Uygulamanın tek ve açık bir kayıt sınırı vardır: AppKit düzenlemeyi bitirir, koordinatör alanı okur ve bağlama, ona bağlı SwiftUI metnini günceller. Temsilci güncellemeleri sırasında taslağın korunması, özel metin düzeni araçlarına gerek kalmadan bu sınırı günlük etiket düzenlemeleri için kullanışlı kılar.

Kaynaklar