अपने Mac ऐप में सूची को पुनर्व्यवस्थित करने के लिए, SwiftUI के onMove स्रोत और डेस्टिनेशन को सीधे move(fromOffsets:toOffset:) में पास करें। डेस्टिनेशन मूल कलेक्शन में एक सीमा दर्शाता है, इसलिए नीचे की ओर ले जाते समय उसमें से एक घटाने पर गलत परिणाम मिलता है।

यह वॉकथ्रू सिंथेटिक लेबल का उपयोग करके एक छोटी नेटिव macOS सूची बनाता है, डेस्टिनेशन की गणना समझाता है और गलत जगह पहुँची पंक्तियों की जाँच के लिए परीक्षण मैट्रिक्स देता है। इसका दायरा आपके अपने ऐप की सूची के भीतर आइटम पुनर्व्यवस्थित करना है।

onMove डेस्टिनेशन का क्या अर्थ है?

Apple इन दो onMove(perform:) आर्ग्युमेंट को डायनेमिक व्यू के अंतर्निहित कलेक्शन के सापेक्ष ऑफ़सेट के रूप में परिभाषित करता है। पहला आर्ग्युमेंट स्रोत ऑफ़सेट का IndexSet है; दूसरा डेस्टिनेशन ऑफ़सेट है। ये स्थान उसी ForEach द्वारा उपयोग किए गए कलेक्शन के होते हैं। Apple का onMove(perform:) दस्तावेज़।

move(fromOffsets:toOffset:) के लिए Apple डेस्टिनेशन को उस ऑफ़सेट के रूप में परिभाषित करता है जिसके पहले स्थानांतरित किए गए एलिमेंट डाले जाने चाहिए; count का अर्थ अंत भी हो सकता है। इसे मूल कलेक्शन के एलिमेंटों के बीच की सीमा समझें। Apple का move(fromOffsets:toOffset:) दस्तावेज़।

मूल क्रम A, B, C, D, E पर विचार करें। इसके एलिमेंट ऑफ़सेट 0, 1, 2, 3, 4 हैं, जबकि इंसर्शन सीमाएँ 0 से 5 तक हैं। सीमा 4, E से ठीक पहले है; सीमा 5 अंतिम आइटम के बाद है।

इसलिए B को स्रोत ऑफ़सेट 1 से डेस्टिनेशन 4 पर ले जाने से क्रम A, C, D, B, E बनता है। स्थानांतरित आइटम इंडेक्स 3 पर पहुँचता है, भले ही डेस्टिनेशन आर्ग्युमेंट 4 था। दोनों मान शून्य-आधारित स्थानों का उपयोग करते हैं; वे ऑपरेशन के अलग-अलग चरणों का वर्णन करते हैं।

यही अंतर नीचे की ओर ले जाने में होने वाली आम गलती को समझाता है। कलेक्शन के move मेथड को कॉल करने से पहले डेस्टिनेशन 4 को 3 में बदलने वाला कोड एक अलग सीमा का अनुरोध करता है और B को D से पहले रखता है। Apple के move मेथड का उपयोग करते समय कॉलबैक के मानों को बिना बदले आगे पास करें।

पूरी तरह पुनर्व्यवस्थित की जा सकने वाली macOS सूची बनाएँ

Xcode में नेटिव macOS SwiftUI ऐप प्रोजेक्ट का उपयोग करें। नीचे दिया पूरा उदाहरण macOS 11 या उसके बाद के संस्करण को लक्ष्य करता है, क्योंकि इसमें SwiftUI App लाइफ़साइकिल का उपयोग है; Apple के अनुसार onMove स्वयं macOS 10.15 से उपलब्ध है। जनरेट की गई ऐप और content-view घोषणाओं को इस कोड से बदलें और टार्गेट में ठीक एक @main एंट्री पॉइंट रखें। Apple का App दस्तावेज़, Apple का onMove(perform:) दस्तावेज़।

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 उसी ऐरे को रेंडर करता है जिसे हैंडलर बदलता है। हर आइटम की एक स्थिर पहचान होती है, जो उसकी मौजूदा स्थिति से स्वतंत्र है। Apple के अनुसार ForEach के एलिमेंटों की पहचान संभव होनी चाहिए या स्पष्ट पहचान कुंजी देनी चाहिए; स्थानांतरण के दौरान हर पहचान को बनाए रखने से व्यू उसी आइटम का प्रतिनिधित्व करता रहता है। Apple का ForEach दस्तावेज़।

ऐरे स्थानीय व्यू स्टेट है। इसे बदलने पर SwiftUI निर्भर व्यू अपडेट करता है, जिनमें सूची और दिखाया गया क्रम शामिल हैं। यह उदाहरण क्रम को मेमोरी में रखता है और इसमें स्थायी संग्रहण का कोई कार्यान्वयन नहीं है। Apple का State दस्तावेज़।

