ड्रैग स्रोत के सत्र-समाप्ति कॉलबैक से धुंधले आकार, आउटलाइन और ड्रैग लेबल बहाल करें। किसी डेस्टिनेशन का एग्ज़िट कॉलबैक सिर्फ़ यह बताता है कि पॉइंटर उस लक्ष्य से बाहर चला गया। Mac ऐप के लिए, उपलब्ध होने पर SwiftUI का स्रोत-सत्र API इस्तेमाल करें या AppKit स्रोत को ड्रैग का स्वामित्व दें।

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

ड्रॉप लक्ष्यों के बाहर ड्रैग फ़ीडबैक सक्रिय क्यों रहता है?

एक सामान्य कार्यान्वयन ड्रैग शुरू होने पर isDragging फ़्लैग सेट करता है और उसे performDrop में साफ़ करता है। इससे सफ़ाई ऐसे इवेंट से जुड़ जाती है जिसे केवल कोई स्वीकार करने वाला डेस्टिनेशन संभाल सकता है। खाली विंडो क्षेत्र पर छोड़ने से स्रोत को रीसेट करने की ज़िम्मेदारी किसी डेस्टिनेशन पर नहीं रहती।

Apple DropDelegate को ऐसे व्यू के इंटरफ़ेस के रूप में परिभाषित करता है जिसे ड्रॉप स्वीकार करने के लिए संशोधित किया गया हो। इसका dropExited(info:) मेथड बताता है कि मान्य ड्रैग उस व्यू से बाहर निकल गया। ड्रैग किसी दूसरे लक्ष्य में जा सकता है, पहले लक्ष्य पर लौट सकता है या कहीं और समाप्त हो सकता है। इसलिए लक्ष्य से बाहर निकलना यह साबित नहीं कर सकता कि स्रोत का सत्र समाप्त हो गया। DropDelegate

यही फ़र्क dropDestination(for:action:isTargeted:) पर भी लागू होता है: isTargeted बताता है कि पॉइंटर लक्ष्य क्षेत्र के अंदर है या नहीं। इसे लक्ष्य की बॉर्डर या बैकग्राउंड के लिए इस्तेमाल करें। स्रोत की धुंधली दिखावट या ड्रैग लेबल को स्रोत-सत्र कॉलबैक से नियंत्रित करें। dropDestination(for:action:isTargeted:)

इन प्रलेखित सीमाओं से एक उपयोगी डिज़ाइन नियम निकलता है: हर दृश्य स्थिति की ज़िम्मेदारी उस इवेंट-स्वामी को दें जो उसे भरोसेमंद ढंग से समाप्त कर सकता है।

फ़ीडबैक या परिणाम ज़िम्मेदार रीसेट या पुष्टि का बिंदु
धुंधला स्रोत आकार ड्रैग स्रोत स्रोत सत्र समाप्त होने पर
हाइलाइट किया गया डेस्टिनेशन ड्रॉप डेस्टिनेशन पॉइंटर के बाहर निकलने पर; स्थानीय ड्रैग के लिए स्रोत पूरा होने पर अतिरिक्त सफ़ाई के साथ
स्वीकार किया गया सिंथेटिक आइटम डेस्टिनेशन/मॉडल ड्रॉप की पुष्टि और सफलता पर
अस्थायी निर्यात संसाधन ट्रांसफ़र लाइफ़साइकल लागू होने पर ट्रांसफ़र पूरा होने पर

Mac ऐप को कौन-सा समर्थित तरीका इस्तेमाल करना चाहिए?

Apple का मौजूदा संदर्भ onDragSessionUpdated(_:) को macOS 26 और उसके बाद के संस्करणों के लिए उपलब्ध बताता है। यह चाइल्ड व्यू पदानुक्रम में सबसे नज़दीकी ड्रैग सत्र के अपडेट देखता है। इसका दायरा स्पष्ट रखने के लिए इसे स्रोत के ड्रैग मॉडिफ़ायर के ठीक बाहर लगाएँ। ऐसा SDK इस्तेमाल करें जिसमें यह मेथड घोषित हो; रनटाइम उपलब्धता से SDK में अनुपस्थित घोषणा अपने आप कंपाइल नहीं हो जाती। onDragSessionUpdated(_:)

दस्तावेज़ में एक विसंगति है, जिसे सीमित रूप से संभालना बेहतर है: मॉडिफ़ायर के उदाहरण में लोकेशन और ऑपरेशन आर्ग्युमेंट वाला ended चरण दिखाया गया है, जबकि समर्पित चरण संदर्भ ended(DropOperation) घोषित करता है। नीचे दिया उदाहरण associated values निकाले बिना .ended से मिलान करता है, क्योंकि दृश्य स्थिति बहाल करने के लिए उनकी ज़रूरत नहीं है। ऑपरेशन-विशिष्ट व्यवहार जोड़ने से पहले अपने इंस्टॉल किए गए SDK की घोषणा देखें। DragSession.Phase.ended(_:)

