SwiftUI satırlarının tekrar tekrar başlatılması, bir satırın kaybolduğunu veya durumunu yitirdiğini kanıtlamaz. Mac’te kaydırılan bir arayüzü teşhis etmek için oluşturulmayı, korunan durumu, görünüm geri çağrılarını ve görüntü alanındaki görünürlüğü ayrı ayrı izleyin.

Bu kılavuz, kendi macOS uygulamasındaki yinelenen satır günlüklerini inceleyen geliştiriciler içindir. Amaç, bir güncellemenin yalnızca yeni bir görünüm değeri mi oluşturduğunu, satır durumunu mu sıfırladığını, yoksa kaydırma görüntü alanındaki satırları mı değiştirdiğini belirlemektir. Örnekte sentetik etiketler ve sıradan düğmeler kullanılır.

Her yaşam döngüsü sinyali gerçekte neyi gösterir?

Bir SwiftUI satırı genellikle bir yapı kullanır; yapı, Swift’te bir değer türüdür. Başka bir yapı değeri oluşturmak, kalıcı bir başvuru nesnesi oluşturmaktan farklıdır. Swift’in belgeleri bu ayrımı Swift’te Değer ve Başvuru Türleri başlıklı yazıda açıklar.

SwiftUI kendi kimlik modelini de ekler. Apple, geçici görünüm değerlerinin güncellemeler boyunca aynı kimliği temsil edebileceğini ve görünümün yaşam süresinin bu kimliği izlediğini açıklar. Dolayısıyla başlatıcı günlüğü, önceki satırın yaşam süresinin sona erdiğini kanıtlayamaz. Bkz. SwiftUI’nin Gizemini Çözmek.

Farklı soruları yanıtlamak için şu sinyalleri kullanın:

Sinyal Yanıtladığı soru Yorumlama sınırı
Satır başlatıcısı Başka bir satır değeri oluşturuldu mu? Görünümü veya durumun değiştirilmesini kanıtlamaz.
body değerlendirmesi Bu görünüm açıklaması değerlendirildi mi? Görüntülenen kareleri saymaz.
Korunan satır durumu Bu tanılama değeri güncellemeden sağ çıktı mı? Görüntü alanında bulunmayı değil, bu durumun sürekliliğini açıklar.
onAppear ve onDisappear SwiftUI bir görünüm geçişi bildirdi mi? Zamanlama görünüm türüne bağlıdır.
onScrollVisibilityChange Satır, seçilen bir görüntü alanı eşiğini geçti mi? Belgelenen kaydırma görünürlüğü koşulunu ölçer.

Apple, onAppear yönteminin ilk oluşturulan kareden önce tamamlandığını ve kesin zamanlamanın görünüm türüne bağlı olduğunu belgeler. onDisappear görünüm kaybolduktan sonra çalışır; bunun zamanlaması da görünüm türüne bağlıdır. Bu yöntemlerin hiçbiri durumun serbest bırakıldığını belgelemez. Bu geri çağrı olaylarını, depolama ömrüyle ilgili sonuçlardan ayrı tutun. Bkz. onAppear(perform:) ve onDisappear(perform:).

Görüntü alanıyla ilgili bir soru için açıkça bir eşik belirleyin. macOS 15 ve sonraki sürümlerde onScrollVisibilityChange(threshold:_:), görünümün verilen görünürlük eşiğini geçip geçmediğini bildirir; varsayılan değer 0.5’tir. Apple ayrıca görünüm, eşik zaten sağlanmış durumdayken belirdiğinde başlangıç geri çağrısı yapıldığını da belgeler. Bkz. onScrollVisibilityChange(threshold:_:).

Bu makalede “görüntü alanında görünür” ifadesi, belirtilen kaydırma eşiğinin karşılanması anlamına gelir. Birinin satıra bakıp bakmadığının ölçümü değildir.

Sinyalleri birbirinden ayıran küçük bir deney oluşturun

macOS 15 API’lerini içeren bir SDK kullanarak macOS SwiftUI uygulaması oluşturun ve dağıtım hedefini macOS 15 veya sonraki bir sürüm olarak ayarlayın. Oluşturulan uygulama ve içerik görünümü bildirimlerini aşağıdaki tam programla değiştirin. Yalnızca tek bir @main giriş noktası bırakın.

Örnek, görüntü alanı davranışını açıkça göstermek için ScrollView ve LazyVStack kullanır. Apple, LazyVStack öğelerini gerektiğinde oluşturan bir yapı olarak tanımlar. Bu deney, List’in yükleme veya elde tutma davranışının aynı olduğunu varsaymaz. Bkz. LazyVStack.

