Pemulaan baris SwiftUI yang berulang tidak membuktikan bahawa sesuatu baris telah hilang atau kehilangan keadaannya. Untuk mendiagnosis antara muka Mac yang boleh ditatal, jejak pembinaan, keadaan yang dikekalkan, panggilan balik kemunculan dan keterlihatan dalam port pandangan secara berasingan.

Panduan ini ditujukan kepada pembangun yang menyiasat log baris berulang dalam app macOS mereka sendiri. Tugas khususnya ialah menentukan sama ada kemas kini hanya mencipta satu lagi nilai paparan, menetapkan semula keadaan baris atau mengubah baris yang memenuhi port pandangan tatal. Contoh ini menggunakan label sintetik dan butang biasa.

Apakah yang sebenarnya diberitahu oleh setiap isyarat kitar hayat?

Baris SwiftUI biasanya menggunakan struktur, iaitu jenis nilai Swift. Membina satu lagi nilai struktur berbeza daripada mencipta objek rujukan yang berterusan. Dokumentasi Swift menerangkan perbezaan ini dalam Jenis Nilai Dan Jenis Rujukan Dalam Swift.

SwiftUI mempunyai model identitinya sendiri. Apple menerangkan bahawa nilai paparan sementara boleh mewakili identiti yang sama merentas kemas kini, dan jangka hayat paparan mengikuti identiti tersebut. Oleh itu, log pemula tidak dapat membuktikan bahawa jangka hayat baris sebelumnya telah berakhir. Lihat Merungkai SwiftUI.

Gunakan isyarat berikut untuk menjawab soalan yang berbeza:

Isyarat Soalan yang dijawab Had tafsiran
Pemula baris Adakah satu lagi nilai baris dibina? Tidak membuktikan kemunculan atau penggantian keadaan.
Penilaian body Adakah perihalan paparan ini dinilai? Tidak mengira bingkai yang dipaparkan.
Keadaan baris yang dikekalkan Adakah nilai diagnostik ini kekal merentas kemas kini? Menerangkan kesinambungan keadaan itu, bukan baris yang memenuhi port pandangan.
onAppear dan onDisappear Adakah SwiftUI melaporkan peralihan kemunculan? Masa pelaksanaan bergantung pada jenis paparan.
onScrollVisibilityChange Adakah baris melepasi ambang port pandangan yang dipilih? Mengukur syarat keterlihatan tatal yang didokumenkan.

Apple mendokumenkan bahawa onAppear selesai sebelum bingkai pertama dirender, dan masa tepatnya bergantung pada jenis paparan. onDisappear berjalan selepas kemunculan, juga dengan masa yang bergantung pada paparan. Tiada satu pun kaedah ini mendokumenkan penyahperuntukan keadaan. Asingkan peristiwa panggilan balik ini daripada kesimpulan tentang jangka hayat storan. Lihat onAppear(perform:) dan onDisappear(perform:).

Untuk persoalan tentang port pandangan, tetapkan ambang secara jelas. Pada macOS 15 dan versi lebih baharu, onScrollVisibilityChange(threshold:_:) melaporkan sama ada paparan melepasi ambang keterlihatan yang diberikan; nilai lalainya ialah 0.5. Apple juga mendokumenkan panggilan balik awal apabila paparan muncul dengan ambang tersebut sudah dicapai. Lihat onScrollVisibilityChange(threshold:_:).

Dalam artikel ini, “kelihatan dalam port pandangan” bermaksud memenuhi ambang tatal tersebut. Istilah ini bukan ukuran sama ada seseorang melihat baris itu.

Bina eksperimen kecil yang memisahkan isyarat

Cipta app SwiftUI macOS menggunakan SDK yang merangkumi API macOS 15, dan tetapkan sasaran penggunaan kepada macOS 15 atau lebih baharu. Gantikan pengisytiharan app dan paparan kandungan yang dijana dengan program lengkap di bawah. Kekalkan hanya satu titik masuk @main.

Contoh ini menggunakan ScrollView dan LazyVStack supaya tingkah laku port pandangan jelas. Apple menerangkan bahawa LazyVStack mencipta item apabila diperlukan. Eksperimen ini tidak mengandaikan bahawa List mempunyai tingkah laku pemuatan atau pengekalan yang sama. Lihat 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"
    }
}

Kedua-dua UUID ini mempunyai tujuan yang berbeza. constructionID melabel setiap penggunaan pemula. appearanceToken bermula dalam keadaan kosong dan ditetapkan semasa kemunculan pertama yang diperhatikan bagi keadaan baris yang dikekalkan. Ia tidak mencatat masa peruntukan keadaan. Kiraan memberikan satu lagi nilai yang kelihatan untuk diperiksa kesinambungannya.

Apple mendokumenkan bahawa SwiftUI mengurus storan State dan mengemas kini kandungan antara muka yang bergantung padanya apabila nilainya berubah. Oleh itu, diagnostik ini menyimpan token dan kiraannya dalam keadaan persendirian, manakala maklumat pembinaan kekal sebagai sifat tersimpan biasa. Lihat State.

