Untuk menyusun semula senarai dalam app Mac anda, hantarkan sumber dan destinasi onMove SwiftUI terus kepada move(fromOffsets:toOffset:). Destinasi menandakan sempadan dalam koleksi asal, jadi menolak satu secara manual untuk pergerakan ke bawah menghasilkan keputusan yang salah.

Panduan ini membina senarai macOS natif yang ringkas menggunakan label sintetik, menerangkan aritmetik destinasi dan menyediakan matriks ujian untuk mendiagnosis baris yang tersalah letak. Skopnya ialah menyusun semula item dalam senarai app anda sendiri.

Apakah maksud destinasi onMove?

Apple mentakrifkan dua argumen onMove(perform:) sebagai ofset relatif kepada koleksi asas paparan dinamik. Argumen pertama ialah IndexSet ofset sumber; argumen kedua ialah ofset destinasi. Kedudukan ini merujuk kepada koleksi yang digunakan oleh ForEach tersebut. Dokumentasi onMove(perform:) Apple.

Bagi move(fromOffsets:toOffset:), Apple mentakrifkan destinasi sebagai ofset sebelum elemen yang dipindahkan patut disisipkan, dan count juga boleh bermaksud hujung. Anggaplah ia sebagai sempadan antara elemen dalam koleksi asal. Dokumentasi move(fromOffsets:toOffset:) Apple.

Pertimbangkan susunan asal A, B, C, D, E. Ofset elemennya ialah 0, 1, 2, 3, 4, manakala sempadan penyisipannya bermula dari 0 hingga 5. Sempadan 4 terletak tepat sebelum E; sempadan 5 terletak selepas item terakhir.

Oleh itu, memindahkan B dari ofset sumber 1 ke destinasi 4 menghasilkan A, C, D, B, E. Item yang dipindahkan berakhir pada indeks 3, walaupun argumen destinasi ialah 4. Kedua-dua nilai menggunakan kedudukan berasaskan sifar; nilainya menerangkan peringkat operasi yang berbeza.

Perbezaan itu menjelaskan kesilapan lazim semasa pergerakan ke bawah. Kod yang menukar destinasi 4 kepada 3 sebelum memanggil kaedah move koleksi meminta sempadan yang berbeza lalu meletakkan B sebelum D. Apabila menggunakan kaedah move Apple, hantarkan nilai panggilan balik tanpa perubahan.

Bina senarai macOS lengkap yang boleh disusun semula

Gunakan projek app SwiftUI macOS natif dalam Xcode. Contoh lengkap di bawah menyasarkan macOS 11 atau lebih baharu kerana ia menggunakan kitar hayat App SwiftUI; Apple menyatakan onMove itu sendiri tersedia mulai macOS 10.15. Gantikan pengisytiharan app dan paparan kandungan yang dijana dengan kod ini, dan pastikan hanya satu titik masuk @main dalam sasaran. Dokumentasi App Apple, dokumentasi onMove(perform:) Apple.

import SwiftUI
import Foundation

struct DemoItem: Identifiable {
    let id: Int
    let label: String

    static let samples: [DemoItem] = [
        DemoItem(id: 0, label: "A"),
        DemoItem(id: 1, label: "B"),
        DemoItem(id: 2, label: "C"),
        DemoItem(id: 3, label: "D"),
        DemoItem(id: 4, label: "E")
    ]
}

@main
struct ReorderDemoApp: App {
    var body: some Scene {
        WindowGroup {
            ReorderView()
        }
    }
}

struct ReorderView: View {
    @State private var items = DemoItem.samples
    @State private var selection = Set<Int>()
    @State private var lastMove = "No move yet"

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Drag rows to change their order")

            List(selection: $selection) {
                ForEach(items) { item in
                    Text(item.label)
                        .tag(item.id)
                }
                .onMove(perform: moveItems)
            }

            Text("Order: " + items.map(\.label).joined(separator: ", "))
            Text(lastMove)

            Button("Reset") {
                items = DemoItem.samples
                selection.removeAll()
                lastMove = "No move yet"
            }
        }
        .padding()
        .frame(minWidth: 420, minHeight: 300)
    }

    private func moveItems(from source: IndexSet, to destination: Int) {
        lastMove = "Source: \(Array(source)); destination: \(destination)"
        items.move(fromOffsets: source, toOffset: destination)
    }
}