अगर आपका deployment target या SDK SwiftUI कॉलबैक इस्तेमाल नहीं कर सकता, तो AppKit ड्रैग स्रोत पूरा होने का प्रलेखित बिंदु देता है। Apple स्पष्ट रूप से बताता है कि ड्रैग समाप्त या रद्द होने पर स्रोत को draggingSession(_:endedAt:operation:) मिलता है। कस्टम स्रोत को वह सत्र स्वयं शुरू करना होगा। beginDraggingSession(with:event:source:)

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

SwiftUI में स्रोत-स्वामित्व वाली सफ़ाई लागू करें

ऐसा SDK इस्तेमाल करके macOS SwiftUI ऐप बनाएँ जो onDragSessionUpdated(_:) उपलब्ध कराता हो, deployment target को macOS 26 या उसके बाद पर सेट करें, और जनरेट किए गए ऐप तथा content-view घोषणाओं की जगह यह पूरा प्रोग्राम रखें। इसमें एक सिंथेटिक स्ट्रिंग shape.circle और एक प्राप्तकर्ता क्षेत्र है।

स्रोत ट्रांसफ़रेबल सामग्री देने के लिए draggable(_:) इस्तेमाल करता है। डेस्टिनेशन ठीक एक मेल खाती स्ट्रिंग स्वीकार करता है और अन्य इनपुट के लिए false लौटाता है। Apple बताता है कि डेस्टिनेशन का Boolean परिणाम दर्शाता है कि ड्रॉप सफल हुआ या नहीं। draggable(_:), dropDestination(for:action:isTargeted:)

import SwiftUI

@main
struct DragFeedbackDemoApp: App {
    var body: some Scene {
        WindowGroup {
            DragFeedbackDemo()
        }
    }
}

struct DragFeedbackDemo: View {
    private let payload = "shape.circle"

    @State private var isDragging = false
    @State private var isTargeted = false
    @State private var acceptedCopies = 0

    var body: some View {
        VStack(spacing: 24) {
            Text("Drag the circle into the receiving area")

            Circle()
                .fill(Color.blue)
                .frame(width: 72, height: 72)
                .opacity(isDragging ? 0.4 : 1)
                .draggable(payload)
                .onDragSessionUpdated { session in
                    switch session.phase {
                    case .initial, .active:
                        isDragging = true
                    case .ended, .dataTransferCompleted:
                        restoreFeedback()
                    default:
                        break
                    }
                }

            Text(isDragging ? "Dragging" : "Ready")

            RoundedRectangle(cornerRadius: 12)
                .fill(isTargeted
                      ? Color.blue.opacity(0.2)
                      : Color.gray.opacity(0.12))
                .overlay {
                    Text("Receiving area")
                }
                .frame(width: 280, height: 120)
                .dropDestination(for: String.self) { items, _ in
                    guard items.count == 1,
                          items.first == payload else {
                        return false
                    }
                    acceptedCopies += 1
                    return true
                } isTargeted: { targeted in
                    isTargeted = targeted
                }

            Text("Accepted copies: \(acceptedCopies)")
        }
        .padding(32)
        .frame(minWidth: 380, minHeight: 400)
    }

    private func restoreFeedback() {
        isDragging = false
        isTargeted = false
    }
}

इन दोनों फ़्लैग की भूमिकाएँ अलग हैं। isDragging स्रोत की अपारदर्शिता बदलता है। isTargeted प्राप्तकर्ता क्षेत्र का फ़िल बदलता है। इसलिए प्राप्तकर्ता क्षेत्र में प्रवेश और उससे बाहर निकलना ड्रैग जारी रहते हुए स्रोत को गलती से सामान्य रूप में बहाल नहीं कर सकता।

चरण संदर्भ initial को शुरू होने वाले सत्र, active को उसकी गति और ended को उसके पूरा होने के रूप में बताता है। उदाहरण ended पर फ़ीडबैक बहाल करता है; साथ ही dataTransferCompleted पर वही रीसेट दोबारा होना भी सुरक्षित रखता है। असफल ड्रैग को बहाल करने के लिए यह कभी केवल ट्रांसफ़र पूरा होने की प्रतीक्षा नहीं करता। DragSession.Phase

Apple dataTransferCompleted को वह बिंदु बताता है जहाँ ड्रैग किए गए आइटम ट्रांसफ़र हो चुके होते हैं और अस्थायी आइटम साफ़ किए जा सकते हैं। इस उदाहरण में कोई अस्थायी निर्यात संसाधन नहीं है। बड़े कार्यान्वयन में दृश्य सफ़ाई को संसाधन सफ़ाई से अलग रखें, ताकि अपारदर्शिता बहाल करने से वह चीज़ न हटे जिसकी ट्रांसफ़र को अभी ज़रूरत है। DragSession.Phase.dataTransferCompleted