Program ini telah disemak berdasarkan API yang didokumenkan, tetapi tidak dikompil atau dijalankan di sini kerana toolchain Swift tidak tersedia. Contoh log di bawah menerangkan cara mentafsir hasil larian anda; contoh itu bukan hasil yang dirakam.

Jalankan tiga semakan terkawal dan tafsirkan hasilnya

1. Kemas kini input baris tanpa mengubah identiti

Jalankan app dalam Xcode dan biarkan konsol nyahpepijatnya terbuka. Klik butang Count pada baris yang kelihatan sebanyak dua kali, catat tokennya, kemudian klik Update rows. Semakan yang dihantar kepada baris berubah, manakala pengecam integer tetap kekal sama.

Jika mesej pemula muncul semula tetapi baris yang dipilih mengekalkan token dan kiraannya, anda mempunyai bukti bahawa nilai dibina berulang kali sementara keadaan diagnostik dikekalkan. Jangan anggap mesej pemula itu sebagai jangka hayat berasingan pada skrin. Semakan yang dipaparkan mengesahkan bahawa baris menerima input baharu.

Token kemunculan ditetapkan melalui keadaan, dan tindakan itu sendiri boleh menyebabkan satu lagi kemas kini. Itulah tingkah laku diagnostik yang disengajakan. Jangan mengharapkan bilangan mesej pemula atau body yang tepat, dan buang pemeriksaan ini apabila siasatan selesai.

2. Tatal baris keluar dari port pandangan dan kembali semula

Tatal perlahan-lahan sehingga baris yang dipilih melepasi ambang 50%. Bandingkan mesej viewport dengan mesej kemunculan, kemudian kembali ke baris itu. Periksa semula token dan kiraannya.

Token yang kekal selepas kembali menunjukkan kesinambungan keadaan diagnostik ini sepanjang pergerakan tersebut. Token yang berbeza bermakna token terdahulu tidak dikekalkan. Untuk menerangkan penetapan semula dalam app sebenar, siasat perubahan identiti, penyingkiran bersyarat dan tingkah laku pengekalan bekas, bukannya membuat kesimpulan tentang puncanya berdasarkan penatalan sahaja.

Panduan Apple semasa menerangkan bahawa tindanan malas boleh mengambil paparan terlebih dahulu dan menyediakan susun aturnya sebelum kemunculan. Jika arah tatal berubah, paparan yang telah disediakan mungkin tidak pernah menerima onAppear. Apple juga menerangkan bahawa keadaan tindanan malas di luar skrin akhirnya boleh dibuang, dan mengesyorkan supaya data yang perlu kekal merentas penatalan disimpan dalam model atau paparan luar. Lihat Menyelami tindanan malas dan penatalan dengan SwiftUI.

Oleh itu, kiraan setempat pada baris dalam contoh ini berguna sebagai pemeriksaan. Untuk antara muka penyuntingan sebenar, simpan perubahan yang perlu kekal selepas baris dibuang dalam pemilik yang jangka hayatnya lebih panjang, dengan kunci berupa pengecam stabil item tersebut.

3. Tetapkan semula identiti dengan sengaja

Klik Reset identity. Program mengubah identiti yang dikaitkan dengan keseluruhan paparan tatal, supaya anda boleh membuat perbandingan langsung dengan kemas kini semakan biasa. Apple secara jelas mendokumenkan bahawa perubahan pada nilai yang diberikan kepada id(_:) menetapkan semula identiti paparan, termasuk keadaannya. Lihat id(_:).

Periksa baris yang muncul semula untuk melihat token baharu dan kiraan awal. Oleh sebab tetapan semula ini meliputi subpokok paparan tatal, jangan gunakannya sebagai ujian terkawal untuk pengekalan kedudukan tatal.

Gunakan panduan keputusan ini semasa menyemak jejak anda sendiri:

Pemerhatian Kesimpulan yang disokong Semakan seterusnya
Mesej pemula baharu; token dan kiraan kekal Pembinaan berlaku sementara keadaan diagnostik dikekalkan. Periksa kos pemula jika kemas kini perlahan.
Panggilan balik kemunculan berulang; token kekal Kemunculan berulang tidak menggantikan token itu. Gunakan panggilan balik port pandangan untuk tingkah laku yang bergantung pada ambang.
Token dan kiraan ditetapkan semula selepas kemas kini biasa Keadaan baris tidak kekal seperti yang dijangkakan. Periksa ID data, pengubah id yang berubah dan cabang bersyarat.
Baris dimulakan tetapi tidak pernah melaporkan kemunculan Pembinaan sahaja tidak membuktikan kemunculan. Pertimbangkan penyediaan malas dan perubahan penatalan selepas itu.
Keterlihatan port pandangan menjadi palsu Ambang tatal yang dipilih tidak lagi dipenuhi. Tentukan sama ada ambang itu sepadan dengan tingkah laku yang dimaksudkan.

