Untuk mengalihkan kumpulan dan item anaknya bersama-sama, simpan anak-anak dalam model induk dan susun semula koleksi induk. Panduan ini membina senarai SwiftUI Mac ringkas yang membolehkan setiap kumpulan dialihkan sebagai satu baris, dengan anak-anaknya kekal bersama.
Tugas ini ialah menyusun semula kumpulan dalam aplikasi anda sendiri: contohnya, mengalihkan Group A, yang mengandungi Tile A1 dan Tile A2, ke bawah Group B. Contoh ini menggunakan data setempat sintetik dan menyediakan butang Move Up dan Move Down khusus bersama panggil balik peralihan senarai SwiftUI.
Apakah yang patut dianggap sebagai satu item yang boleh dialihkan?
Pastikan unit peralihan sepadan dengan unit dalam data anda. Bagi antara muka ini, tatasusunan luar mengandungi kumpulan, dan setiap kumpulan mengandungi tatasusunan anaknya sendiri. Menyusun semula tatasusunan luar mengubah susunan kumpulan sambil mengekalkan anak setiap kumpulan dan susunan sedia ada mereka.
Wakilkan setiap kumpulan sebagai satu baris senarai komposit: tajuk, butang peralihan dan label anak yang dianjak ke dalam VStack. Lampirkan onMove(perform:) pada ForEach luar. Apple menerangkan bahawa ofset panggil balik merujuk kepada koleksi asas paparan dinamik itu; dalam kes ini, koleksi tersebut ialah tatasusunan kumpulan. Dokumentasi panggil balik peralihan Apple.
Berikan pengecam yang stabil kepada kumpulan dan anak. ForEach Apple memerlukan elemen yang dikenal pasti, sama ada melalui Identifiable atau laluan kunci pengecam yang dinyatakan secara eksplisit. Dalam reka bentuk ini, pengecam dimiliki oleh model dan kekal sama apabila kedudukannya berubah. Elakkan penggunaan kedudukan tatasusunan sebagai identiti bagi kandungan yang boleh disusun semula. Dokumentasi ForEach Apple.
Paparan Group SwiftUI bukan model aplikasi yang digunakan di bawah. Menamakan bekas atau memaparkan tajuk tidak menentukan rekod anak yang patut bergerak bersamanya. Tatasusunan children yang disimpan oleh induk menjadikan hubungan itu jelas.
Bina contoh SwiftUI yang lengkap
Cipta projek aplikasi macOS dalam Xcode dengan antara muka SwiftUI dan sasaran penggunaan macOS 12 atau lebih baharu. Gantikan fail entri aplikasi yang dijana dengan program lengkap di bawah, dan pastikan hanya ada satu pengisytiharan aplikasi @main dalam sasaran. Contoh ini sengaja menggunakan senarai biasa dan butang teks supaya pergerakannya mudah diperiksa.
import Foundation
import SwiftUI
private struct DemoItem: Identifiable {
let id: String
let title: String
}
private struct DemoGroup: Identifiable {
let id: String
let title: String
let children: [DemoItem]
}
@main
struct GroupMoveDemoApp: App {
var body: some Scene {
WindowGroup {
GroupCollectionView()
}
}
}
private struct GroupCollectionView: View {
@State private var groups: [DemoGroup] = [
DemoGroup(
id: "group-a", title: "Group A",
children: [
DemoItem(id: "a1", title: "Tile A1"),
DemoItem(id: "a2", title: "Tile A2")
]
),
DemoGroup(
id: "group-b", title: "Group B",
children: [
DemoItem(id: "b1", title: "Tile B1")
]
),
DemoGroup(
id: "group-c", title: "Group C",
children: []
)
]
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text("Arrange Groups")
.font(.title2)
.padding(.horizontal)
List {
ForEach(groups) { group in
VStack(alignment: .leading, spacing: 8) {
HStack {
Text(group.title).font(.headline)
Spacer()
Button("Move Up") {
moveOne(id: group.id, down: false)
}
.disabled(group.id == groups.first?.id)
Button("Move Down") {
moveOne(id: group.id, down: true)
}
.disabled(group.id == groups.last?.id)
}
.buttonStyle(BorderlessButtonStyle())
ForEach(group.children) { child in
Text(child.title)
.padding(.leading, 16)
}
}
.padding(.vertical, 8)
}
.onMove(perform: moveGroups)
}
}
.padding(.vertical)
.frame(minWidth: 520, minHeight: 320)
}
private func moveGroups(from source: IndexSet, to destination: Int) {
withAnimation {
groups.move(fromOffsets: source, toOffset: destination)
}
}
private func moveOne(id: String, down: Bool) {
guard let index = groups.firstIndex(where: { $0.id == id }) else {
return
}
let neighbor = down ? index + 1 : index - 1
guard groups.indices.contains(neighbor) else {
return
}
let destination = down ? index + 2 : index - 1
moveGroups(from: IndexSet(integer: index), to: destination)
}
}
ForEach luar menghasilkan satu VStack bagi setiap kumpulan. ForEach bersarangnya memaparkan anak-anak dalam baris itu; tiada pengendali peralihan anak yang berasingan. Kedua-dua butang peralihan mencari kedudukan semasa kumpulan melalui ID dan memanggil mutasi yang sama seperti onMove.
Jalankan aplikasi dan klik Move Down di sebelah Group A. Susunan yang dijangka ialah Group B, Group A, Group C, dengan Tile A1 dan Tile A2 masih dipaparkan di bawah Group A. Mengklik Move Up di sebelah kumpulan itu akan memulihkan susunan asal. Group C juga menunjukkan bahawa kumpulan tanpa anak masih boleh dialihkan.
Program ini menyimpan susunannya dalam ingatan sepanjang hayat paparan semasa; program ini tidak mempunyai lapisan ketekalan data. Rujukan dan logik peralihannya telah diperiksa, tetapi ia tidak dikompil di sini kerana shell yang tersedia tidak mempunyai pengkompil Swift. Bina dan periksa antara muka dalam Xcode pada Mac sebelum mengintegrasikannya.
Mengapakah peralihan ke bawah menggunakan destinasi yang berbeza?
Destinasi yang dihantar kepada move(fromOffsets:toOffset:) ialah sempadan sisipan dalam koleksi asal. Apple mentakrifkannya sebagai ofset sebelum elemen yang dialihkan disisipkan, dalam julat 0...count. Menghantar ofset elemen itu sendiri atau ofset berikutnya tidak menghasilkan perubahan. Dokumentasi peralihan koleksi Apple.
Bagi susunan permulaan contoh [A, B, C], operasi berasingan ini menunjukkan perbezaannya:
| Hasil yang dikehendaki | Ofset sumber | Destinasi yang dihantar kepada move |
Hasil |
|---|---|---|---|
Alihkan A ke bawah B |
0 |
2 |
[B, A, C] |
Alihkan B ke atas A |
1 |
0 |
[B, A, C] |
Alihkan A ke hujung |
0 |
3 |
[B, C, A] |
Kekalkan A di tempatnya |
0 |
1 |
[A, B, C] |
Itulah sebabnya butang ke bawah menggunakan index + 2: ia menyisip sebelum elemen selepas jiran seterusnya, atau di hujung. Butang ke atas menggunakan index - 1. Bagi ofset yang diberikan terus oleh onMove, hantarkannya kepada kaedah koleksi tanpa mengira semula menggunakan pengiraan khusus butang ini.
Pengiraan destinasi dalam contoh ini digunakan untuk satu peralihan bersebelahan. Bagi panggil balik peralihan yang mengandungi berbilang ofset sumber, gunakan IndexSet lengkap dengan kaedah koleksi, yang mendokumenkan bahawa susunan elemen yang dialihkan dikekalkan. Dokumentasi peralihan koleksi Apple.
Antara muka manakah yang memerlukan pelaksanaan berbeza?
Pilih pelaksanaan berdasarkan cara kumpulan dipaparkan dan cara pengguna perlu berinteraksi dengan anak-anaknya. Contoh baris komposit sesuai apabila kumpulan perlu sentiasa bergerak sebagai satu blok. Contoh ini sengaja meletakkan label anak dalam blok itu, bukannya sebagai baris senarai berasingan.
| Keperluan antara muka | Titik permulaan yang sesuai | Tanggungjawab peralihan |
|---|---|---|
| Tajuk kumpulan dan anak-anaknya membentuk satu blok yang boleh dialihkan | ForEach luar dengan satu baris komposit bagi setiap kumpulan |
Susun semula tatasusunan induk |
| Hierarki boleh dikembangkan dengan keturunan bersarang | OutlineGroup |
Laksanakan peralihan model untuk koleksi induk yang dimaksudkan |
| Koleksi AppKit dengan satu bahagian bagi setiap kumpulan | NSCollectionView |
Kemas kini sumber data, kemudian maklumkan paparan koleksi tentang peralihan bahagian |
| Teks boleh edit tersuai dalam item | TextKit dalam editor item | Kekalkan penyusunan semula kumpulan dalam model koleksi sekeliling |
Pokok boleh dikembangkan memerlukan peraturan peralihan yang jelas
Apple menerangkan OutlineGroup sebagai paparan data berstruktur pokok yang dikenal pasti, dengan kawalan pendedahan untuk mengembangkan dan meruntuhkan cabang. Ia tersedia pada macOS 11 dan lebih baharu. Tingkah laku paparannya yang didokumenkan tidak menetapkan dasar umum untuk menyusun semula subpokok bagi aplikasi anda. Dokumentasi OutlineGroup Apple.
Bagi pokok, tentukan destinasi dengan tepat: mengalihkan induk antara adik-beradiknya dan mengalihkannya ke dalam induk lain ialah dua operasi berbeza. Penyusunan semula antara adik-beradik mengubah satu tatasusunan induk. Menukar induk memerlukan nod dikeluarkan daripada induk lamanya, disisipkan ke dalam induk baharu dan destinasi dalam keturunan nod itu sendiri ditolak. Ini ialah peraturan model aplikasi yang berpunca daripada struktur pokok.
Jika tugasnya masih mengalihkan keseluruhan kumpulan peringkat teratas, pelaksanaan baris komposit mengelakkan dasar destinasi tambahan itu. Jika anak perlu dipilih atau disusun semula secara berasingan, reka interaksi tersebut dengan jelas sebelum meratakan hierarki menjadi baris yang kelihatan.
AppKit menyokong peralihan bahagian bersama item yang terkandung di dalamnya
Apabila antara muka anda ialah NSCollectionView dengan setiap kumpulan diwakili sebagai satu bahagian, Apple menyediakan moveSection(_:toSection:). Dokumentasinya secara jelas merangkumi penyusunan semula bahagian dan item yang terkandung di dalamnya, serta memerlukan sumber data dikemas kini sebelum kaedah itu dipanggil. API ini tersedia pada macOS 10.11 dan lebih baharu. Dokumentasi peralihan bahagian Apple.
Bagi pendekatan itu, tentukan indeks bahagian sumber dan destinasi akhir, susun semula data kumpulan, kemudian maklumkan paparan koleksi menggunakan API peralihan bahagiannya. Asingkan pengiraan sempadan sisipan SwiftUI daripada indeks bahagian destinasi yang dijangka oleh AppKit. Kedua-dua kaedah mentakrifkan destinasinya dengan cara yang berbeza.
Untuk membenamkan koleksi AppKit dalam SwiftUI, gunakan NSViewRepresentable, pembalut yang didokumenkan oleh Apple untuk mencipta dan mengemas kini paparan AppKit. Salurkan interaksi koleksi kembali kepada model SwiftUI melalui mekanisme penyelarasan yang sesuai, dan biarkan SwiftUI mengawal susun atur luar paparan yang diwakilkan. Dokumentasi NSViewRepresentable Apple.
TextKit mengendalikan susun atur teks dalam item
Apple mentakrifkan NSTextLayoutManager sebagai pengurus bagi susun atur dan persembahan teks tersuai, yang tersedia pada macOS 12 dan lebih baharu. Tanggungjawabnya melibatkan bekas teks, elemen dan fragmen susun atur. Bagi tugas koleksi ini, kesan seni binanya ialah susunan kumpulan perlu kekal dalam model koleksi, walaupun anak mengandungi editor TextKit tersuai. Dokumentasi NSTextLayoutManager Apple.
Bagaimanakah anda mendiagnosis kumpulan yang bergerak dengan tidak betul?
Mulakan dengan contoh terkecil yang kelihatan dan periksa model selepas satu operasi. Semakan di bawah membezakan ralat susunan daripada ketidakpadanan antara baris yang dipaparkan dengan data asas.
- Hanya tajuk yang bergerak: periksa sama ada anak disimpan sebagai entri berasingan dalam tatasusunan rata. Alihkan objek induk yang memilikinya, atau laksanakan peralihan blok secara eksplisit untuk perwakilan rata itu.
- Peralihan ke bawah tidak berkesan: periksa sama ada butang menghantar
index + 1. Bagi satu item sumber, sempadan itu mengekalkan item di tempatnya. - Kumpulan yang salah bergerak selepas penapisan: periksa sama ada ofset yang kelihatan digunakan pada tatasusunan penuh. Petakan ID kumpulan yang kelihatan kepada model kanonik sebelum mutasi, dan tentukan kedudukan destinasi yang ditapis.
- Susunan terus kembali seperti asal: periksa sama ada tatasusunan yang dipaparkan disusun semula pada setiap pemaparan. Susunan manual memerlukan tertib yang benar-benar dikekalkan oleh paparan.
- Anak bergerak secara berasingan: periksa lokasi pengendali peralihan dilampirkan. Dalam contoh ini, pengendali itu hanya dilampirkan pada koleksi kumpulan luar.
Sebelum integrasi, semak peralihan ke atas dan ke bawah, kedua-dua hujung senarai, satu kumpulan yang tinggal dan kumpulan kosong. Sahkan invarian model serta rupanya: ID kumpulan yang sama kekal wujud, setiap kumpulan mengekalkan ID anak yang sama dan susunan anak tidak berubah. Semakan ini menguji secara langsung keperluan supaya induk dan anak-anaknya bergerak bersama.
Bagi antara muka Mac yang ringkas, mulakan dengan satu model induk bagi setiap baris yang boleh dialihkan dan satu fungsi penyusunan semula yang dikongsi. Pengguna kemudian boleh mengalihkan Group A dengan satu tindakan sambil anak-anaknya kekal bersama. Tambahkan pelaksanaan hierarki atau AppKit apabila antara muka memerlukan interaksi anak yang berasingan atau tingkah laku koleksi berasaskan bahagian.
Sumber
- onMove(perform:) — Apple
- ForEach — Apple
- move(fromOffsets:toOffset:) — Apple
- OutlineGroup — Apple
- moveSection(_:toSection:) — Apple
- NSViewRepresentable — Apple
- NSTextLayoutManager — Apple