पूरे सत्र के दौरान सर्कल व्यू पदानुक्रम में बना रहता है। स्वीकार किया गया ड्रॉप डेस्टिनेशन की गिनती बढ़ाता है; अस्वीकार किया गया ड्रॉप सामग्री नहीं बदलता। इससे यह उदाहरण पुनर्व्यवस्था या स्थानांतरण के व्यवहार से स्वतंत्र रूप से फ़ीडबैक की समस्या पहचानने में उपयोगी होता है। कोड को प्राप्त की गई घोषणाओं से मिलाकर देखा गया; इसे कंपाइल या चलाया नहीं गया, क्योंकि उपलब्ध वातावरण Linux है और उसमें SwiftUI नहीं है।

AppKit फ़ॉलबैक में इवेंट का स्वामित्व कैसे बनाए रखें?

macOS के किसी पुराने समर्थित संस्करण के लिए, ड्रैग-स्रोत कार्यान्वयन को NSViewRepresentable के माध्यम से होस्ट किए गए NSView से बदलें। representable नेटिव व्यू बनाता और अपडेट करता है तथा उसके कॉलबैक को SwiftUI स्थिति से जोड़ता है। SwiftUI प्रतिनिधित्व किए गए व्यू का लेआउट नियंत्रित करता है, इसलिए स्वयं उसका फ़्रेम असाइन करने के बजाय SwiftUI साइजिंग मॉडिफ़ायर इस्तेमाल करें। NSViewRepresentable

फ़ॉलबैक को स्रोत-स्वामित्व वाले एक क्रम के रूप में लागू करें:

  1. नेटिव स्रोत व्यू के mouseDown(with:) में, उस व्यू को मिला वास्तविक mouse-down इवेंट रखें।
  2. mouseDragged(with:) में, चुनी हुई ड्रैग सीमा लागू करें और दूसरा सत्र शुरू होने से रोकें।
  3. सिंथेटिक आइटम पहचानकर्ता रखने वाले इन-मेमरी पेस्टबोर्ड राइटर से NSDraggingItem बनाएँ।
  4. आकार की जनरेट की गई छवि दें और उसका ड्रैगिंग फ़्रेम स्रोत व्यू के कोऑर्डिनेट सिस्टम में सेट करें।
  5. रखे गए mouse-down इवेंट और NSDraggingSource लागू करने वाले ऑब्जेक्ट का इस्तेमाल करके beginDraggingSession(with:event:source:) कॉल करें।
  6. draggingSession(_:willBeginAt:) से स्रोत फ़ीडबैक दिखाएँ और draggingSession(_:endedAt:operation:) से उसे बिना शर्त बहाल करें।
  7. ट्रैकिंग पूरी होने पर रखा गया इनपुट इवेंट और सत्र का हिसाब साफ़ करें। सत्र पूरा होने तक स्रोत और उसका कॉलबैक स्वामी सक्रिय रखें।

ये विवरण AppKit के स्पष्ट अनुबंध का पालन करते हैं: सत्र शुरू करने वाला मेथड आरंभिक mouse-down इवेंट लेता है, ड्रैगिंग आइटम के फ़्रेम व्यू के कोऑर्डिनेट सिस्टम का इस्तेमाल करते हैं, और स्रोत को लाइफ़साइकल संदेश मिलते हैं। NSDraggingItem ऐसा ऑब्जेक्ट स्वीकार करता है जो NSPasteboardWriting के अनुरूप हो; इसका एकल-छवि सुविधा मेथड ड्रैगिंग फ़्रेम और सामग्री सेट करता है। beginDraggingSession(with:event:source:), init(pasteboardWriter:), setDraggingFrame(_:contents:)

अपने ऐप के भीतर इस इंटरैक्शन के लिए, NSDraggingContext.withinApplication पर .copy लौटाएँ और अन्य संदर्भों के लिए ऑपरेशनों का खाली सेट दें। यह केवल स्थानीय कॉपी उपलब्ध कराने का स्पष्ट विकल्प है। AppKit स्रोत से पूछता है कि वह draggingSession(_:sourceOperationMaskFor:) के ज़रिए किन ऑपरेशनों की अनुमति देता है। draggingSession(_:sourceOperationMaskFor:)

हर पूर्णता परिणाम पर दिखावट बहाल करें, केवल सफल कॉपी पर नहीं। मॉडल में बदलाव केवल उस ऑपरेशन के अनुसार करें जिसे आपके ऐप ने वास्तव में लागू किया है। पूर्णता कॉलबैक का बिंदु स्क्रीन कोऑर्डिनेट में होता है; उसकी तुलना सीधे SwiftUI लक्ष्य की स्थानीय सीमाओं से न करें। draggingSession(_:endedAt:operation:)