Untuk data dinamik, kekalkan pengecam yang dikaitkan dengan item asas merentas kemas kini. Pengecam baharu yang dijana setiap kali data dibaca menghalang kesinambungan. Periksa juga identiti leluhur: ID baris yang stabil tidak dapat mengekalkan baris dalam subpokok yang identitinya anda gantikan dengan sengaja. Apple membincangkan pengecam data yang stabil dan identiti struktur dalam Merungkai SwiftUI.

Apakah laluan yang disokong pada versi macOS terdahulu?

Pengubah keterlihatan tatal dalam contoh ini memerlukan macOS 15. Menurunkan sasaran penggunaan memerlukan cabang ketersediaan dengan pelaksanaan berasingan; onAppear bukan ukuran ambang yang setara.

Jika app anda sudah mempunyai antara muka penatalan AppKit, NSScrollView.documentVisibleRect memberikan bahagian paparan dokumennya yang kelihatan melalui paparan kandungan, dalam koordinat paparan dokumen. Alternatif berasaskan geometri ialah membandingkan segi empat tepat itu dengan segi empat tepat baris anda sendiri dalam sistem koordinat yang sama, kemudian mengira semula selepas perubahan tatal dan susun atur. Lihat documentVisibleRect.

Contohnya, peraturan yang dipilih secara jelas boleh mengelaskan sesuatu baris sebagai kelihatan apabila sekurang-kurangnya separuh daripada kawasan segi empat tepatnya bersilang dengan segi empat tepat yang kelihatan dalam dokumen. Ini ialah peraturan aplikasi yang dicadangkan, bukannya dakwaan bahawa peraturan itu meniru pelaksanaan SwiftUI. Kendalikan segi empat tepat kosong dan penukaran koordinat secara jelas.

SwiftUI menyediakan NSViewRepresentable untuk menyepadukan paparan AppKit yang dicipta dan diurus oleh app anda. Kaedah penciptaan, pengemaskinian, penyelarasan dan pemusnahan yang didokumenkan menyediakan jambatan yang disokong. Patuhi kawalan SwiftUI terhadap susun atur paparan yang diwakilkan; Apple memberi amaran supaya anda tidak mengubah bingkai atau sempadan paparan itu secara langsung. Lihat NSViewRepresentable.

Pilihan gantian ini berkaitan dengan paparan dokumen AppKit yang anda miliki. Ia tidak memerlukan anda membuat andaian tentang paparan sokongan peribadi bagi List SwiftUI.

Adakah susun atur TextKit membuktikan bahawa teks berada pada skrin?

TextKit juga memerlukan pemisahan antara penyediaan dengan geometri port pandangan. Hal ini penting jika baris anda mengandungi paparan teks AppKit sendiri dan persoalannya ialah segmen teks yang mana bersilang dengan kawasan yang kelihatan.

NSTextViewportLayoutController TextKit 2, yang tersedia pada macOS 12 dan lebih baharu, mengurus kawasan susun atur aktif yang biasanya merangkumi kawasan kelihatan serta kawasan lebihan tatal. viewportBounds merangkumi lukisan berlebihan, manakala viewportRange pilihan menerangkan susun atur port pandangan semasa. Kedua-duanya tidak patut dilabel semula sebagai set aksara yang kelihatan dengan tepat. Lihat NSTextViewportLayoutController.

Untuk pelaksanaan TextKit 2, gunakan geometri susun atur teks dan bukannya peristiwa peruntukan. NSTextLayoutManager boleh menghitung segmen teks bagi julat teks yang diberikan. Bandingkan segi empat tepat segmen yang terhasil dengan segi empat tepat pemotongan sebenar selepas kedua-duanya diletakkan dalam sistem koordinat yang sama. Ambil kira textContainerOrigin bagi paparan teks; jangan anggap koordinat bekas teks sama dengan koordinat paparan dokumen. Lihat enumerateTextSegments(in:type:options:using:) dan textContainerOrigin.

Untuk pelaksanaan TextKit 1 sedia ada, NSLayoutManager.glyphRange(forBoundingRect:in:) menerima segi empat tepat dalam koordinat bekas teks. Apple menyatakan bahawa julat berterusan yang dikembalikan boleh merangkumi glif di luar segi empat tepat itu, maka ia ialah julat calon untuk pemeriksaan lanjut dan bukannya senarai keterlihatan yang tepat. Lihat glyphRange(forBoundingRect:in:).

Kekalkan enjin teks semasa mendiagnosisnya. Apple mendokumenkan bahawa mengakses sifat layoutManager pada NSTextView atau bekas teks pada macOS 12 dan lebih baharu menyebabkan peralihan kepada mod keserasian yang menggunakan NSLayoutManager. Untuk laluan TextKit 2, gunakan textLayoutManager dan APInya. Lihat NSTextView.

Pilih isyarat yang menjawab persoalan antara muka: log pemula untuk pembinaan, keadaan yang dikekalkan untuk kesinambungan, panggilan balik kemunculan untuk peralihan kemunculan dan geometri port pandangan untuk keterlihatan. Eksperimen ID stabil dengan satu tetapan semula identiti yang disengajakan menjadikan perbezaan ini boleh diperhatikan tanpa membuat tekaan berdasarkan jumlah keluaran konsol.

Sumber