import SwiftUI
import Foundation

@main
struct RowLifetimeLabApp: App {
    var body: some Scene {
        WindowGroup("Row Lifetime Lab") {
            ProbeView()
        }
    }
}

struct ProbeView: View {
    @State private var revision = 0
    @State private var generation = 0

    var body: some View {
        VStack(spacing: 12) {
            HStack {
                Button("Update rows") {
                    revision += 1
                }
                Button("Reset identity") {
                    generation += 1
                }
                Text("Revision \(revision)")
            }

            ScrollView {
                LazyVStack(spacing: 8) {
                    ForEach(1...60, id: \.self) { rowID in
                        ProbeRow(rowID: rowID, revision: revision)
                    }
                }
                .padding()
            }
            .id(generation)
        }
        .padding()
        .frame(minWidth: 560, minHeight: 360)
    }
}

struct ProbeRow: View {
    let rowID: Int
    let revision: Int
    private let constructionID: UUID

    @State private var appearanceToken: UUID? = nil
    @State private var count = 0

    init(rowID: Int, revision: Int) {
        self.rowID = rowID
        self.revision = revision
        self.constructionID = UUID()
        print("init row=\(rowID) value=\(constructionID)")
    }

    var body: some View {
        let _ = print("body row=\(rowID) value=\(constructionID)")

        HStack {
            VStack(alignment: .leading) {
                Text("Sample row \(rowID)")
                Text("Revision \(revision)")
                Text(appearanceToken?.uuidString ?? "Awaiting appearance")
                    .font(.caption.monospaced())
            }
            Spacer()
            Button("Count \(count)") {
                count += 1
            }
        }
        .padding(12)
        .frame(height: 80)
        .background(Color.gray.opacity(0.12))
        .onAppear {
            if appearanceToken == nil {
                appearanceToken = UUID()
            }
            print("appear row=\(rowID) token=\(tokenDescription)")
        }
        .onDisappear {
            print("disappear row=\(rowID) token=\(tokenDescription)")
        }
        .onScrollVisibilityChange(threshold: 0.5) { visible in
            print("viewport row=\(rowID) visible=\(visible)")
        }
    }

    private var tokenDescription: String {
        appearanceToken?.uuidString ?? "unassigned"
    }
}

İki UUID farklı amaçlara hizmet eder. constructionID her başlatıcı çağrısını etiketler. appearanceToken başlangıçta boştur ve korunan satır durumu için gözlemlenen ilk görünüm sırasında atanır. Durumun ayrıldığı zamanı belirtmez. Sayı, sürekliliğini inceleyebileceğiniz başka bir görünür değer sağlar.

Apple, SwiftUI’nin State depolamasını yönettiğini ve değeri değiştiğinde bağımlı arayüz içeriğini güncellediğini belgeler. Bu nedenle tanılama, belirtecini ve sayısını özel durumda tutarken oluşturma bilgilerini sıradan bir depolanmış özellikte tutar. Bkz. State.

Program belgelenen API’lere göre incelendi, ancak burada bir Swift araç zinciri bulunmadığından derlenmedi veya çalıştırılmadı. Aşağıdaki günlük örnekleri, çalıştırmanızın nasıl yorumlanacağını açıklar; kaydedilmiş sonuçlar değildir.

Üç kontrollü denetim çalıştırın ve sonuçları yorumlayın

1. Kimliği değiştirmeden satır girdilerini güncelleyin

Uygulamayı Xcode’da çalıştırın ve hata ayıklama konsolunu açık tutun. Görünür bir satırın Count düğmesine iki kez tıklayın, belirtecini not edin, ardından Update rows düğmesine tıklayın. Satırlara aktarılan revizyon değişirken sabit tamsayı tanımlayıcıları aynı kalır.

Başlatıcı iletileri yeniden görünür, ancak seçili satır belirtecini ve sayısını korursa, korunan tanılama durumuyla birlikte değerlerin yeniden oluşturulduğuna dair kanıtınız olur. Bu başlatıcı iletilerini ayrı ekranda kalma süreleri olarak sınıflandırmayın. Görüntülenen revizyon, satırın yeni girdiyi aldığını doğrular.

Görünüm belirteci durum üzerinden atanır; bu işlem kendi başına başka bir güncellemeyi tetikleyebilir. Bu, tanılama amacıyla bilinçli olarak yapılmıştır. Kesin sayıda başlatıcı veya body iletisi beklemeyin; inceleme tamamlandığında bu gözlem noktalarını kaldırın.

2. Bir satırı görüntü alanının dışına kaydırıp geri getirin