ऐप चलाएँ, B को E से ठीक पहले वाली सीमा तक खींचें और क्रम तथा कॉलबैक डिस्प्ले दोनों देखें। कोई दूसरा परिदृश्य आज़माने से पहले Reset करें। स्पष्ट selection binding से कई पंक्तियाँ चुनना भी संभव है; Apple, Mac पर selection sets और edit mode में गए बिना चयन करने का दस्तावेज़ देता है। Apple का List दस्तावेज़।

इस नेटिव macOS उदाहरण में EditButton() न जोड़ें। Apple की मौजूदा उपलब्धता जानकारी में iOS और Mac Catalyst शामिल हैं, लेकिन नेटिव macOS शामिल नहीं है। मोबाइल के संपादन नियंत्रणों को उपलब्धता जाँचे बिना Mac टार्गेट में कॉपी नहीं करना चाहिए। Apple का EditButton दस्तावेज़।

अंतिम इंसर्शन इंडेक्स की गणना कैसे करें?

यह गणना तभी चाहिए जब आप स्वयं हटाने और डालने का कार्यान्वयन कर रहे हों, या मूव को किसी दूसरे मॉडल ऑपरेशन में बदल रहे हों। यह Apple द्वारा दर्ज इंसर्शन अर्थ से निकला व्याख्यात्मक मॉडल है; बिल्ट-इन move मेथड को कॉल करने से पहले लागू किया जाने वाला कोई अतिरिक्त समायोजन नहीं।

गणना यह है:

हटाने के बाद इंसर्शन इंडेक्स = मूल डेस्टिनेशन
                           - डेस्टिनेशन से नीचे के स्रोत ऑफ़सेट की संख्या

स्रोत {1} और डेस्टिनेशन 4 के लिए, हटाया गया एक एलिमेंट मूल रूप से सीमा से पहले आता है। घटा हुआ ऐरे A, C, D, E है और इंसर्शन इंडेक्स 4 - 1 = 3 है। वहाँ B डालने पर A, C, D, B, E बनता है।

कई स्रोतों के लिए केवल एक बार घटाना पर्याप्त नहीं है। स्रोत {1, 3} और डेस्टिनेशन 5 होने पर, स्थानांतरित आइटम B, D हैं; बचा हुआ ऐरे A, C, E है। दो स्रोत ऑफ़सेट डेस्टिनेशन 5 से पहले आते हैं, इसलिए इंसर्शन इंडेक्स 3 है। परिणाम A, C, E, B, D है।

कस्टम कार्यान्वयन को पहले स्थानांतरित किए जाने वाले एलिमेंटों को उनके मूल क्रम में सहेजना चाहिए, फिर मूल एलिमेंट इस तरह हटाने चाहिए कि पहले की गई हटाने की कार्रवाइयों से बाद के स्रोत स्थान अमान्य न हों। इसके बाद यह समायोजित इंसर्शन स्थिति की गणना करके सहेजे गए ब्लॉक को एक बार डाल सकता है। इस ऐरे-आधारित सूची के लिए, Apple का मौजूदा मेथड आपको ये चरण खुद लागू करने से बचाता है।

किन किनारी मामलों का परीक्षण करना चाहिए?

निम्नलिखित मामले मूल क्रम A, B, C, D, E के लिए उदाहरणात्मक गणनाएँ हैं। हर मामले से पहले उस क्रम को रीसेट करें। आखिरी कॉलम अपेक्षित मॉडल परिणाम बताता है, जो ड्रॉप इंडिकेटर के दिखने के तरीके से स्वतंत्र है।

