Agar sheet SwiftUI tetap terbuka selama pembaruan, pertahankan identitas host presentasinya dan pastikan binding presentasinya tetap aktif. Pasang sheet pada kontainer yang stabil agar konten anak dapat berubah tanpa mengganti presenter.

Panduan ini ditujukan bagi pengembang yang membuat app Mac dengan sheet yang dapat diedit dan seharusnya tetap terlihat saat antarmuka di bawahnya berubah. Contoh yang dibahas menggunakan teks lokal sintetis dan memisahkan tiga hal yang dapat diperiksa secara terpisah: status presentasi, masa hidup tampilan yang menampilkan sheet, dan isi editor.

Mengapa pembaruan tampilan dapat membuat sheet menghilang?

SwiftUI dapat membuat nilai tampilan baru saat mengevaluasi body sambil mempertahankan identitas tampilan yang ditampilkan. Status bertahan selama masa hidup identitas tersebut. Menghapus tampilan atau mengubah identitasnya akan mengakhiri masa hidup itu; berpindah di antara cabang kondisional juga dapat menghasilkan identitas berbeda, meskipun kedua cabang berisi tipe tampilan yang sama. Apple menjelaskan hubungan ini dalam Demystify SwiftUI.

Untuk sheet yang dikendalikan oleh Boolean, kontrol presentasi yang didokumentasikan adalah binding yang diteruskan ke sheet(isPresented:onDismiss:content:): nilai true meminta presentasi. Saat menyelidiki sheet yang menghilang, periksa binding tersebut dan pastikan tampilan yang memuat modifier tetap ada. Nilai true yang disimpan di tempat lain tidak membuktikan bahwa host presentasi asli tetap bertahan. Lihat dokumentasi sheet Boolean dari Apple.

Penyebab yang sangat langsung adalah identifier eksplisit yang berubah. Apple mendokumentasikan bahwa perubahan nilai yang diberikan ke id(_:) akan mengatur ulang identitas tampilan, termasuk statusnya. Periksa tampilan yang menampilkan sheet dan leluhurnya untuk mencari identifier yang berasal dari penghitung revisi, teks yang dapat diedit, atau nilai yang baru dibuat. Hapus identifier semacam itu jika perilaku pengaturan ulangnya tidak diinginkan. Lihat id(_:).

Kesimpulan praktis dari desain ini adalah menempatkan modifier sheet di luar bagian antarmuka yang berubah. Jika tampilan pemuatan, baris yang difilter, atau tata letak detail alternatif dapat dihapus, leluhur persisten terdekat yang sesuai biasanya menjadi pemilik presentasi yang lebih baik.

Buat pemilik sheet yang stabil dengan contoh Mac lengkap

Buat app SwiftUI macOS di Xcode, tetapkan target deployment ke macOS 12 atau lebih baru, lalu ganti deklarasi app dan tampilan konten yang dihasilkan dengan satu file berikut. Pertahankan hanya satu deklarasi app @main. Persyaratan minimum macOS 12 mendukung aksi lingkungan dismiss pada contoh ini; TextEditor sendiri tersedia mulai macOS 11. Lihat dokumentasi Apple untuk dismiss dan TextEditor.

Tampilan root jendela memiliki flag presentasi, teks yang dapat diedit, penghitung revisi, dan flag tata letak. VStack terluarnya memuat modifier sheet. Hanya konten di dalam kontainer itu yang berpindah cabang. Apple menyarankan agar status dideklarasikan secara privat pada tampilan tertinggi yang membutuhkannya dan binding diteruskan ke anak yang perlu mengubahnya. Lihat State.

import SwiftUI

@main
struct StableSheetDemoApp: App {
    var body: some Scene {
        WindowGroup {
            SheetOwnerView()
        }
    }
}

struct SheetOwnerView: View {
    @State private var isEditorPresented = false
    @State private var note = "Sample note Alpha"
    @State private var revision = 0
    @State private var compactLayout = false

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text("Local Notes")
                .font(.title)

            if compactLayout {
                Text("Compact summary: revision \(revision)")
            } else {
                VStack(alignment: .leading, spacing: 8) {
                    Text("Expanded summary")
                    Text("Revision \(revision)")
                    Text(note)
                }
            }

            Button("Edit Note") {
                isEditorPresented = true
            }
        }
        .padding(24)
        .frame(minWidth: 440, minHeight: 260)
        .sheet(isPresented: $isEditorPresented) {
            NoteEditorSheet(
                note: $note,
                revision: $revision,
                compactLayout: $compactLayout
            )
        }
    }
}

struct NoteEditorSheet: View {
    @Binding var note: String
    @Binding var revision: Int
    @Binding var compactLayout: Bool
    @Environment(\.dismiss) private var dismiss

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text("Edit Sample Note")
                .font(.headline)

            TextEditor(text: $note)
                .frame(minHeight: 160)

            Text("Owner revision: \(revision)")

            Button("Update Owner") {
                revision += 1
                compactLayout.toggle()
            }