ForEach memaparkan tatasusunan yang sama seperti yang diubah oleh pengendali. Setiap item mempunyai pengecam tetap yang tidak bergantung pada kedudukannya semasa. Apple memerlukan elemen yang boleh dikenal pasti atau kunci identiti yang dinyatakan secara jelas untuk ForEach; mengekalkan setiap pengecam semasa pemindahan membolehkan paparan terus mewakili item yang sama. Dokumentasi ForEach Apple.

Tatasusunan itu ialah keadaan paparan setempat. Perubahannya menyebabkan SwiftUI mengemas kini paparan yang bergantung padanya, termasuk senarai dan susunan yang dipaparkan. Contoh ini menyimpan susunan dalam memori dan tidak mengandungi pelaksanaan ketekalan data. Dokumentasi State Apple.

Jalankan app, seret B ke sempadan tepat sebelum E, kemudian periksa susunan dan paparan panggilan balik. Gunakan Reset sebelum mencuba senario lain. Pengikatan pilihan yang dinyatakan secara jelas turut menyokong pemilihan berbilang baris; Apple mendokumenkan set pilihan dan pemilihan Mac tanpa memasuki mod suntingan. Dokumentasi List Apple.

Jangan tambah EditButton() pada contoh macOS natif ini. Maklumat ketersediaan Apple semasa merangkumi iOS dan Mac Catalyst, tetapi tidak merangkumi macOS natif. Jangan salin kawalan suntingan tutorial mudah alih ke sasaran Mac tanpa menyemak ketersediaannya. Dokumentasi EditButton Apple.

Bagaimanakah anda boleh mengira indeks penyisipan akhir?

Anda hanya memerlukan pengiraan ini apabila melaksanakan sendiri pembuangan dan penyisipan, atau apabila menterjemahkan pemindahan itu kepada operasi model lain. Ini ialah model penerangan yang diperoleh daripada semantik penyisipan yang didokumenkan oleh Apple, bukannya pelarasan tambahan yang perlu dibuat sebelum memanggil kaedah move terbina dalam.

Pengiraannya ialah:

indeks penyisipan selepas pembuangan = destinasi asal
                                    - bilangan ofset sumber di bawah destinasi

Bagi sumber {1} dan destinasi 4, satu elemen yang dibuang asalnya berada sebelum sempadan. Tatasusunan yang telah dikurangkan ialah A, C, D, E, dan indeks penyisipannya ialah 4 - 1 = 3. Menyisipkan B di situ menghasilkan A, C, D, B, E.

Beberapa sumber menjadikan pembetulan ke bawah tunggal tidak memadai. Dengan sumber {1, 3} dan destinasi 5, item yang dipindahkan ialah B, D; tatasusunan yang tinggal ialah A, C, E. Dua ofset sumber terletak sebelum destinasi 5, jadi indeks penyisipannya ialah 3. Hasilnya ialah A, C, E, B, D.

Pelaksanaan tersuai mesti terlebih dahulu menangkap elemen yang dipindahkan mengikut susunan asalnya, kemudian membuang elemen asal tanpa membiarkan pembuangan terdahulu membatalkan kedudukan sumber yang menyusul. Selepas itu, ia boleh mengira kedudukan penyisipan terlaras dan menyisipkan blok yang ditangkap sekali sahaja. Bagi senarai berasaskan tatasusunan ini, kaedah sedia ada Apple mengelakkan keperluan melaksanakan langkah-langkah itu sendiri.

Kes pinggir manakah yang patut anda uji?

Kes berikut ialah pengiraan ilustrasi untuk susunan asal A, B, C, D, E. Tetapkan semula susunan itu sebelum setiap kes. Lajur terakhir menunjukkan hasil model yang dijangka, tanpa mengira rupa penunjuk lepas.

Ofset sumber Destinasi asal Indeks penyisipan selepas pembuangan Susunan dijangka
{1} 4 3 A, C, D, B, E
{3} 1 1 A, D, B, C, E
{1} 5 4 A, C, D, E, B
{1} 1 1 A, B, C, D, E
{1} 2 1 A, B, C, D, E
{1, 3} 5 3 A, C, E, B, D
{1, 3} 3 2 A, C, B, D, E

Dua kes tanpa operasi untuk satu item itu penting: menyisipkan item pada sempadannya sendiri atau sempadan selepasnya tidak mengubah susunannya. Apple mendokumenkan tingkah laku ini secara jelas. Sempadan hujung juga sah walaupun ia tidak boleh digunakan untuk mengakses elemen berdasarkan subskrip. Dokumentasi move(fromOffsets:toOffset:) Apple.