स्रोत ऑफ़सेट मूल डेस्टिनेशन हटाने के बाद इंसर्शन इंडेक्स अपेक्षित क्रम
{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

एक आइटम वाले दोनों नो-ऑप मामलों पर ध्यान देना ज़रूरी है: किसी आइटम को उसकी अपनी सीमा या उसके बाद वाली सीमा पर डालने से उसका क्रम नहीं बदलता। Apple इस व्यवहार को स्पष्ट रूप से दर्ज करता है। अंतिम सीमा भी मान्य है, भले ही उसे किसी एलिमेंट के सबस्क्रिप्ट के रूप में इस्तेमाल न किया जा सके। Apple का move(fromOffsets:toOffset:) दस्तावेज़।

इन सात मामलों के पीछे के गणना मॉडल को एक छोटी, नियतात्मक Python गणना से जाँचा गया। SwiftUI प्रोग्राम का निरीक्षण किया गया, लेकिन इसे यहाँ कंपाइल या चलाया नहीं गया, क्योंकि Swift टूलचेन और macOS रनटाइम उपलब्ध नहीं थे। अपने लक्षित macOS संस्करण पर वास्तविक ड्रैगिंग इंटरैक्शन की जाँच करें।

पंक्तियाँ अब भी गलत ढंग से क्यों खिसक सकती हैं?

कलेक्शन सीमा से शुरुआत करें, फिर पहचान और बदलाव की जाँच करें। इस क्रम से डेस्टिनेशन गणना की समस्याओं और दिख रही पंक्तियों तथा संग्रहीत डेटा के बीच बेमेल को अलग किया जा सकता है।

  1. ForEach को दिए गए कलेक्शन की जाँच करें। यदि यह फ़िल्टर किया गया ऐरे दिखाता है, तो इसके ऑफ़सेट उसी फ़िल्टर किए गए ऐरे के होंगे। उदाहरण के लिए, दिख रही पंक्तियों B, D के ऑफ़सेट 0, 1 हैं; पूरे ऐरे पर स्रोत 0 लागू करने से A स्थानांतरित होगा। यह परिणाम Apple के दस्तावेज़ित कलेक्शन-सापेक्ष कॉलबैक अनुबंध से निकलता है। Apple का onMove(perform:) दस्तावेज़।
  2. जाँचें कि रेंडरिंग डेटा को फिर से सॉर्ट तो नहीं करती। यदि हर रीड्रॉ लेबल के आधार पर सॉर्ट करता है, तो बैकिंग ऐरे का क्रम बदलने से स्थायी मैन्युअल डिस्प्ले क्रम तय नहीं होगा। उस व्यू में पुनर्व्यवस्था चालू करने से पहले मैन्युअल क्रम की नीति तय करें।
  3. पहचानों की जाँच करें। हर आइटम को स्थानांतरित करते समय उसकी पहचान बनाए रखें। रेंडरिंग के दौरान पहचानें फिर से न बनाएँ और स्थान बदलने वाले आइटमों की पहचान के रूप में बदलती स्थितियों का उपयोग न करें। Apple का ForEach दस्तावेज़।
  4. एक ही काम दो बार होने की जाँच करें। डेस्टिनेशन में से मैन्युअल घटाव हटाएँ, फिर पुष्टि करें कि हैंडलर प्रामाणिक कलेक्शन पर मूव केवल एक बार लागू करता है। दूसरा मूव पहले से बदले हुए क्रम पर काम करता है।

फ़िल्टर किए गए या सेक्शन वाली इंटरफ़ेस के लिए, इंडेक्स बदलने से पहले इच्छित व्यवहार तय करें। तय करें कि मूव केवल दिख रहे आइटमों को पुनर्व्यवस्थित करता है, पूरे कलेक्शन को बदलता है, या किसी आइटम को एक सेक्शन से दूसरे में ले जाता है। दिख रहे आइटमों को मॉडल पहचानों से मैप करें और उसी नीति के अनुसार डेस्टिनेशन निर्धारित करें। केवल एक संख्यात्मक ऑफ़सेट इन सभी विकल्पों को व्यक्त नहीं कर सकता।

कॉलबैक ऑफ़सेट को उसी कलेक्शन स्नैपशॉट से जुड़ा रखें जिसका वे वर्णन करते हैं। यदि ऐप ऑपरेशन को टालता है और इस बीच अन्य बदलाव ऐरे बदल देते हैं, तो पुराने स्थानों को फिर से लागू करने के बजाय इच्छित आइटम और डेस्टिनेशन का फिर से पता लगाएँ। नमूना स्थानीय ऐरे को तुरंत बदलकर इस जटिलता से बचता है।

क्या AppKit या TextKit डेस्टिनेशन के नियम बदलते हैं?

यदि ऐप पहले से NSTableView का उपयोग करता है, तो AppKit का अपना ड्रॉप अनुबंध है। NSTableView.DropOperation.above का अर्थ पंक्ति के ऊपर इंसर्शन है, जबकि .on का अर्थ उस पंक्ति पर ड्रॉप करना है। Apple, अंतिम पंक्ति के नीचे ड्रॉप को पंक्ति n और ऑपरेशन .above के रूप में दर्ज करता है, जहाँ n पंक्तियों की संख्या है। AppKit ड्रॉप को मॉडल मूव में बदलने से पहले पंक्ति और ऑपरेशन दोनों का अर्थ समझें। Apple का NSTableView.DropOperation दस्तावेज़।

TextKit, NSTextView और टेक्स्ट लेआउट मैनेजर जैसे घटकों के माध्यम से टेक्स्ट स्टोरेज, लेआउट और संपादन का काम करता है। यह इस उदाहरण में उपयोग किए गए ऐरे-पुनर्व्यवस्था अनुबंध की व्यवस्था नहीं करता। यदि किसी पंक्ति में एडिटर भी है, तो उसके टेक्स्ट-संपादन कार्यान्वयन को पंक्ति क्रम बदलने वाले कलेक्शन ऑपरेशन से अलग रखें। Apple का TextKit दस्तावेज़।

सीधी-सादी Mac सूची के लिए, मैन्युअल रूप से क्रमित एक कलेक्शन रखें, आइटम पहचानों को बनाए रखें और onMove आर्ग्युमेंट को सीधे कलेक्शन के move मेथड में पास करें। परिणाम की जाँच या ऑपरेशन को किसी दूसरे मॉडल में बदलने के लिए सीमा गणना और परीक्षण मैट्रिक्स का उपयोग करें।

स्रोत