Seçili satır %50 eşiğini geçene kadar yavaşça kaydırın. viewport iletilerini görünüm iletileriyle karşılaştırın, ardından satıra geri dönün. Belirteci ve sayısını yeniden inceleyin.

Geri döndüğünüzde belirtecin korunması, bu tanılama durumunun söz konusu hareket boyunca sürdüğünü gösterir. Belirtecin değişmesi, önceki belirtecin korunmadığı anlamına gelir. Gerçek bir uygulamadaki sıfırlamayı açıklamak için nedenini yalnızca kaydırmadan çıkarsamak yerine kimlik değişikliklerini, koşullu kaldırmayı ve kapsayıcının elde tutma davranışını inceleyin.

Apple’ın güncel yönergeleri, tembel yığınların görünüm gövdesini ve düzenini görünüm belirmeden önce getirebildiğini açıklar. Kaydırma yönü tersine dönerse, önceden getirilen bir görünüm onAppear çağrısını hiç almayabilir. Apple ayrıca ekrandaki görünüm alanı dışında kalan tembel yığın durumunun sonunda atılabileceğini açıklar ve kaydırma boyunca korunması gereken verilerin bir modelde veya dış görünümde tutulmasını önerir. Bkz. SwiftUI ile tembel yığınları ve kaydırmayı keşfetme.

Bu nedenle örnekteki satıra ait sayı, gözlem aracı olarak kullanışlıdır. Gerçek bir düzenleme arayüzünde, satır kaldırıldıktan sonra da korunması gereken değişiklikleri öğenin kararlı tanımlayıcısıyla anahtarlanan, daha uzun ömürlü bir sahipte saklayın.

3. Kimliği bilerek sıfırlayın

Reset identity düğmesine tıklayın. Program, tüm kaydırma görünümüne eklenen kimliği değiştirerek sıradan revizyon güncellemesiyle bilinçli bir karşılaştırma sağlar. Apple, id(_:) öğesine verilen değerin değiştirilmesinin, durum da dahil olmak üzere görünüm kimliğini sıfırladığını açıkça belgeler. Bkz. id(_:).

Yeni beliren satırlarda yeni belirteçler ve başlangıç sayıları olup olmadığını inceleyin. Sıfırlama kaydırma görünümü alt ağacına uygulandığı için bunu kaydırma konumunun korunmasını denetleyen kontrollü bir test olarak kullanmayın.

Kendi izlerinizi incelerken şu karar tablosunu kullanın:

Gözlem Desteklenen sonuç Sonraki denetim
Yeni başlatıcı iletileri; belirteç ve sayı korunuyor Tanılama durumu korunurken oluşturma gerçekleşti. Güncellemeler yavaşsa başlatıcı maliyetini inceleyin.
Görünüm geri çağrıları tekrarlanıyor; belirteç korunuyor Tekrarlanan görünüm, bu belirteci değiştirmedi. Eşiğe bağlı davranış için görüntü alanı geri çağrılarını kullanın.
Sıradan bir güncellemeden sonra belirteç ve sayı sıfırlanıyor Satır durumu beklendiği gibi korunmadı. Veri kimliklerini, değişen id değiştiricilerini ve koşullu dalları inceleyin.
Bir satır başlatılıyor ancak görünüm bildirmiyor Yalnızca oluşturulma, görünümü kanıtlamadı. Tembel hazırlığı ve ardından gelen kaydırma değişikliklerini göz önünde bulundurun.
Görüntü alanı görünürlüğü false oluyor Seçilen kaydırma eşiği artık karşılanmıyor. Bu eşiğin amaçlanan davranışa uyup uymadığına karar verin.

Dinamik veriler için tanımlayıcıları güncellemeler boyunca temel öğelere bağlı tutun. Her okumada yeni bir tanımlayıcı oluşturmak sürekliliği engeller. Üst kimlikleri de inceleyin: kararlı satır kimlikleri, kimliğini bilerek değiştirdiğiniz bir alt ağaç içindeki satırı koruyamaz. Apple, kararlı veri tanımlayıcılarını ve yapısal kimliği SwiftUI’nin Gizemini Çözmek yazısında ele alır.

Önceki macOS sürümlerinde hangi desteklenen yolu izleyebilirsiniz?

Örnekteki kaydırma görünürlüğü değiştiricisi macOS 15 gerektirir. Dağıtım hedefini daha eski bir sürüme düşürmek, ayrı bir uygulamayla kullanılabilirlik dalı gerektirir; onAppear eşdeğer bir eşik ölçümü değildir.