            HStack {
                Spacer()
                Button("Done") {
                    dismiss()
                }
            }
        }
        .padding(24)
        .frame(width: 460, height: 340)
    }
}

Ini adalah app demonstrasi lengkap yang ditinjau berdasarkan API yang didokumentasikan. Contoh ini belum dikompilasi atau dijalankan di sini karena lingkungan ini tidak memiliki toolchain Swift atau runtime macOS.

Cara menguji pembaruan tanpa menutup sheet

  1. Jalankan app dan klik Edit Note.
  2. Ganti teks contoh dengan Sample note Beta.
  3. Klik Update Owner beberapa kali. Setiap klik menambah status pemilik dan mengganti konten kondisional pemilik.
  4. Pastikan editor tetap terlihat, teksnya tetap Sample note Beta, dan tampilan revisinya bertambah.
  5. Klik Done. Pastikan jendela menampilkan teks yang telah diedit dalam tata letak yang diperluas saat tata letak tersebut aktif.
  6. Buka kembali editor dan pastikan teks yang telah diedit tetap tersedia.

Tombol pembaruan berada di dalam sheet sehingga pengujian tidak bergantung pada klik jendela di bawahnya. AppKit mendokumentasikan sheet sebagai modal dokumen: selama sheet terlihat, sebagian besar peristiwa yang ditujukan ke jendela induknya dilarang. Memperbarui pemilik melalui binding memungkinkan contoh ini menguji perubahan status secara langsung. Lihat beginSheet(_:completionHandler:).

Tentukan arti menutup editor

Contoh ini langsung mengedit teks milik pemiliknya. Done menutup editor; tindakan itu tidak menjalankan operasi penyimpanan terpisah. String tetap berada dalam status lokal jendela, dan contoh ini tidak menyediakan persistensi antar-peluncuran app. Perilaku tersebut mengikuti desain binding bersama yang dijelaskan dalam State.

Jika tugas Anda memerlukan draf yang dapat dibatalkan, gunakan nilai draf terpisah dan salin ke dokumen hanya setelah tindakan konfirmasi eksplisit. Tentukan perilaku itu sebelum menerapkan penutupan: memutuskan apakah draf akan disimpan atau dibuang berbeda dari memutuskan tampilan mana yang memiliki sheet.

Diagnosis penutupan dengan tiga pemeriksaan terpisah

Gunakan urutan berikut pada app Anda sendiri. Urutan ini mengubah aturan identitas umum menjadi penyelidikan terarah, tanpa menganggap semua sheet yang menghilang memiliki penyebab yang sama.

Pemeriksaan Hal yang diperiksa Perbaikan praktis
Nilai presentasi Setiap penulisan yang menetapkan Boolean ke false atau mengosongkan item presentasi opsional Tempatkan penulisan penutupan hanya pada tindakan penutupan yang disengaja
Identitas presenter Cabang kondisional, baris yang dihapus, identifier leluhur yang berubah Pindahkan status presentasi dan modifier ke leluhur stabil yang sesuai
Masa hidup konten Status lokal sheet, identifier konten yang berubah, sinkronisasi editor Pertahankan sesi pengeditan yang diinginkan dan perbarui konten yang ada secara sengaja

Pertama, cari penetapan ke properti presentasi dan pemanggilan dismiss() di proyek Anda. Callback penyegaran mungkin secara eksplisit menutup sheet. Baca dismiss di dalam tampilan sheet itu sendiri, seperti pada contoh: Apple mendokumentasikan bahwa aksi tersebut berlaku pada lingkungan tempat aksi itu diperoleh. Membacanya di tampilan root jendela dapat menargetkan jendela itu sendiri. Lihat dismiss.

Kedua, periksa hierarki di atas modifier. Jika sheet dipasang pada tampilan detail yang dihapus saat penyegaran, pindahkan modifier ke atas batas penggantian tersebut. Pindahkan juga status presentasi yang dimiliki secara lokal. Menyimpan flag di anak yang identitasnya berakhir membuat penyimpanannya rentan terhadap penggantian, sesuai penjelasan Apple tentang masa hidup tampilan.

Ketiga, bedakan presentasi yang tertutup dari editor yang tetap terlihat tetapi kehilangan drafnya. Mulailah dengan contoh TextEditor sederhana, lalu tambahkan kembali konten khusus secara bertahap. Perbandingan ini adalah metode diagnostik: metode ini membantu menentukan apakah masalah berasal dari kepemilikan presentasi atau logika pembaruan konten Anda.

Haruskah beralih ke sheet yang dikendalikan oleh item?

Gunakan sheet(item:onDismiss:content:) ketika entitas terpilih tertentu menjadi sumber presentasi. Apple mendokumentasikan item opsional yang sesuai dengan Identifiable, meneruskan item yang bukan nil ke closure konten, dan menjelaskan penggantian saat item berubah. Beralih ke overload lain tidak dengan sendirinya mempertahankan presenter yang masa hidupnya berakhir. Lihat sheet yang dikendalikan oleh item.

