Mac editörünü, kabul edilmiş metni üst görünümde tutup ayrı bir taslağı bir sayfada düzenleyerek oluşturun. Taslağı geri kopyalayan tek işlem Commit olsun; Cancel sayfayı kapatsın. Bu eğitim, kendi uygulamanızdaki küçük bir düz metin notu için bu akışı uygular.
Onaylama ve iptal işlemlerinin ne yapacağını tanımlayın
Okuyucunun görevi basittir: Var olan bir notu açmak, birkaç satırını düzenlemek ve ya düzenlemenin tamamını kabul etmek ya da özgün metni olduğu gibi bırakmak. Örnek, sentetik metin ve tek bir düzenleme oturumu kullanır. Kabul edilmiş metni bellekte tutar; dosya depolama eklemek ayrı bir uygulama kararıdır.
Apple’ın TextEditor bileşeni, çok satırlı ve kaydırılabilir metni görüntüler ve düzenlenen değere bir bağlama kabul eder. Dolayısıyla kabul edilmiş değere bağlamak, düzenlemelerin bu değeri hemen değiştirmesine yol açar. Ertelenmiş kabulü uygulamak için düzenleyiciyi bunun yerine bir taslağa bağlayın. Apple’ın TextEditor belgelerine bakın.
Tasarım üç parçadan oluşur: üst görünümün sahip olduğu kabul edilmiş metin, düzenleme başladığında oluşturulan bir anlık görüntü ve sayfanın sahip olduğu değiştirilebilir taslak durumu. Swift’in String türü değer semantiğine sahiptir; dolayısıyla taslağı değiştirmek kabul edilmiş metni değiştirmez. Kopyalama üzerine yazma depolaması bir uygulama iyileştirmesidir; bağımsız değer davranışı aynıdır. String sayfasına bakın.
| Olay | Taslağın davranışı | Kabul edilmiş metin |
|---|---|---|
| Düzenleyiciyi açma | Geçerli kabul edilmiş metinden başlar | Değişmez |
| Metin yazma veya silme | Sayfa içinde değişir | Değişmez |
Commit seçme |
Üst görünüme aktarılır, ardından sayfa kapanır | Taslakla değiştirilir |
Cancel seçme |
Taslak geri aktarılmadan sayfa kapanır | Değişmez |
| Düzenleyiciyi yeniden açma | Yeni bir oturum başlatır | Yeni başlangıç metnini sağlar |
Bu tablo aynı zamanda uygulama sözleşmesidir. Sayfanın kapanması tek başına kabul anlamına gelmez. Bu ayrımı açık tutmak, geri alma işlemi gerekmeden iptalin çalışmasını sağlar.
macOS örneğinin tamamını oluşturun
SwiftUI kullanarak Xcode’da bir macOS uygulama projesi oluşturun ve dağıtım hedefini macOS 12 veya üstü olarak ayarlayın. Oluşturulan uygulama ve içerik görünümü tanımlarını aşağıdaki tek kaynak dosyayla değiştirin; hedefte tam olarak bir @main giriş noktası kalması için eski uygulama tanımını kaldırın. Örnek, daha yeni biçimlendirilmiş metin başlatıcısı yerine düz metin düzenlemeyi kullanır.
macOS 12 alt sınırı, sayfanın DismissAction kullanmasından kaynaklanır; düz metin TextEditor macOS 11’den itibaren kullanılabilir. Apple, bu kullanılabilirlik sınırlarını DismissAction ve TextEditor belgelerinde açıklar.
import SwiftUI
import Foundation
@main
struct DraftEditorApp: App {
var body: some Scene {
WindowGroup {
NoteView()
}
}
}
private struct EditSession: Identifiable {
let id = UUID()
let initialText: String
}
private struct NoteView: View {
@State private var acceptedText = """
Sample note
Arrange the blue tiles beside the green tiles.
"""
@State private var editSession: EditSession?
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Accepted note")
.font(.headline)
ScrollView {
Text(acceptedText.isEmpty ? "(Empty note)" : acceptedText)
.frame(maxWidth: .infinity, alignment: .leading)
}
Button("Edit Note") {
editSession = EditSession(initialText: acceptedText)
}
}
.padding(20)
.frame(minWidth: 460, minHeight: 300)
.sheet(item: $editSession) { session in
DraftEditor(initialText: session.initialText) { newText in
acceptedText = newText
}
.id(session.id)
}
}
}
private struct DraftEditor: View {
@Environment(\.dismiss) private var dismiss
@State private var draft: String
private let initialText: String
private let onCommit: (String) -> Void
init(
initialText: String,
onCommit: @escaping (String) -> Void
) {
self.initialText = initialText
self.onCommit = onCommit
_draft = State(initialValue: initialText)
}
private var hasChanges: Bool {
draft != initialText
}
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text("Edit note")
.font(.headline)
TextEditor(text: $draft)
.font(.body)
.accessibilityLabel("Note draft")
.frame(minHeight: 220)
Text(hasChanges ? "Draft changed" : "No changes")
.font(.caption)
.foregroundColor(.secondary)
HStack {
Spacer()
Button("Cancel") {
dismiss()
}
.keyboardShortcut(.cancelAction)
Button("Commit") {
onCommit(draft)
dismiss()
}
.keyboardShortcut(.return, modifiers: .command)
}
}
.padding(20)
.frame(minWidth: 480, minHeight: 340)
}
}
Bu, başlangıç metni, sunum durumu, düzenleyici ve giriş noktası dahil olmak üzere eksiksiz bir uygulama örneğidir. Araştırma ortamında Swift derleyicisi ve macOS SDK bulunmadığından, örnek burada derlenip çalıştırılmak yerine alınan API belgelerine göre incelenmiştir.
Kabul edilen değerin sahibi neden üst görünüm olmalı?
Kabul edilmiş sonucu görüntülediği ve bir sonraki düzenleme oturumunun başlangıç değerini sağladığı için üst görünüm acceptedText sahibidir. Yalnızca düzenleme arayüzünün değiştirmesi gerektiğinden alt görünüm draft sahibidir. Bu yaklaşım, Apple’ın durumu gizli tutma ve görünüm hiyerarşisinde uygun düzeye yerleştirme yönergesine uyar. State sayfasına bakın.
Üst görünüm, düzenleyiciye $acceptedText aktarmaz. Bunun yerine bir metin değeri ve bir closure aktarır. Closure, bir düzenlemeyi kabul eden tek atamayı içerir: acceptedText = newText. Bu uygulamada ne yazma ne de iptal bu atamaya ulaşabilir.
Her açılış için neden bir oturum tanımlayıcısı gerekiyor?
İsteğe bağlı EditSession, hem sunum durumunu hem de başlangıç metnini sağlar. Apple, sheet(item:onDismiss:content:) bağlı öğe nil-nil olduğunda içeriği sunduğunu ve bu öğeyi içerik closure’ına aktardığını belgeliyor. sheet(item:onDismiss:content:) sayfasına bakın.
Edit Note düğmesine her basış yeni bir tanımlayıcı oluşturur. Bu tanımlayıcıyı düzenleyiciye uygulamak, amaçlanan durum ömrünü açık hale getirir: Her taslak tek bir düzenleme oturumuna aittir. Başlatıcı, ilk durum değerini sağlar; mevcut bir taslağı üst görünümdeki sonraki değişikliklerle sürekli eşitleme mekanizması değildir.
Bu görev için anlık görüntü davranışı uygundur. Üst görünümün ilgisiz bir bölümü güncellendi diye kısmen düzenlenmiş bir not aniden sıfırlanmamalıdır. Yeniden açmak, en son kabul edilmiş metinden yeni bir taslak oluşturur.
Kapatma ve klavye davranışını bilinçli belirleyin
Örnekte olduğu gibi, sunulan düzenleyicinin içinde @Environment(\.dismiss) değerini okuyun. Apple, bu eylemin tanımlandığı ortama uygulandığını açıklar. Sunumu yapan kök görünümde okumak, sayfa yerine o görünümün penceresini hedefleyebilir. DismissAction sayfasına bakın.
Cancel düğmesi sayfayı doğrudan kapatır. Taslak, kabul edilmiş değeri hiç değiştirmediği için kaybolabilir. Commit düğmesi önce kabul closure’ını çağırır, ardından sayfayı kapatır. Kabul işlemini onDismiss içine taşımayın: Bu geri çağırım, biri iptal ettiğinde de gerçekleşen kapatmayı bildirir.
Örnek, Apple’ın standart iptal kısayolu olarak belgelediği .cancelAction ile Cancel düğmesine Escape tuşunu atar. Açık bir tuş eşdeğeri ve değiştiriciyle Command-Return’ü Commit düğmesine atar. Bunlar uygulama arayüzündeki denetim kısayollarıdır. cancelAction ve keyboardShortcut(_:modifiers:) sayfalarına bakın.
Örnek, onaylama düğmesine .defaultAction atamadığından çok satırlı düzenleme için düz Return tuşu kullanılabilir. Apple, .defaultAction değerini değiştirici olmadan kullanılan Return olarak tanımlar. Command-Return, her Mac düzenleyicisinin kullanması gereken bir kısayol iddiası değil, bu çok satırlı arayüz için bir tasarım tercihidir. defaultAction sayfasına bakın.
İlgili arayüz içinde kısayolları benzersiz tutun. Apple, birden fazla denetimde aynı kısayol varsa çözümleme sırasında bulunan ilk eşleşen denetimin kullanıldığını belgeliyor. Ekleme noktası düzenleyicinin içindeyken kısayolları, görünür düğmelere tıklamayla birlikte sınayın. keyboardShortcut(_:modifiers:) sayfasına bakın.
Kısa bir test dizisiyle kabul sınırını doğrulayın
Çalışan Mac uygulamasında aşağıdaki kontrolleri yapın. Kolayca gözden kaçabilen boş dize durumu da dahil olmak üzere, taslak düzenleyiciyi canlı düzenleyiciden ayıran davranışları sınarlar.
Edit Notedüğmesini açın, ikinci satırıArrange the green tiles in a row.olarak değiştirin veCanceldüğmesine tıklayın. Kabul edilmiş notta özgün ikinci satır kalmalıdır.- Düzenleyiciyi yeniden açın. İptal edilen düzenleme olmadan, özgün kabul edilmiş metni içermelidir.
- Aynı değişikliği yapıp
Commitdüğmesine tıklayın. Kabul edilmiş notta artık düzenlenmiş satır görünmelidir. - Yeniden açın, bir satır sonu ekleyin ve Command-Return kullanın. Çok satırlı taslağın tamamı kabul edilmiş metin olmalıdır.
- Yeniden açın, bir değişiklik yapın ve düzenleme sırasında Escape tuşunu sınayın. İptalin kabul edilmiş metni değiştirmediğini kontrol edin.
- Yeniden açın, tüm metni silin ve
Commitdüğmesine tıklayın. Üst görünüm(Empty note)değerini görüntülemelidir. Yeniden açıldığında düzenleyici boş olmalıdır.
Bu örnekte boş metin geçerlidir. Uygulama boşlukları kırpmaz, satır sonlarını kaldırmaz ve boş dize için kabulü devre dışı bırakmaz. Kendi not türünüz içerik gerektiriyorsa görünür bir doğrulama kuralı ekleyip bu kuralı hem kabul eyleminde hem de düğmenin etkinlik durumunda uygulayın.
hasChanges karşılaştırması yalnızca durum etiketini kontrol eder. Commit düğmesini değişiklik olmadığında da etkin bırakmak, aynı değerin zararsızca atanmasını sağlar. Daha sonra değişiklik yokken düğmeyi devre dışı bırakırsanız, sayfanın her zaman belirgin bir çıkış yolu olması için Cancel düğmesini kullanılabilir tutun.
SwiftUI’nin ne zaman yeterli, AppKit’in ne zaman yararlı olduğunu bilin
Bu düz metin görevi için TextEditor gerekli düzenleme alanını zaten sağlar. Taslak değişikliklerini kabul edilmiş metinden ayırmak için özel metin yerleşimi veya AppKit köprüsü gerekmez. Bu ayrım, uygulamanın durum tasarımına aittir.
Güncel SwiftUI, macOS 26’dan itibaren kullanılabilen, biçimlendirilmiş dize kullanan bir TextEditor başlatıcısını da destekler. Dolayısıyla zengin metin, SwiftUI’yi otomatik olarak değiştirmeyi gerektirmez. Taslağın türünü değiştirmek ve daha yeni dağıtım gereksinimini karşılamak gerekir. Apple ayrıca bu düzenleyicideki biçimlendirme özniteliği dönüştürme sınırlarını belgeliyor. init(text:selection:) sayfasına bakın.
AppKit’e özgü düzenleme davranışına veya metin yerleşimi üzerinde doğrudan denetime ihtiyacınız varsa desteklenen tümleştirme yolu, NSViewRepresentable üzerinden sunulan bir NSTextView’dür. Apple; AppKit görünümünü oluşturma, güncelleme ve kaldırmaya yönelik temsil edilebilir yöntemleri ve temsilci değişikliklerini SwiftUI’ye ileten bir koordinatörü açıklar. NSViewRepresentable sayfasına bakın.
Bu köprüde de aynı kabul sözleşmesini koruyun: AppKit düzenlemesi taslağı günceller, kabul edilmiş modeli ise yalnızca açık onaylama eylemi günceller. Uygulama önlemi olarak, her SwiftUI güncellemesinde metin görünümünün tüm içeriğini değiştirmekten kaçının; gerçekten gelen değişiklikleri düzenleyicinin kendi dışa aktardığı değişikliklerden ayırın.
TextKit’in bir sınırı daha vardır: NSTextView, modern yerleşimi textLayoutManager, eski yerleşimi ise layoutManager üzerinden sunar. Apple, macOS 12 veya üstünde ikinci seçeneğe erişmenin metin görünümünü NSLayoutManager kullanan uyumluluk kipine geçirdiğini belgeliyor. Yerleşim özellikleri eklerken altyapıyı bilinçli seçin. TextKit ve NSTextView sayfalarına bakın.
Son olarak, örnekteki onaylama işlemi eşzamanlıdır ve bellekte gerçekleşir. Bunu belge yazacak şekilde genişletirseniz kabulün modelin güncellenmesi mi yoksa yazma işleminin tamamlanması mı olduğunu tanımlayın. Yazmadan önce kapatan bir tasarımda, yazma başarısız olduğunda sayfayı açık tutun ve yeniden deneme için taslağı koruyun. Düzenleme sırasında aynı not uygulamanın başka bir arayüzünden değişebiliyorsa, notu değiştirmeden önce sürüm denetimi ekleyin.
Temel tasarım küçük kalır: Her düzenleme oturumu için bir taslak oluşturun, düzenleyicinin bu taslağı değiştirmesine izin verin ve kabul için tek, açık bir yol sunun. Bu sınır kurulduğunda, düzenleme alanı büyüse de iptal öngörülebilir kalır.
Kaynaklar
- TextEditor — Apple
- String — Apple
- State — Apple
- sheet(item:onDismiss:content:) — Apple
- DismissAction — Apple
- cancelAction — Apple
- defaultAction — Apple
- keyboardShortcut(_:modifiers:) — Apple
- init(text:selection:) — Apple
- NSViewRepresentable — Apple
- TextKit — Apple
- NSTextView — Apple