Uygulamanız zaten AppKit kaydırma arayüzüne sahipse, NSScrollView.documentVisibleRect içerik görünümü üzerinden görülebilen belge görünümünün bölümünü belge görünümü koordinatlarında sağlar. Geometri tabanlı bir alternatif, aynı koordinat sistemindeki dikdörtgenleri karşılaştırarak bu dikdörtgeni kendi satır dikdörtgenlerinizle karşılaştırmak ve kaydırma ile düzen değişikliklerinden sonra yeniden hesaplamaktır. Bkz. documentVisibleRect.

Örneğin, açıkça seçilmiş bir kural, dikdörtgen alanının en az yarısı belge görünür dikdörtgeniyle kesiştiğinde satırı görünür kabul edebilir. Bu, SwiftUI uygulamasını taklit ettiği iddiası değil, önerilen bir uygulama kuralıdır. Boş dikdörtgenleri ve koordinat dönüşümünü açıkça ele alın.

SwiftUI, uygulamanızın oluşturduğu ve yönettiği bir AppKit görünümünü tümleştirmek için NSViewRepresentable sağlar. Belgelenmiş oluşturma, güncelleme, eşgüdüm ve sonlandırma yöntemleri desteklenen bir köprü sunar. Temsil edilen görünümün düzenini SwiftUI’nin kontrolünde tutun; Apple bu görünümün çerçevesini veya sınırlarını doğrudan değiştirmenin sakıncalı olduğu konusunda uyarır. Bkz. NSViewRepresentable.

Bu geri dönüş yolu, sahip olduğunuz bir AppKit belge görünümüyle ilgilidir. SwiftUI List’in özel destekleyici görünümleri hakkında varsayımda bulunmayı gerektirmez.

TextKit düzeni, metnin ekranda olduğunu kanıtlar mı?

TextKit’te de hazırlıkla görüntü alanı geometrisini birbirinden ayırmak gerekir. Bu, kendi satırınız bir AppKit metin görünümü içeriyorsa ve soru görünür alanla kesişen metin parçalarını belirlemeye dönüşüyorsa önemlidir.

macOS 12 ve sonraki sürümlerde kullanılabilen TextKit 2’nin NSTextViewportLayoutController bileşeni, genellikle görünür alanı ve taşma kaydırma bölgesini içeren etkin bir düzen alanını yönetir. viewportBounds taşan çizimi de içerir; isteğe bağlı viewportRange ise geçerli görüntü alanı düzenini açıklar. Bunların hiçbiri, ekranda görünen karakterlerin tam listesi olarak adlandırılmamalıdır. Bkz. NSTextViewportLayoutController.

TextKit 2 uygulamasında ayırma olaylarını değil, metin düzeni geometrisini kullanın. NSTextLayoutManager, verilen metin aralığı için metin parçalarını numaralandırabilir. Elde edilen parça dikdörtgenlerini, her ikisini de aynı koordinat sistemine dönüştürdükten sonra gerçek kırpma dikdörtgeniyle karşılaştırın. Metin görünümünün textContainerOrigin değerini hesaba katın; metin kapsayıcısı koordinatlarının belge görünümü koordinatlarıyla aynı olduğunu varsaymayın. Bkz. enumerateTextSegments(in:type:options:using:) ve textContainerOrigin.

Mevcut bir TextKit 1 uygulamasında NSLayoutManager.glyphRange(forBoundingRect:in:), metin kapsayıcısı koordinatlarında bir dikdörtgen kabul eder. Apple, döndürülen bitişik aralığın bu dikdörtgenin dışındaki glifleri de içerebileceğini belirtir; bu nedenle aralık, tam görünürlük listesi değil, daha ayrıntılı inceleme için aday bir aralıktır. Bkz. glyphRange(forBoundingRect:in:).

Tanılama sırasında metin altyapısını koruyun. Apple, macOS 12 ve sonraki sürümlerde NSTextView veya metin kapsayıcısının layoutManager özelliğine erişilmesinin NSLayoutManager kullanan uyumluluk moduna geri dönmeye yol açtığını belgeler. TextKit 2 yolunda bunun yerine textLayoutManager ve API’lerini kullanın. Bkz. NSTextView.

Arayüz sorusunu yanıtlayan sinyali seçin: oluşturma için başlatıcı günlükleri, süreklilik için korunan durum, görünüm geçişleri için görünüm geri çağrıları ve görünürlük için görüntü alanı geometrisi. Kararlı kimlikler kullanan ve bilinçli olarak tek bir kimlik sıfırlaması içeren bir deney, konsol çıktısının miktarından tahmin yürütmeden bu farkları gözlemlenebilir kılar.

Kaynaklar