Pilih identifier yang mewakili entitas selama sesi pengeditan yang diinginkan. Protokol Identifiable Swift menyatakan identitas yang stabil, dan dokumentasinya secara eksplisit menyerahkan durasi serta cakupan yang diperlukan kepada tipe yang berpartisipasi. Identifier baru setiap kali suatu properti dibaca tidak dapat mewakili entitas yang berkelanjutan. Lihat Identifiable.

Untuk editor sederhana seperti demonstrasi ini, flag presentasi Boolean ditambah binding teks terpisah memudahkan pemeriksaan hubungan yang diinginkan. Untuk editor item terpilih, pisahkan identitas pilihan dari kolom yang sering berubah. Perlakukan pemilihan entitas lain sebagai keputusan presentasi yang disengaja, dan tentukan terlebih dahulu apa yang harus terjadi pada draf saat ini.

Apakah menonaktifkan penutupan interaktif akan menjaga sheet tetap hidup?

interactiveDismissDisabled(_:) mengendalikan penutupan nonprogramatis. Apple secara eksplisit menyatakan bahwa modifier tersebut tidak mencegah penutupan akibat perubahan binding presentasi atau pemanggilan dismiss. Karena itu, peran yang didokumentasikannya tidak memberi solusi untuk penggantian identitas. Lihat interactiveDismissDisabled(_:).

Terapkan modifier di dalam hierarki sheet hanya jika antarmuka Anda memerlukan perilaku penutupan tersebut, dan tetap sediakan jalur penyelesaian yang eksplisit. Untuk tugas ini, mulailah dengan mempertahankan host dan statusnya. Menambahkan pembatasan penutupan sebelum menemukan penyebabnya dapat membuat masalah kepemilikan awal tetap belum terselesaikan.

Apa yang berubah saat sheet memuat konten AppKit atau TextKit?

Untuk NSTextView yang disematkan dengan NSViewRepresentable, pisahkan pembuatan dari pembaruan. Apple mendefinisikan makeNSView(context:) sebagai langkah pembuatan dan updateNSView(_:context:) sebagai tempat menerapkan informasi SwiftUI berikutnya. Koordinator meneruskan perubahan delegate atau target-action kembali ke SwiftUI. Lihat NSViewRepresentable dan makeNSView(context:).

Aturan implementasi yang berguna berdasarkan siklus hidup tersebut adalah menghindari pembuatan ulang editor hanya karena status pemilik yang tidak terkait berubah. Sebelum mengganti isinya, tentukan apakah teks model yang masuk benar-benar berbeda dan bagaimana pengeditan lokal yang berlangsung bersamaan harus ditangani. Sheet yang stabil saja tidak menyelesaikan penulisan yang saling bersaing terhadap teksnya.

TextKit menyediakan penyimpanan teks, tata letak, dan perenderan. NSTextView AppKit mengemas kemampuan tersebut serta menyediakan mesin tata letak modern dan lama. Mekanisme itu berkaitan dengan konten editor; kepemilikan presentasi tetap memerlukan masa hidupnya sendiri yang stabil. Gunakan TextKit saat tugas pengeditan membutuhkannya, dan selidiki perilaku pembaruan teks secara terpisah dari penutupan sheet. Lihat gambaran umum TextKit dari Apple.

Kapan presentasi AppKit yang dimiliki jendela sesuai digunakan?

Jika app Anda yang sudah ada mengelola siklus hidup jendelanya di AppKit, macOS 26 memperkenalkan NSWindow.beginSheet(content:completionHandler:) untuk menampilkan konten SwiftUI langsung pada jendela tersebut. Apple mendokumentasikan dukungan untuk aksi lingkungan dismiss sheet, pengaturan ukuran, toolbar, dan kontrol penutupan interaktif. Gunakan SDK macOS 26 dan pastikan batas ketersediaan macOS 26 terpenuhi sebelum mengadopsinya. Lihat beginSheet(content:completionHandler:).

NSWindow.HostingSheetRepresentation yang dikembalikan dapat mengubah tampilan root yang ditampilkan atau mendukung penutupan terprogram dari AppKit. API ini menyediakan opsi terdokumentasi saat kepemilikan tingkat jendela sesuai dengan arsitektur app. API ini tidak diperlukan untuk contoh kontainer SwiftUI yang stabil di atas. Lihat NSWindow.HostingSheetRepresentation.

Untuk macOS versi sebelumnya, beginSheet(_:completionHandler:) yang sudah tersedia di AppKit menerima sheet NSWindow dan mendokumentasikan antrean presentasi. Jangan menganggap overload konten SwiftUI yang lebih baru tersedia di sistem tersebut. Lihat beginSheet(_:completionHandler:).

Pertahankan pemilik yang stabil, pastikan nilai presentasi tetap aktif, dan pertahankan sumber kebenaran editor yang diinginkan. Verifikasi kondisi tersebut dengan tindakan pembaruan lokal sederhana sebelum memulihkan konten yang lebih kompleks. Ini memberi Anda cara konkret untuk terus mengedit saat antarmuka diperbarui, sesuai batas masa hidup dan API yang didokumentasikan Apple.

Sumber