ओवरलैपिंग ड्रॉप ज़ोन वाले Mac कैनवस के लिए, एक स्थिर पूर्वज पर नेटिव ड्रॉप हैंडलिंग रखें और मौजूदा मॉडल स्थिति से तार्किक गंतव्य तय करें। ड्रॉप कमिट करते समय पात्रता फिर से जाँचें; अस्वीकृत कॉलबैक को किसी अन्य व्यू को सौंपने का दस्तावेज़ीकृत तरीका नहीं बताया गया है।
यह लेख सिंथेटिक आकार लेबल और दो ओवरलैपिंग पैनलों वाले ऐप के लिए यह इंटरैक्शन बनाता है। ठोस काम यह है कि ऊपरी पैनल द्वारा आइटम स्वीकार करना बंद करने पर निचला पैनल ड्रॉप प्राप्त कर सके, इसमें वह स्थिति भी शामिल है जब ड्रैग के दौरान उसकी पात्रता बदलती है। नीचे दी गई रूटिंग नीति, दस्तावेज़ीकृत कॉलबैक पर आधारित ऐप डिज़ाइन है।
स्थिति बदलने पर ड्रैग दूसरे लक्ष्य तक भरोसेमंद ढंग से क्यों नहीं पहुँचता?
शुरुआत में तीन सवाल अलग रखें: कौन-सा नेटिव व्यू ड्रैग प्राप्त करता है, आपका ऐप किस तार्किक पैनल को पात्र मानता है, और आखिर में कौन-सा मॉडल आइटम प्राप्त करता है। स्थिति बदलने से दूसरे सवाल का जवाब बदल सकता है, लेकिन इससे पहले सवाल का नया जवाब तय नहीं होता।
Apple, SwiftUI के validateDrop(info:) को किसी संगत ड्रॉप के गंतव्य में प्रवेश करने पर होने वाली पुष्टि के रूप में दस्तावेज़ित करता है। वह dropUpdated(info:) को तब अपडेट के रूप में दस्तावेज़ित करता है जब पुष्टि किया हुआ ड्रॉप संशोधित व्यू के भीतर चलता है। इनमें से कोई भी विवरण असंबंधित स्वामी-व्यू स्थिति बदलने पर नए गंतव्य की खोज का वादा नहीं करता। SwiftUI पुष्टि और ड्रॉप अपडेट।
AppKit की सीमा और स्पष्ट है: एक समय में केवल एक गंतव्य को draggingUpdated(_:) संदेशों का क्रम मिलता है। जब दो ओवरलैपिंग व्यू मान्य गंतव्य हों, तो इमेज छोड़े जाने या बाहर खींचे जाने तक सबसे ऊपर वाला व्यू ये संदेश प्राप्त करता है। यह AppKit रूटिंग का वर्णन है; इससे SwiftUI के सिब्लिंग की प्राथमिकता का कोई सामान्य अनुबंध स्थापित नहीं होता। AppKit गंतव्य अपडेट।
पुराने SwiftUI ड्रॉप ऐक्शन से false लौटाने का अर्थ है कि ड्रॉप सफल नहीं हुआ। Apple इस रिटर्न वैल्यू को नीचे के गंतव्य से दोबारा प्रयास करने का अनुरोध नहीं बताता। इसी तरह, dropUpdated(info:) से nil लौटाने पर पिछला मान्य प्रस्ताव बना रहता है या डिफ़ॉल्ट रूप से कॉपी चुनी जाती है; इसका अर्थ “आगे जाने दें” नहीं है। क्लोज़र-आधारित ड्रॉप हैंडलिंग और प्रस्ताव का व्यवहार।
इसका व्यावहारिक नतीजा यह है कि फ़ॉलबैक को ऐप के स्पष्ट नियम के रूप में लागू करें। एकल कैनवस के लिए, एक नेटिव रिसीवर कई तार्किक पैनलों में से चुन सकता है और ओवरलैपिंग चाइल्ड रिसीवरों के बीच नेटिव हैंडऑफ़ पर निर्भर नहीं रहता।
मॉडिफ़ायर जोड़ने से पहले स्वामित्व और फ़ॉलबैक तय करें
इस उदाहरण में नारंगी पैनल नीले पैनल के पीछे है। नारंगी हमेशा नमूना लेबल स्वीकार करता है। नीला उन्हें तभी स्वीकार करता है जब blueEnabled true हो। अपेक्षित व्यवहार यह है:
| पॉइंटर की जगह | नीला ड्रॉप स्वीकार करता है | तार्किक प्राप्तकर्ता |
|---|---|---|
| केवल नारंगी वाला क्षेत्र | कोई भी मान | नारंगी |
| ओवरलैप | हाँ | नीला |
| ओवरलैप | नहीं | नारंगी |
| केवल नीला वाला क्षेत्र | नहीं | कोई नहीं |
| दोनों पैनलों के बाहर | कोई भी मान | कोई नहीं |
यह तालिका कार्यान्वयन के लिए निर्णय-सहायक है। खास तौर पर, नीले को अक्षम करने से उसके आयत के भीतर का हर बिंदु नारंगी का गंतव्य नहीं बन जाता। नारंगी को आइटम तभी मिलता है जब पॉइंटर नारंगी के आयत के भीतर भी हो।
रूटिंग नियंत्रित करने वाली स्थिति को विज़ुअल पैनलों से ऊपर रखें। Apple सलाह देता है कि किसी स्थिति मान को उस सबसे ऊपरी व्यू में रखें जिसे उसकी ज़रूरत है और उसे वंशजों के साथ साझा करें। यहाँ एक पैरेंट ऐसा मॉडल रखता है जिसमें पात्रता, ज्यामिति और प्राप्त आइटम शामिल हैं। चाइल्ड आयत अलग-अलग ड्रॉप हैंडलर के बजाय केवल रूप-रंग तय करते हैं। SwiftUI स्थिति का स्वामित्व।
ड्रॉप ऐक्शन चलने पर उस मॉडल को पढ़ें। प्रवेश के समय पात्रता का बूलियन या गंतव्य पहचानकर्ता सहेजकर बाद में उसे निर्णायक न मानें। Swift क्लोज़र अपने निर्माण संदर्भ के मान और संदर्भ बनाए रख सकते हैं; स्वामी बदलने के बाद भी रखा गया संदर्भ पुराने मॉडल की ओर इशारा कर सकता है। जाँचें कि कॉलबैक वास्तव में किस ऑब्जेक्ट को कैप्चर करता है। Swift क्लोज़र कैप्चर।
स्वामी बदलने की स्थिति के लिए अलग नीति चुनें: नए स्वामी के आधार पर निर्णय लें या पुराने स्वामी से जुड़ा काम अस्वीकार करें। अस्वीकार करने के लिए एक उपयोगी डिज़ाइन यह है कि स्वामी के साथ संशोधन संख्या जोड़ें, बदलने पर उसे बढ़ाएँ और विलंबित काम कमिट करने से पहले कैप्चर की गई संख्या से तुलना करें। अस्थायी “अभी हाइलाइट है” मान को स्वामित्व तय नहीं करना चाहिए।
एक स्थिर SwiftUI ड्रॉप सतह बनाएँ
नीचे दिए गए पूरे उदाहरण के लिए macOS 26 डिप्लॉयमेंट टार्गेट वाला macOS SwiftUI ऐप और dropDestination(for:isEnabled:action:) शामिल करने वाला SDK आवश्यक है। Apple इस ओवरलोड को ऐसा बताता है जो Transferable प्रकार, सक्षम/अक्षम स्थिति और डिकोड किए गए आइटम तथा DropSession प्राप्त करने वाला ऐक्शन स्वीकार करता है। गंतव्य संशोधित व्यू के आकार और स्थान में रहता है। मौजूदा ड्रॉप-डेस्टिनेशन API।
SwiftUI का उपयोग करके नया macOS App प्रोजेक्ट बनाएँ। उसकी बनाई हुई ऐप घोषणा को नीचे दिए दो कोड ब्लॉक से बदलें और उन्हें एक ही Swift फ़ाइल में क्रम से रखें। दोनों मिलकर एक प्रोग्राम बनाते हैं। इसमें केवल मेमोरी में रखी स्ट्रिंग और आयतें इस्तेमाल होती हैं। स्रोत लेबल ड्रॉप के बाद भी उपलब्ध रहते हैं, इसलिए यह उदाहरण कॉपी की तरह काम करता है।
मॉडल और निर्धारक रूटिंग
चुनी गई विज़ुअल प्राथमिकता दिखाने के लिए मॉडल पहले नीले पैनल को जाँचता है। रेंडरिंग और रूटिंग दोनों उन्हीं आयतों का उपयोग करते हैं। इसका कमिट मेथड मौजूदा पात्रता के आधार पर प्राप्तकर्ता फिर से तय करता है और कुछ जोड़ने से पहले पूरे नमूना बैच की पुष्टि करता है।
import SwiftUI
import Combine
@MainActor
final class CanvasModel: ObservableObject {
enum Zone: String {
case orange = "Orange"
case blue = "Blue"
}
let orangeRect = CGRect(x: 30, y: 30, width: 230, height: 170)
let blueRect = CGRect(x: 150, y: 100, width: 230, height: 170)
let samples = ["Circle", "Square"]
@Published var blueEnabled = true
@Published private(set) var orangeItems: [String] = []
@Published private(set) var blueItems: [String] = []
@Published private(set) var message = "Drag a sample onto a panel."
func recipient(at point: CGPoint) -> Zone? {
if blueEnabled && blueRect.contains(point) {
return .blue
}
if orangeRect.contains(point) {
return .orange
}
return nil
}
func receive(_ items: [String], at point: CGPoint, isLocal: Bool) {
guard isLocal,
!items.isEmpty,
items.allSatisfy({ samples.contains($0) }) else {
message = "No items added: use this window's sample labels."
return
}
guard let zone = recipient(at: point) else {
message = "No items added: this location has no eligible panel."
return
}
switch zone {
case .orange:
orangeItems.append(contentsOf: items)
case .blue:
blueItems.append(contentsOf: items)
}
message = "Added \(items.joined(separator: ", ")) to \(zone.rawValue)."
}
}
ऐप और ड्रॉप रिसीवर
पैरेंट आयत में एकमात्र ड्रॉप मॉडिफ़ायर है। किसी भी रंगीन पैनल में प्रतिस्पर्धी गंतव्य दर्ज नहीं किया गया है। DropSession गंतव्य के स्थानीय निर्देशांक-क्षेत्र में स्थान और एक localSession देता है, जो ऐप से शुरू हुए ड्रैग की पहचान करता है। मॉडल नमूना मानों की भी जाँच करता है। ड्रॉप-सेशन की जानकारी।
@main
struct DropRoutingDemoApp: App {
var body: some Scene {
WindowGroup {
CanvasDemo()
}
}
}
@MainActor
struct CanvasDemo: View {
@StateObject private var model = CanvasModel()
@State private var scheduledChange: Task<Void, Never>?
var body: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
ForEach(model.samples, id: \.self) { item in
Text(item)
.padding(10)
.background(Color.gray.opacity(0.15))
.draggable(item)
}
}
Toggle("Blue accepts drops", isOn: $model.blueEnabled)
Button("Toggle blue in 3 seconds") {
scheduledChange?.cancel()
scheduledChange = Task { @MainActor in
do {
try await Task.sleep(for: .seconds(3))
} catch {
return
}
model.blueEnabled.toggle()
}
}
ZStack(alignment: .topLeading) {
Rectangle().fill(Color.gray.opacity(0.08))
panel("Orange", rect: model.orangeRect,
color: .orange, opacity: 0.55)
panel("Blue", rect: model.blueRect,
color: .blue,
opacity: model.blueEnabled ? 0.55 : 0.15)
}
.frame(width: 420, height: 300)
.dropDestination(for: String.self, isEnabled: true) {
items, session in
model.receive(items, at: session.location,
isLocal: session.localSession != nil)
}
Text(model.message)
Text("Orange: \(model.orangeItems.joined(separator: ", "))")
Text("Blue: \(model.blueItems.joined(separator: ", "))")
}
.padding(20)
.onDisappear {
scheduledChange?.cancel()
}
}
private func panel(_ title: String, rect: CGRect,
color: Color, opacity: Double) -> some View {
Rectangle()
.fill(color.opacity(opacity))
.overlay(Text(title))
.frame(width: rect.width, height: rect.height)
.position(x: rect.midX, y: rect.midY)
}
}
SwiftUI का draggable(_:) उपयोगकर्ता इनपुट के ज़रिए ड्रैग शुरू करने के लिए आवश्यक जेस्चर जोड़ता है। विलंबित बटन केवल उदाहरण की पात्रता स्थिति बदलता है; यह ड्रैग सेशन शुरू, फिर से शुरू या स्थानांतरित नहीं करता। SwiftUI ड्रैग-स्रोत का व्यवहार।
Toggle blue in 3 seconds दबाएँ, फिर Circle को ओवरलैप में खींचकर वहाँ तब तक रोकें जब तक नीला धुंधला न हो जाए। ओवरलैप के भीतर छोड़ें: अपेक्षित प्राप्तकर्ता नारंगी है। नीले के गैर-ओवरलैपिंग क्षेत्र में, नीला अक्षम होने पर छोड़ें: किसी भी आइटम ऐरे में बदलाव नहीं होगा और स्थिति इसका कारण बताएगी।
यह रिसीवर पूरे कैनवस में सक्षम रहता है ताकि वह पूरी रूटिंग नीति लागू कर सके। इसलिए उदाहरण स्थान-विशिष्ट नेटिव अस्वीकृति कर्सर या होवर हाइलाइट नहीं देता। इसकी स्थिति छोड़ने के बाद ऐप की स्वीकृति बताती है। यदि उत्पाद को यह फ़ीडबैक चाहिए, तो प्रस्ताव और होवर हैंडलिंग अलग से जोड़ें।
प्राप्त API घोषणाओं के आधार पर कोड की जाँच की गई। इसे यहाँ कंपाइल या चलाया नहीं गया, क्योंकि उपलब्ध वातावरण में Swift टूलचेन या macOS रनटाइम नहीं है।
होवर अपडेट और पुराने डिप्लॉयमेंट टार्गेट अलग से सँभालें
यदि आप नए ओवरलोड के बिना सिस्टम को सपोर्ट करते हैं, तो उपलब्ध रिसीविंग API चुनते हुए वही मॉडल और ज्यामिति नीति बनाए रखें। पुराना dropDestination(for:action:isTargeted:) डिकोड किए गए आइटम और स्थानीय बिंदु देता है; इसका बूलियन परिणाम सफलता बताता है। इसका isTargeted क्लोज़र नेटिव गंतव्य क्षेत्र—इस डिज़ाइन में पूरा कैनवस—में प्रवेश और बाहर निकलने का वर्णन करता है। यह पॉइंटर के नीचे के तार्किक पैनल की पहचान नहीं करता। पुराने ड्रॉप-डेस्टिनेशन का अनुबंध।
स्पष्ट पुष्टि और ऑपरेशन प्रस्तावों के लिए, onDrop(of:delegate:) एक DropDelegate लगाता है। शुरुआती पात्रता के लिए validateDrop(info:), मौजूदा प्रस्तावों के लिए dropUpdated(info:) और अंतिम पुष्टि तथा आइटम जोड़ने के लिए performDrop(info:) का उपयोग करें। .forbidden लौटाना उस स्थान पर निषिद्ध गतिविधि बताता है; यह किसी अन्य नेटिव रिसीवर को सौंपने का दस्तावेज़ीकृत तरीका नहीं है। डेलीगेट-आधारित गंतव्य और ड्रॉप ऑपरेशन।
प्रोवाइडर का उपयोग करते समय लोडिंग की एक महत्वपूर्ण सीमा है: DropInfo.itemProviders(for:) केवल performDrop के दौरान मान्य है। उस मेथड के लौटने से पहले प्रोवाइडर की सामग्री लोड करना शुरू करें। पूरा होने की सूचना बाद में आ सकती है, इसलिए मॉडल बदलने से पहले गंतव्य की पहचान, संशोधन और जारी पात्रता फिर से जाँचें। अस्वीकृत या विफल लोड की सूचना दें, न कि नतीजे को चुपचाप छोड़ दें। प्रोवाइडर तक पहुँच और ड्रॉप पूरा करने की आवश्यकताएँ।
होवर प्रस्तुति के लिए अस्थायी सेशन जानकारी को प्राप्त आइटम से अलग रखें। नए DropSession के दस्तावेज़ में चरण के बदलाव देखने के लिए onDropSessionUpdated(_:) का उल्लेख है। यह न मानें कि मॉडल की स्थिति बदलने से पॉइंटर का एक और अपडेट मिलेगा; यदि आपके पास पहले से मान्य होवर स्थान है, तो पात्रता बदलने पर तार्किक हाइलाइट भी फिर से निकालें। अंतिम स्वीकृति में फिर भी रूटिंग फ़ंक्शन दोबारा चलना चाहिए। ड्रॉप-सेशन जीवनचक्र।
कैनवस को AppKit कब इस्तेमाल करना चाहिए?
जब आवश्यक इंटरैक्शन इस बात पर निर्भर हो कि पॉइंटर स्थिर रहने पर नियमित नेटिव अपडेट मिलें, तब AppKit गंतव्य इस्तेमाल करें। AppKit का wantsPeriodicDraggingUpdates() इस व्यवहार को नियंत्रित करता है: false लौटाने पर अपडेट केवल माउस की गति या मॉडिफ़ायर में बदलाव तक सीमित रहते हैं; अन्यथा कुछ न बदलने पर भी नियमित अपडेट होते हैं। इससे स्थिर ड्रैग के दौरान मौजूदा ऑपरेशन का दोबारा मूल्यांकन करने का दस्तावेज़ीकृत तरीका मिलता है। नियमित ड्रैगिंग अपडेट।
इसकी अनुरूप संरचना में भी पूरे कैनवस के लिए एक ही गंतव्य होता है। उसके स्वीकार किए गए पेस्टबोर्ड प्रकार दर्ज करें, draggingEntered(_:) और draggingUpdated(_:) के दौरान तार्किक पैनल तय करें, छोड़ते समय स्वीकृति जाँचें और performDragOperation(_:) में आइटम शामिल करें। Apple विशेष रूप से एक गंतव्य व्यू के भीतर मौजूद ग्राफ़िक्स या टेक्स्ट क्षेत्रों के अनुसार ऑपरेशन ढालने का वर्णन करता है। AppKit गंतव्य जीवनचक्र और स्थान-निर्भर ऑपरेशन।
NSViewRepresentable के साथ उस गंतव्य को एम्बेड करते समय updateNSView(_:context:) में उसका मॉडल संदर्भ, पात्रता कॉन्फ़िगरेशन और कॉलबैक अपडेट करें। केवल शुरुआती निर्माण से बाद के स्वामी बदलाव समन्वित नहीं हो सकते; Apple इस अपडेट मेथड को AppKit व्यू को SwiftUI स्थिति के अनुरूप लाने की जगह बताता है। AppKit व्यू अपडेट करना।
स्रोत इवेंट की सीमा भी बनाए रखें। AppKit का beginDraggingSession(with:event:source:) ड्रैगिंग आइटम और शुरुआती माउस-डाउन इवेंट से सेशन शुरू करता है। यह गंतव्य को दूसरे स्थान पर भेजने वाला API नहीं है। यदि मौजूदा SwiftUI स्रोत काम करता है तो उसे बनाए रखें; AppKit स्रोत तभी उपयोग करें जब आपको स्रोत-पक्ष के उस इंटरैक्शन का नियंत्रण भी चाहिए। AppKit ड्रैग शुरू करना।
उन बिंदुओं पर स्थिति बदलाव सत्यापित करें जो स्वामित्व को प्रभावित करते हैं
जिन Mac डिप्लॉयमेंट संस्करणों को आप सपोर्ट करते हैं, उन पर ये मैन्युअल जाँचें करें। वे सही रूटिंग को ऐसे विज़ुअल बदलाव से अलग करती हैं जो केवल काम करता हुआ लगता है:
- नीला सक्षम होने पर ओवरलैप में ड्रॉप करें: केवल नीले के ऐरे में आइटम जुड़ता है।
- ड्रैग शुरू करने से पहले नीला अक्षम करें: ओवरलैप नारंगी को जाता है।
- स्थिर ड्रैग के दौरान नीला अक्षम करें: अंतिम स्वीकृति में अक्षम स्थिति का उपयोग होता है।
- नीले के केवल अपने क्षेत्र में, अक्षम होने पर ड्रॉप करें: दोनों ऐरे अपरिवर्तित रहते हैं और स्थिति परिणाम बताती है।
- ड्रैग के दौरान नीला फिर सक्षम करें: कमिट के समय ओवरलैप नीले को जाता है।
- ड्रैग रद्द करें: किसी भी ऐरे में बदलाव नहीं होता।
- यदि आपका ऐप कैनवस के स्वामी बदलता है, तो ड्रैग के दौरान एक स्वामी बदलें और चुनी हुई स्वामी-बदलाव नीति की पुष्टि करें।
- यदि आपका कार्यान्वयन प्रोवाइडर को असिंक्रोनस ढंग से लोड करता है, तो पूरा होने से पहले स्वामी बदलें और संशोधन जाँच तथा दिखाई देने वाली विफलता सूचना की पुष्टि करें।
स्क्रॉल होने वाले, ज़ूम किए जाने वाले या आकार बदलने वाले कैनवस के लिए, हर पैनल की सीमाओं और ड्रॉप स्थान को एक ही निर्देशांक-क्षेत्र में बदलकर इसी डिज़ाइन को बढ़ाएँ। इस उदाहरण में स्थिर आयतें यह रूपांतरण टालती हैं; बदले हुए लेआउट में उनके नंबर कॉपी करने से रूटिंग नीति टूट जाएगी।
एक स्थिर नेटिव रिसीवर और मौजूदा स्थिति पर आधारित रूटिंग फ़ंक्शन इस इंटरैक्शन का नतीजा स्पष्ट करते हैं: सबसे ऊपर वाला पात्र पैनल आइटम प्राप्त करता है, पात्र निचला पैनल ओवरलैप का फ़ॉलबैक प्राप्त करता है, और किसी पैनल से न ढकी जगह पर मॉडल अपरिवर्तित रहता है। होवर फ़ीडबैक और अंतिम स्वीकृति में यही नीति साझा रखें और अंतिम स्वीकृति पर अपनी जाँच दोबारा करें।
स्रोत
validateDrop(info:) — Apple।
dropUpdated(info:) — Apple।
draggingUpdated(_:) — Apple।
State — Apple।
Closures — Swift.org।
DropSession — Apple।
draggable(_:) — Apple।
onDrop(of:delegate:) — Apple।
DropOperation — Apple।
itemProviders(for:) — Apple।
performDrop(info:) — Apple।
wantsPeriodicDraggingUpdates() — Apple।
NSDraggingDestination — Apple।
updateNSView(_:context:) — Apple।