Daftarkan operasi buat asal pada stor model yang tahan lama, dan biarkan paparan menghantar suntingan melalui kaedah stor. Untuk editor bentuk Mac, kekalkan pratonton seretan secara setempat, buat komit sekali sahaja apabila dilepaskan, dan minta stor mendaftarkan songsangan yang turut membolehkan buat semula.
Tutorial ini membina aplikasi SwiftUI macOS kecil yang mengandungi satu segi empat tepat sintetik. Anda boleh mengalihkannya, menyembunyikan editornya, membuat asal pergerakan itu dan memaparkan semula editor. Tugas khususnya ialah mengekalkan tingkah laku buat asal apabila paparan penyuntingan hilang sedangkan modelnya masih hidup. Semuanya kekal dalam memori.
Mengapakah stor perlu menerima operasi buat asal?
Kaedah pendaftaran berasaskan penutupan Apple memerlukan sasaran jenis rujukan. Apple juga mendokumenkan bahawa pengurus buat asal mengekalkan rujukan tanpa pemilikan kepada sasaran itu. Oleh itu, mendaftarkan tindakan tidak menetapkan jangka hayat sasaran. Apple mengesyorkan penggunaan parameter sasaran pengendali dan bukannya menangkap rujukan lain kepada sasaran yang sama. Dokumentasi pendaftaran buat asal Apple.
Stor yang memiliki keadaan yang disunting ialah sasaran yang sesuai. Pemilik yang stabil memastikan stor itu terus hidup; paparan sementara memaparkannya dan meminta perubahan. Di sini, paparan akar tetingkap memiliki stor dengan @StateObject, manakala kanvas yang boleh dialih keluar memerhatikannya. SwiftUI mengekalkan objek keadaan sepanjang hayat bekas yang mengisytiharkannya, tetapi perubahan identiti bekas boleh menghasilkan tika baharu. Dokumentasi StateObject Apple.
“Songsangan milik stor” menerangkan tempat logik pemulihan berada. Pengurus buat asal masih merekod dan menjadualkan panggilan balik. Stor menyediakan nilai lama dan kaedah yang memulihkannya. Pembahagian ini mengikut seni bina yang didokumenkan Apple, yang membenarkan penerima buat asal sama ada objek yang diubah atau pemiliknya. Panduan Undo Manager Apple.
| Tanggungjawab | Pemilik dalam contoh ini |
|---|---|
| Kedudukan segi empat tepat yang dikomit | Stor model |
| Pendaftaran songsangan dan mutasi | Kaedah stor model |
| Jangka hayat stor | Akar tetingkap yang stabil |
| Terjemahan seretan sementara | Keadaan gerak isyarat kanvas |
| Sejarah dan pelaksanaan buat asal | Pengurus buat asal persekitaran |
| Memilih pengurus yang berkenaan | Paparan pada sempadan interaksi |
Laksanakan mutasi stor yang boleh diterbalikkan
Cipta aplikasi SwiftUI macOS dengan sasaran penggunaan macOS 13 atau lebih baharu dan SDK Apple semasa. Tiga fail berikut membentuk contoh lengkap; gantikan titik masuk aplikasi dan paparan kandungan yang dijana, bukannya mengekalkan pengisytiharan @main kedua. Pengkompil Swift dan rangka kerja macOS tidak tersedia dalam persekitaran penyelidikan ini, jadi contoh ini telah diperiksa tetapi tidak dikompil di sini.
Cipta ShapeStore.swift. Stor ini mempunyai satu kedudukan yang dikomit dan satu kaedah mutasi. Menangkap kedudukan sebelumnya sebelum penetapan memberikan kaedah itu maklumat yang diperlukan untuk membalikkan suntingan.
import Foundation
import Combine
@MainActor
final class ShapeStore: ObservableObject {
@Published private(set) var position = CGPoint(x: 120, y: 100)
func move(to newPosition: CGPoint, undoManager: UndoManager?) {
let previousPosition = position
guard newPosition != previousPosition else { return }
if let undoManager {
undoManager.registerUndo(withTarget: self) {
[weak undoManager] store in
store.move(
to: previousPosition,
undoManager: undoManager
)
}
undoManager.setActionName("Move Rectangle")
}
position = newPosition
}
}
Songsangan memanggil kaedah yang sama. Katakan kedudukan berubah daripada (120, 100) kepada (160, 100). Panggilan balik yang didaftarkan meminta (120, 100). Apabila buat asal melaksanakan panggilan balik itu, kaedah tersebut melihat (160, 100) sebagai nilai sebelumnya lalu mendaftarkan tindakan untuk kembali semula. Apple mendokumenkan bahawa pendaftaran yang dibuat semasa proses buat asal menjadi operasi buat semula. Melangkau pendaftaran setiap kali isUndoing bernilai benar akan memutuskan corak ini. Panduan Undo Manager Apple.
Penutupan beroperasi pada parameter store dan menangkap pengurus secara lemah. Ini mengelakkan rujukan kuat daripada penutupan tersimpan pengurus kembali kepada pengurus itu. Dokumentasi pengiraan rujukan Swift menjelaskan bahawa rujukan lemah tidak memastikan tika yang dirujuk terus hidup dan menjadi nil apabila ia dinyahperuntukkan. The Swift Programming Language: Automatic Reference Counting.
Stor dan kaedah mutasinya diasingkan pada aktor utama. Ini sepadan dengan dokumentasi Apple semasa: UndoManager dan pengendali pendaftaran diasingkan pada aktor utama. Kaedah itu melaksanakan pendaftaran dan mutasi secara segerak, sekali gus mengekalkan suntingan kecil ini dalam interaksi yang memanggilnya. Dokumentasi UndoManager Apple.
Kekalkan stor di atas editor yang boleh dialih keluar
Cipta StoreUndoDemoApp.swift. Akar memiliki stor tanpa mengira sama ada kanvas dipaparkan. Butangnya membaca pengurus persekitaran semasa apabila sesuatu tindakan dijalankan. Kanvas menerima pengurus yang sama untuk suntingannya.
import SwiftUI
@main
struct StoreUndoDemoApp: App {
var body: some Scene {
WindowGroup {
EditorRoot()
}
}
}
@MainActor
struct EditorRoot: View {
@StateObject private var store = ShapeStore()
@State private var showsEditor = true
@Environment(\.undoManager) private var undoManager
var body: some View {
VStack(spacing: 16) {
HStack {
Button("Move Right") {
store.move(
to: CGPoint(
x: store.position.x + 20,
y: store.position.y
),
undoManager: undoManager
)
}
Button("Undo") {
if let undoManager, undoManager.canUndo {
undoManager.undo()
}
}
Button("Redo") {
if let undoManager, undoManager.canRedo {
undoManager.redo()
}
}
Toggle("Show Editor", isOn: $showsEditor)
}
Text(
"Position: \(Int(store.position.x)), " +
"\(Int(store.position.y))"
)
if showsEditor {
ShapeCanvas(store: store, undoManager: undoManager)
} else {
Text("Editor hidden")
.frame(width: 400, height: 260)
}
}
.padding(20)
.frame(width: 620, height: 380)
}
}
Pengurus buat asal SwiftUI dalam persekitaran adalah pilihan. Apple secara jelas membenarkan pendaftaran buat asal dilangkau apabila persekitaran membekalkan nil. Contoh ini tetap menggerakkan segi empat tepat dalam keadaan itu, tetapi tidak mencipta entri buat asal. Jika operasi buat asal yang dijangka tiada, periksa sama ada interaksi itu menerima pengurus sebelum mengubah logik songsangan. Dokumentasi undoManager Apple bagi persekitaran.
Menghantar pengurus ke setiap mutasi juga menjadikan konteks tetingkap jelas. Stor tidak menyimpan pengurus tetingkap yang kebetulan muncul dahulu. Untuk contoh ini, setiap akar tetingkap memiliki stor sendiri. Jika beberapa tetingkap kemudian menyunting satu model kongsi, tentukan sejarah mana yang memiliki suntingan itu sebelum menggunakan semula susunan ini merentas tetingkap.
Komit satu seretan tanpa memanjangkan kumpulan buat asal merentasi peristiwa
Cipta ShapeCanvas.swift. Semasa menyeret, kedudukan segi empat tepat yang dipaparkan merangkumi terjemahan sementara. Kedudukan stor yang dikomit hanya berubah apabila gerak isyarat berakhir dengan jayanya. Selesaikan seretan sebelum menggunakan arahan suntingan atau buat asal yang lain; contoh ringkas ini mengandaikan interaksi berurutan dan tidak mengurus arahan serentak semasa seretan.
import SwiftUI
@MainActor
struct ShapeCanvas: View {
@ObservedObject var store: ShapeStore
let undoManager: UndoManager?
@GestureState private var translation: CGSize = .zero
var body: some View {
ZStack(alignment: .topLeading) {
Color.gray.opacity(0.12)
Rectangle()
.fill(Color.blue)
.frame(width: 70, height: 50)
.position(
x: store.position.x + translation.width,
y: store.position.y + translation.height
)
.gesture(
DragGesture()
.updating($translation) { value, state, _ in
state = value.translation
}
.onEnded { value in
store.move(
to: CGPoint(
x: store.position.x
+ value.translation.width,
y: store.position.y
+ value.translation.height
),
undoManager: undoManager
)
}
)
}
.frame(width: 400, height: 260)
.clipped()
}
}
Gerak isyarat menggunakan terjemahan akhirnya untuk mengomit pergerakan. Ia tidak bergantung pada masa sifat pratonton ditetapkan semula. Apple mendokumenkan bahawa updating(_:body:) menetapkan semula keadaan gerak isyarat apabila gerak isyarat berakhir atau dibatalkan, manakala onEnded(_:) hanya berjalan selepas gerak isyarat selesai dengan jayanya. Oleh itu, gerak isyarat yang dibatalkan dalam reka bentuk ini tidak menghasilkan pergerakan yang dikomit atau tindakan buat asal baharu. Dokumentasi pengemaskinian gerak isyarat Apple, Dokumentasi penyelesaian gerak isyarat Apple.
Ini mengekalkan tiga sempadan yang berasingan: kemas kini pratonton mengubah keadaan paparan sementara; penyelesaian yang berjaya meminta satu suntingan model; stor mendaftarkan songsangan suntingan itu. Sebaliknya, memanggil mutasi stor pada setiap kemas kini seretan akan mendaftarkan kedudukan perantaraan sepanjang gerak isyarat.
Pengumpulan peristiwa lalai Apple mengumpulkan pendaftaran di sekitar giliran gelung peristiwa. Seretan boleh menghasilkan kemas kini merentasi beberapa giliran, jadi pengumpulan peristiwa sahaja tidak menyatakan transaksi seluruh seretan yang dimaksudkan. Di sini, mengomit sekali sahaja mengelakkan masalah itu tanpa mengubah konfigurasi pengurus kongsi. Dokumentasi groupsByEvent Apple.
Untuk arahan masa hadapan yang mengubah beberapa item secara segerak, kumpulan buat asal yang eksplisit dan seimbang boleh mengumpulkan songsangannya. Kekalkan permulaan dan pengakhirannya dalam arahan itu. Jika mutasi model secara langsung semasa seretan diperlukan, reka operasi mula, kemas kini, komit dan batal yang berasingan, termasuk cara arahan lain berinteraksi dengan suntingan aktif. Pendekatan pratonton sahaja di sini sengaja mengekalkan kerumitan transaksi tambahan itu di luar stor.
Sahkan pemilikan, buat semula dan penghalaan tetingkap secara berasingan
Gunakan urutan berikut dengan aplikasi yang sedang berjalan. Koordinat ialah nilai jangkaan ilustrasi daripada keadaan awal dan kenaikan butang yang dibekalkan, bukannya hasil yang diukur.
| Tindakan | Kedudukan atau hasil yang dijangka setelah dikomit |
|---|---|
| Lancarkan tetingkap baharu | (120, 100) |
Klik Move Right |
(140, 100) |
Matikan Show Editor |
Kanvas hilang; kedudukan kekal (140, 100) |
Klik Undo |
Kedudukan menjadi (120, 100) sementara kanvas kekal tiada |
Klik Redo |
Kedudukan menjadi (140, 100) |
Hidupkan Show Editor |
Segi empat tepat muncul pada kedudukan yang dibuat semula |
Selesaikan seretan, kemudian klik Undo |
Segi empat tepat kembali ke kedudukan sebelum seretan itu |
Pemeriksaan editor tersembunyi membezakan pemilikan model daripada jangka hayat persembahan. Jika buat asal hanya berfungsi semasa panel tertentu wujud, periksa sasaran yang didaftarkan dengan pengurus dan objek yang mengekalkannya. Jika buat asal berfungsi tetapi buat semula tidak, periksa sama ada songsangan itu mendaftarkan operasi untuk kembali semula. Jika satu seretan memerlukan banyak tindakan buat asal, periksa sama ada kemas kini pratonton sampai ke mutasi stor yang boleh dibuat asal.
Butang eksplisit memudahkan laluan mutasi diperiksa. Untuk tingkah laku menu Mac biasa, uji juga menu Edit standard dengan tetingkap yang dimaksudkan aktif. Responder asas AppKit mendapatkan pengurus buat asalnya daripada responder seterusnya. Apabila tetingkap meminta pengurus daripada wakilnya, windowWillReturnUndoManager(_:) ialah titik penyesuaian yang didokumenkan; tanpa pelaksanaan itu, tetingkap mencipta pengurusnya sendiri. Mendaftar pada pengurus yang tidak berkaitan tidak menetapkan penghalaan rantaian responder yang dimaksudkan. Dokumentasi undoManager NSResponder Apple, Dokumentasi wakil pengurus buat asal tetingkap Apple.
Stor juga memerlukan sempadan pelupusan yang ditentukan dengan jelas. Jika anda menggantikan stor sementara pengurusnya masih wujud, alih keluar pendaftaran stor itu sebelum melepaskannya dengan menggunakan removeAllActions(withTarget:) pada setiap pengurus yang menyimpan tindakannya. Jangan kosongkan sejarah hanya kerana kanvas hilang: tingkah laku yang dibina di sini ialah mengekalkan suntingan stor sepanjang kehilangan itu. Dokumentasi pembersihan buat asal khusus sasaran Apple.
Contoh ini mempunyai satu segi empat tepat tetap dan tiada laluan pemadaman. Apabila dikembangkan menjadi koleksi, tangkap pengecam item yang stabil dan nilai yang diperlukan untuk membalikkan setiap operasi. Pemadaman memerlukan songsangan yang boleh memulihkan item dan susunan yang dimaksudkan; indeks tatasusunan lama sahaja tidak mencukupi jika koleksi boleh berubah.
Reka bentuk yang lengkap memberikan akar tanggungjawab terhadap jangka hayat, kanvas tanggungjawab terhadap keadaan interaksi sementara, dan stor tanggungjawab terhadap suntingan yang boleh diterbalikkan. Menyembunyikan kanvas membiarkan suntingan itu tersedia kerana kaedah songsangannya beroperasi pada stor yang masih hidup.
Sumber
- registerUndo(withTarget:handler:) — Apple
- StateObject — Apple
- Undo Manager — Apple
- Automatic Reference Counting — Swift.org
- UndoManager — Apple
- undoManager — EnvironmentValues — Apple
- updating(_:body:) — Apple
- onEnded(_:) — Apple
- groupsByEvent — Apple
- undoManager — NSResponder — Apple
- windowWillReturnUndoManager(_:) — Apple
- removeAllActions(withTarget:) — Apple