अपने Mac ऐप के भीतर ड्रैग और ड्रॉप के लिए, स्रोत समर्थित कार्रवाइयाँ घोषित करता है, गंतव्य उनमें से एक चुनता है, और आपका मॉडल परिणाम लागू करता है। macOS 26 या उसके बाद के संस्करण पर SwiftUI में, दोनों सिरों को कॉन्फ़िगर करें और मूल आइटम को केवल सफलतापूर्वक पूरा हुए मूव के बाद हटाएँ।
यह लेख डेस्कटॉप का एक छोटा इंटरैक्शन बनाता है: एक कृत्रिम वृत्त को Copy here या Move here में ड्रैग करें। सब कुछ मेमोरी में रहता है। उदाहरण में कार्रवाई, आइटम की पहचान और स्रोत की सफ़ाई स्पष्ट की गई है, फिर इन्हीं ज़िम्मेदारियों को AppKit के लिए समझाया गया है।
स्रोत और गंतव्य को किस बात पर सहमत होना चाहिए?
ट्रांसफ़रेबल पेलोड बताता है कि दृश्यों के बीच क्या जाता है। कॉपी या मूव बताता है कि मूल आइटम के साथ क्या होता है। ये अलग-अलग निर्णय हैं। Apple का SwiftUI दस्तावेज़ कहता है कि ड्रैग में डिफ़ॉल्ट रूप से कॉपी समर्थित होती है; मूव सक्षम करने पर एक और समर्थित कार्रवाई जुड़ती है। फिर गंतव्य चुनता है कि आइटम को कैसे प्राप्त करना है। DragConfiguration
इंटरैक्शन डिज़ाइन करने के लिए तीन ज़िम्मेदारियाँ इस्तेमाल करें:
| ज़िम्मेदारी | कॉपी | मूव |
|---|---|---|
| स्रोत की क्षमता | कॉपी की सुविधा दें | मूव की सुविधा दें |
| गंतव्य पर बदलाव लागू करना | एक स्वतंत्र आइटम जोड़ें | मौजूदा आइटम प्राप्त करें |
| स्रोत पर कार्रवाई पूरी करना | मूल आइटम रखें | सफलतापूर्वक पूरा हुए मूव के बाद मूल आइटम हटाएँ |
इस वृत्त वाले उदाहरण में, स्वतंत्र कॉपी को एक नई पहचान मिलती है। मूव में पहचान वही रहती है, केवल उसका स्थान बदलता है। यह ऐप के डिज़ाइन का निर्णय है और Swift की उस आवश्यकता के अनुरूप है कि पहचान योग्य इकाई की पहचान स्थिर हो; उस पहचान का दायरा और अवधि ऐप तय करता है। Identifiable
व्यावहारिक रूप से, मूव कार्रवाई लौटाने से आपके ऐरे में कोई एलिमेंट अपने आप स्थानांतरित नहीं होता। आपको अब भी गंतव्य पर आइटम जोड़ना और स्रोत से हटाना होगा। Apple विशेष रूप से पूरा हुआ ड्रैग ऑपरेशन देखने का उदाहरण देता है, ताकि मॉडल को पता चले कि स्रोत आइटम कब हटाना है। onDragSessionUpdated(_:)
Mac ऐप को कौन-से SwiftUI API इस्तेमाल करने चाहिए?
इस कार्यान्वयन के लिए macOS 26 या उसके बाद का डिप्लॉयमेंट टारगेट और दस्तावेज़ित कॉन्फ़िगरेशन मॉडिफ़ायर वाला SDK इस्तेमाल करें। स्रोत पर draggable(_:) जोड़ें, उसकी कार्रवाइयाँ घोषित करने के लिए dragConfiguration(_:) जोड़ें, और गंतव्य की कार्रवाई चुनने के लिए dropConfiguration(_:) जोड़ें। दोनों कॉन्फ़िगरेशन मॉडिफ़ायर macOS 26 पर उपलब्ध बताए गए हैं। dragConfiguration(_:), dropConfiguration(_:)
मौजूदा dropDestination(for:isEnabled:action:) ओवरलोड अपने ऐक्शन को DropSession देता है। उसका ऐक्शन Void लौटाता है। यह पुराने ओवरलोड से अलग है, जिसके ऐक्शन को लोकेशन मिलता है और जो Boolean लौटाता है। उदाहरण में isEnabled: स्पष्ट रूप से दें, ताकि इच्छित ओवरलोड साफ़ रहे। dropDestination(for:isEnabled:action:)
गंतव्य पर, कार्रवाई चुनने से पहले suggestedOperations देखें। सामान्यतः मूव को प्राथमिकता देने वाले टारगेट के लिए, मूव उपलब्ध हो तो उसे चुनें; अन्यथा कॉपी उपलब्ध हो तो कॉपी चुनें; और यदि इनमें से कोई उपलब्ध न हो तो निषिद्ध चुनें। Apple का दस्तावेज़ित उदाहरण यही मूव से कॉपी वाला फ़ॉलबैक इस्तेमाल करता है और बताता है कि कॉन्फ़िगरेशन क्लोज़र बार-बार चलता है, इसलिए इसे हल्का रखें। dropConfiguration(_:)
हमारे दोनों टारगेट के अर्थ जानबूझकर निश्चित हैं। Copy here कॉपी स्वीकार करता है; Move here मूव स्वीकार करता है। यदि नामित कार्रवाई उपलब्ध न हो, तो टारगेट निषिद्ध लौटाता है। इससे कीबोर्ड मॉडिफ़ायर की ज़रूरत के बिना परिणाम अनुमानित रहता है।
मेमोरी में पूरा कॉपी और मूव उदाहरण बनाएँ
एक macOS SwiftUI ऐप बनाएँ, उसका डिप्लॉयमेंट टारगेट macOS 26 या उसके बाद का रखें, और ऐप व कंटेंट-व्यू की जनरेट की गई घोषणाओं को नीचे दी गई एकल फ़ाइल से बदलें। यह उदाहरण आइटम पहचान वाला अंतर्निहित स्ट्रिंग ट्रांसफ़र करता है, इसलिए किसी कस्टम ट्रांसफ़रेबल टाइप या कस्टम टाइप घोषणा की ज़रूरत नहीं है। Apple ड्रैग स्रोतों के लिए ट्रांसफ़रेबल पेलोड और ड्रॉप गंतव्यों के लिए स्ट्रिंग पेलोड—दोनों का दस्तावेज़ देता है। draggable(_:), dropDestination(for:isEnabled:action:)
यह एक खुली विंडो और एक स्रोत आइटम के लिए बनाया गया पूरा न्यूनतम ऐप है। मैंने इसके API संदर्भों को प्राप्त दस्तावेज़ों से मिलाकर देखा; यहाँ इसे कंपाइल या चला नहीं सका, क्योंकि Swift और macOS SDK उपलब्ध नहीं हैं।
import SwiftUI
import Foundation
@main
struct CopyMoveDemoApp: App {
var body: some Scene {
WindowGroup {
CopyMoveDemo()
}
}
}
struct CopyMoveDemo: View {
@State private var sourceID = UUID()
@State private var sourcePresent = true
@State private var copiedIDs: [UUID] = []
@State private var movedIDs: [UUID] = []
@State private var moveReceived = false
private var sourceConfiguration: DragConfiguration {
DragConfiguration(
operationsWithinApp: .init(allowMove: true),
operationsOutsideApp: .init(
allowCopy: false,
allowMove: false,
allowDelete: false
)
)
}
var body: some View {
VStack(spacing: 24) {
Text("Drag the circle to a target")
.font(.headline)
if sourcePresent {
Circle()
.fill(.blue)
.frame(width: 90, height: 90)
.overlay(Text("Circle").foregroundStyle(.white))
.draggable(sourceID.uuidString)
.dragConfiguration(sourceConfiguration)
.onDragSessionUpdated { session in
if session.phase == .initial {
moveReceived = false
}
if session.phase == .ended(.move), moveReceived {
sourcePresent = false
}
if case .ended = session.phase {
moveReceived = false
}
}
} else {
Text("Original moved")
.frame(width: 90, height: 90)
}
HStack(alignment: .top, spacing: 20) {
target(moving: false)
target(moving: true)
}
}
.padding(24)
.frame(minWidth: 520, minHeight: 320)
}
private func target(moving: Bool) -> some View {
let operation: DropOperation = moving ? .move : .copy
let ids = moving ? movedIDs : copiedIDs
return VStack(spacing: 10) {
Text(moving ? "Move here" : "Copy here")
.font(.headline)
ForEach(ids, id: \.self) { id in
Text("Circle \(id.uuidString.prefix(4))")
}
Spacer(minLength: 20)
}
.frame(width: 210, height: 150)
.background(.blue.opacity(0.08))
.contentShape(Rectangle())
.dropDestination(for: String.self, isEnabled: true) {
tokens, session in
guard session.localSession != nil,
sourcePresent,
tokens == [sourceID.uuidString] else {
return
}
if moving {
guard !movedIDs.contains(sourceID) else { return }
movedIDs.append(sourceID)
moveReceived = true
} else {
copiedIDs.append(UUID())
}
}
.dropConfiguration { session in
guard sourcePresent,
session.localSession != nil,
session.itemsCount == 1,
session.suggestedOperations.contains(operation) else {
return DropConfiguration(operation: .forbidden)
}
return DropConfiguration(operation: operation)
}
}
}
स्रोत कॉन्फ़िगरेशन ऐप के भीतर कॉपी और मूव की सुविधा देता है। macOS इनिशियलाइज़र का उपयोग करके यह ऐप के बाहर की कार्रवाइयों को निष्क्रिय करता है। गंतव्य localSession भी जाँचता है, जिसे Apple ऐप के बाहर से शुरू हुए ड्रैग के लिए अनुपस्थित बताता है। ये सेटिंग्स उदाहरण को अपनी विंडो के मेमोरी में मौजूद आइटम तक सीमित रखती हैं। DragConfiguration.OperationsWithinApp, init(allowCopy:allowMove:allowDelete:), DropSession
ड्रॉप ऐक्शन कुछ जोड़ने से पहले टोकन का सटीक मिलान करता है। कॉपी एक नई पहचान जोड़ती है। मूव मूल पहचान जोड़ता है और दर्ज करता है कि इस गंतव्य ने उसे प्राप्त किया। फिर स्रोत प्रदर्शित मूल आइटम को तभी हटाता है, जब सत्र का अंत मूव के साथ हुआ हो और उस प्राप्ति का रिकॉर्ड मौजूद हो। यह रिकॉर्ड इस सीमित प्रदर्शन के लिए ऐप-स्तर का सुरक्षा उपाय है; यह SwiftUI की ट्रांज़ैक्शन सुविधा नहीं है।
Apple की मौजूदा ड्रैग-फ़ेज़ घोषणा में initial, active, ending, ended और dataTransferCompleted शामिल हैं। उदाहरण ऑपरेशन के परिणाम के लिए दस्तावेज़ित ended(DropOperation) केस इस्तेमाल करता है। यह होवर करते समय या उससे पहले वाले समाप्ति चरण में स्रोत को नहीं हटाता। DragSession.Phase
स्वामित्व और जेस्चर की सीमाएँ स्पष्ट रखें
स्ट्रिंग केवल लुकअप टोकन है, वृत्त का पूरा मॉडल नहीं। गंतव्य इसे इस दृश्य की स्रोत स्थिति में खोजता है। कई दस्तावेज़ों या विंडो के लिए, इस स्थानीय लुकअप को स्पष्ट मॉडल स्वामी और वास्तविक ड्रैग सत्र तथा आइटमों से जुड़ी प्राप्ति के रिकॉर्ड से बदलें। केवल ऐप के भीतर से शुरू हुए ड्रैग से यह तय नहीं होता कि आइटम किस दस्तावेज़ का है।
कई ड्रैग किए जा सकने वाले आइटमों के लिए, स्थिर मॉडल पहचानें इस्तेमाल करें और जहाँ उपलब्ध हो, सत्र से ड्रैग की गई पहचानें प्राप्त करें। Apple पहचान योग्य पेलोड या अलग से दिए गए आइडेंटिफ़ायर के लिए draggedItemIDs(for:) देता है। पूरा होने की सूचना आने पर जो भी चयनित हो, उसे हटाने से बचें; चयन और ड्रैग किए गए आइटम की पहचान अलग इनपुट हैं। DragSession
ड्रैग इंटरैक्शन स्थापित करने के लिए SwiftUI का draggable(_:) मॉडिफ़ायर इस्तेमाल करें। पेलोड बनाते समय मॉडल से आइटम न हटाएँ। Apple बताता है कि यह मॉडिफ़ायर उपयुक्त जेस्चर जोड़ता है और चेतावनी देता है कि एक्सपोर्ट करने वाले क्लोज़र को मुख्य ऐक्टर पर डिस्पैच न करें। draggable(_:)
AppKit में यही सहमति कैसे काम करती है?
जब आपका डिप्लॉयमेंट टारगेट या मौजूदा व्यू आर्किटेक्चर स्पष्ट ड्रैग लाइफ़साइकल की माँग करे, तब AppKit इस्तेमाल करें। इसका स्रोत प्रोटोकॉल draggingSession(_:sourceOperationMaskFor:) के माध्यम से अनुमत कार्रवाइयाँ घोषित करता है। कॉन्टेक्स्ट से आप ऐप के अंदर और बाहर के गंतव्यों में अंतर कर सकते हैं। ऊपर वाले इंटरैक्शन के लिए, ऐप के भीतर कॉपी और मूव की सुविधा दें और ऐप के बाहर कोई कार्रवाई न दें। draggingSession(_:sourceOperationMaskFor:)
गंतव्य संबंधित पेस्टबोर्ड टाइप के लिए रजिस्टर करता है, फिर draggingEntered(_:) में कार्रवाई चुनता है और ज़रूरत पड़ने पर draggingUpdated(_:) में उस चयन को अपडेट करता है। Apple के अनुसार गंतव्य को स्रोत की कार्रवाइयों और अपने समर्थित ऑपरेशनों तथा डेटा टाइपों के बीच के मेल पर विचार करना चाहिए। एक कार्रवाई लौटाएँ, या स्वीकार्य मेल न होने पर कुछ भी नहीं। NSDraggingDestination, draggingEntered(_:)
तैयारी सफल होने के बाद, performDragOperation(_:) में आइटम जोड़ें। डेटा स्वीकार होने पर ही true लौटाएँ। स्रोत को पूरा हुआ ऑपरेशन draggingSession(_:endedAt:operation:) के ज़रिए मिलता है; मूव के लिए स्रोत से आइटम हटाना पूरा करने हेतु, अपने मॉडल के स्वामित्व नियमों के साथ उस परिणाम का उपयोग करें। performDragOperation(_:), draggingSession(_:endedAt:operation:)
यदि आप SwiftUI इंटरफ़ेस में AppKit ड्रैग सतह एम्बेड करते हैं, तो AppKit इंटरैक्शन का स्वामित्व उसी सतह को लेने दें। beginDraggingSession(with:event:source:) शुरू करने वाला माउस-डाउन इवेंट लेता है, और ड्रैग आइटम के फ़्रेम व्यू के कोऑर्डिनेट सिस्टम का उपयोग करते हैं। बाद के SwiftUI जेस्चर मान से इवेंट गढ़ने के बजाय, इवेंट और कोऑर्डिनेट का यह संबंध बनाए रखें। beginDraggingSession(with:event:source:)
पुराना SwiftUI गंतव्य कोड DropDelegate.dropUpdated(info:) से DropProposal(operation: .move) लौटा सकता है। इससे गंतव्य का प्रस्ताव व्यक्त होता है। यह स्रोत की क्षमता कॉन्फ़िगर करने, पूरे हुए ऑपरेशन को देखने या मॉडल में बदलाव करने की ज़िम्मेदारी की जगह नहीं लेता। जब नए SwiftUI स्रोत API उपलब्ध न हों और आपको दोनों सिरों पर स्पष्ट नियंत्रण चाहिए, तो AppKit का स्रोत और गंतव्य लाइफ़साइकल यह रास्ता देता है। dropUpdated(info:), NSDraggingSource
ऑपरेशन और मॉडल—दोनों की पुष्टि करें
इस उदाहरण के लिए, अपेक्षित स्थिति की यह चेकलिस्ट इस्तेमाल करें। ये कार्यान्वयन से निकली स्वीकृति-शर्तें हैं, रिपोर्ट किए गए परीक्षण परिणाम नहीं:
| कार्रवाई | मूल आइटम | कॉपी टारगेट | मूव टारगेट |
|---|---|---|---|
Copy here पर एक बार ड्रॉप करें |
बना रहता है | एक नई पहचान जुड़ती है | अपरिवर्तित |
| फिर से कॉपी करें | बना रहता है | एक और नई पहचान जुड़ती है | अपरिवर्तित |
Move here पर ड्रॉप करें |
मूव पूरा होने के बाद गायब हो जाता है | अपरिवर्तित | मूल पहचान प्राप्त करता है |
| दोनों टारगेट से बाहर रद्द करें या छोड़ें | बना रहता है | अपरिवर्तित | अपरिवर्तित |
जब व्यवहार अपेक्षा से अलग हो, तो चरणों को इसी क्रम में देखें: स्रोत की क्षमताएँ, गंतव्य की कार्रवाई, गंतव्य पर आइटम जोड़ना और स्रोत पर कार्रवाई पूरी करना। मूव के बाद मूल की डुप्लिकेट मौजूद हो, तो संभवतः स्रोत की सफ़ाई नहीं हुई। दो बार हटना, मॉडल के प्रतिस्पर्धी स्वामियों की ओर संकेत करता है। अप्रत्याशित कॉपी का अर्थ हो सकता है कि स्रोत ने मूव की सुविधा नहीं दी या गंतव्य ने कॉपी चुनी।
अंतिम ज़िम्मेदारी स्पष्ट रखें: तय की गई कार्रवाई ट्रांसफ़र का वर्णन करती है, जबकि आपका मॉडल आइटम का स्थान और पहचान लागू करता है। जिन कार्रवाइयों का आप समर्थन करते हैं, वे दें; गंतव्य पर अनुमत कार्रवाई चुनें; और हर मॉडल बदलाव उसके दस्तावेज़ित लाइफ़साइकल चरण पर एक ही बार करें।
स्रोत
DragConfiguration — Apple
Identifiable — Apple
onDragSessionUpdated(_:) — Apple
dragConfiguration(_:) — Apple
dropConfiguration(_:) — Apple
draggable(_:) — Apple
DropSession — Apple
DragSession.Phase — Apple
DragSession — Apple
NSDraggingDestination — Apple
draggingEntered(_:) — Apple
performDragOperation(_:) — Apple
dropUpdated(info:) — Apple
NSDraggingSource — Apple