Model aritmetik di sebalik tujuh kes ini disemak dengan pengiraan Python deterministik yang ringkas. Program SwiftUI diperiksa tetapi tidak dikompil atau dijalankan di sini kerana toolchain Swift dan masa jalan macOS tidak tersedia. Sahkan sendiri interaksi seret pada versi macOS sasaran anda.

Mengapakah baris masih boleh tersalah gerak?

Mulakan dengan sempadan koleksi, kemudian semak identiti dan perubahan. Urutan ini membezakan masalah aritmetik destinasi daripada ketidakpadanan antara baris yang dipaparkan dengan data yang disimpan.

  1. Semak koleksi yang dihantar kepada ForEach. Jika ia memaparkan tatasusunan yang ditapis, ofsetnya merujuk kepada tatasusunan yang ditapis itu. Contohnya, baris yang kelihatan B, D mempunyai ofset 0, 1; menggunakan sumber 0 pada tatasusunan penuh akan memindahkan A. Kesannya berpunca daripada kontrak panggilan balik relatif kepada koleksi yang didokumenkan Apple. Dokumentasi onMove(perform:) Apple.
  2. Semak sama ada pemaparan menyusun data semula. Jika setiap lukisan semula menyusun mengikut label, perubahan pada susunan tatasusunan asas tidak dapat mengekalkan susunan paparan manual. Tentukan dasar susunan manual sebelum mendayakan penyusunan semula dalam paparan itu.
  3. Semak pengecam. Kekalkan pengecam setiap item semasa memindahkannya. Jangan jana semula pengecam semasa pemaparan atau gunakan kedudukan yang berubah sebagai identiti item yang boleh dipindahkan. Dokumentasi ForEach Apple.
  4. Semak kerja berganda. Buang penolakan destinasi secara manual, kemudian pastikan pengendali menggunakan pemindahan itu sekali sahaja pada koleksi berautoriti. Pemindahan kedua akan beroperasi pada susunan yang sudah berubah.

Bagi antara muka bertapis atau berbahagian, tentukan tingkah laku yang dimaksudkan sebelum menterjemahkan indeks. Putuskan sama ada pemindahan menyusun semula item yang kelihatan sahaja, mengubah seluruh koleksi atau memindahkan item antara bahagian. Petakan item yang kelihatan kepada identiti model dan tentukan destinasi mengikut dasar tersebut. Ofset angka sahaja tidak dapat menyatakan semua pilihan itu.

Pastikan ofset panggilan balik terikat pada petikan koleksi yang diterangkannya. Jika app menangguhkan operasi sementara suntingan lain mengubah tatasusunan, tentukan semula item dan destinasi yang dimaksudkan dan bukannya memainkan semula kedudukan lapuk. Contoh ini mengelakkan kerumitan itu dengan mengubah tatasusunan setempatnya serta-merta.

Adakah AppKit atau TextKit mengubah peraturan destinasi?

Bagi app yang sudah menggunakan NSTableView, AppKit mempunyai kontrak lepasnya sendiri. NSTableView.DropOperation.above bermaksud sisipan di atas baris, manakala .on bermaksud lepas pada baris itu. Apple mendokumenkan lepas di bawah baris terakhir sebagai baris n dengan operasi .above, dengan n ialah bilangan baris. Tafsirkan kedua-dua baris dan operasi sebelum menterjemahkan lepas AppKit kepada pemindahan model. Dokumentasi NSTableView.DropOperation Apple.

TextKit mengendalikan storan, susun atur dan penyuntingan teks melalui komponen seperti NSTextView dan pengurus susun atur teks. Ia tidak menyediakan kontrak penyusunan semula tatasusunan yang digunakan dalam contoh ini. Jika baris turut mengandungi editor, asingkan pelaksanaan penyuntingan teks daripada operasi koleksi yang mengubah susunan baris. Dokumentasi TextKit Apple.

Bagi senarai Mac yang ringkas, kekalkan satu koleksi yang disusun secara manual, pelihara pengecam item dan hantarkan argumen onMove terus kepada kaedah move koleksi. Gunakan pengiraan sempadan dan matriks ujian apabila anda perlu mendiagnosis hasilnya atau menterjemahkan operasi itu kepada model lain.

Sumber