मौजूदा SwiftUI .draggable के पीछे रखा पारदर्शी नेटिव व्यू, केवल NSDraggingSource के अनुरूप होने से उस सत्र का स्रोत नहीं बन जाता। स्रोत वही ऑब्जेक्ट है जिसे सत्र शुरू करते समय पास किया जाता है। शुरुआत और पूर्णता के लिए एक ही स्वामी चुनें और नेटिव ड्रैग सतह को आकार के इच्छित इंटरैक्शन क्षेत्र तक सीमित रखें।

माउस-अप मॉनिटरिंग या प्रोवाइडर का डिइनिशियलाइज़ेशन क्यों नहीं?

ऐप-व्यापी mouse-up मॉनिटर इनपुट डिस्पैच को देखता है, जो ड्रैग पूरा होने की सीमा से अलग है। Apple बताता है कि स्थानीय इवेंट मॉनिटर को नेस्टेड ट्रैकिंग लूप द्वारा उपयोग किए गए इवेंट नहीं मिलते। इस सीमा के कारण मॉनिटर स्रोत के प्रलेखित पूर्णता कॉलबैक का उपयुक्त विकल्प नहीं है। addLocalMonitorForEvents(matching:handler:)

इसी तरह, आइटम प्रोवाइडर का deinit ऑब्जेक्ट का जीवनकाल बताता है। Swift में किसी इंस्टेंस के डीलोकेट होने से ठीक पहले deinitializer चलता है; यह अनुबंध यह तय नहीं करता कि ड्रैग कब समाप्त हुआ। प्रोवाइडर के नष्ट होने को पूर्णता का संकेत मानने से UI स्थिति ड्रैग लाइफ़साइकल के बजाय रेफ़रेंस के स्वामित्व पर निर्भर हो जाती है। Deinitialization

सिस्टम ड्रैग के साथ दूसरा DragGesture जोड़ने में भी यही वैचारिक समस्या है: उसकी स्थिति उस जेस्चर की होती है। जब इंटरैक्शन का स्वामी स्थानीय जेस्चर हो, तब @GestureState इस्तेमाल करें; जब सिस्टम ड्रैग उसका स्वामी हो, तब ड्रैग स्रोत का सत्र कॉलबैक इस्तेमाल करें। GestureState

पूर्णता मैट्रिक्स से सुधार की पुष्टि करें

हर पथ को अलग-अलग जाँचने के लिए सिंथेटिक सर्कल इस्तेमाल करें। ये कार्यान्वयन के अपेक्षित व्यवहार हैं, रिपोर्ट किए गए परीक्षण परिणाम नहीं।

इंटरैक्शन स्रोत फ़ीडबैक का अपेक्षित व्यवहार डेस्टिनेशन का अपेक्षित परिणाम
सर्कल को प्राप्तकर्ता क्षेत्र के भीतर ड्रॉप करें सामान्य रूप में लौटता है गिनती एक बार बढ़ती है
खाली विंडो क्षेत्र पर छोड़ें सामान्य रूप में लौटता है गिनती अपरिवर्तित रहती है
ड्रैग जारी रखते हुए प्राप्तकर्ता क्षेत्र से बाहर जाएँ धुंधला बना रहता है लक्ष्य हाइलाइट हट जाता है
फिर से प्रवेश करके मान्य ड्रॉप पूरा करें पूरा होने पर सामान्य रूप में लौटता है गिनती एक बार बढ़ती है
स्वीकार करने वाले डेस्टिनेशन के बिना ऐप विंडो के बाहर छोड़ें सामान्य रूप में लौटता है गिनती अपरिवर्तित रहती है
सक्रिय ड्रैग रद्द करें सामान्य रूप में लौटता है गिनती अपरिवर्तित रहती है
कई असफल ड्रैग दोहराएँ हर सत्र सामान्य रूप में लौटता है स्वीकार की गई कॉपियाँ जमा नहीं होतीं

अगर स्रोत धुंधला ही रहता है, तो पहले जाँचें कि उसका पूर्णता ऑब्ज़र्वर वास्तविक ड्रैग स्रोत को घेरे हुए है और स्रोत मौजूद बना रहता है। अगर केवल लक्ष्य हाइलाइट अटका है, तो लक्ष्य की प्रवेश/निकास हैंडलिंग और साझा सफ़ाई देखें। अगर अस्वीकृत ड्रॉप पर स्वीकार की गई गिनती बदलती है, तो दृश्य बहाली से अलग सत्यापन और मॉडल में बदलाव की जाँच करें।

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

स्रोत