किसी समूह और उसके चाइल्ड आइटम को एक साथ स्थानांतरित करने के लिए, चाइल्ड आइटम को पैरेंट मॉडल के भीतर रखें और पैरेंट संग्रह का क्रम बदलें। इस गाइड में एक छोटी SwiftUI Mac सूची बनाई गई है, जिसमें हर समूह एक पंक्ति के रूप में स्थानांतरित होता है और उसके चाइल्ड आइटम उससे जुड़े रहते हैं।
यह काम अपने ऐप के भीतर समूहों को फिर से व्यवस्थित करने का है: उदाहरण के लिए, Tile A1 और Tile A2 वाले Group A को Group B के नीचे ले जाना। उदाहरण में कृत्रिम स्थानीय डेटा है और SwiftUI के सूची स्थानांतरण कॉलबैक के साथ स्पष्ट Move Up और Move Down बटन दिए गए हैं।
किसे एक स्थानांतरित किए जा सकने वाले आइटम के रूप में गिनना चाहिए?
स्थानांतरण की इकाई को अपने डेटा की इकाई के अनुरूप रखें। इस इंटरफ़ेस में, बाहरी ऐरे में समूह हैं और हर समूह के अपने चाइल्ड ऐरे हैं। उस बाहरी ऐरे का क्रम बदलने से समूहों का क्रम बदलता है, जबकि हर समूह के चाइल्ड आइटम और उनका मौजूदा क्रम जस का तस रहता है।
हर समूह को एक संयुक्त सूची पंक्ति के रूप में दिखाएँ: VStack के भीतर एक शीर्षक, स्थानांतरण बटन और इंडेंट किए हुए चाइल्ड लेबल। बाहरी ForEach पर onMove(perform:) जोड़ें। Apple के दस्तावेज़ के अनुसार, कॉलबैक के ऑफ़सेट उस डायनामिक व्यू के अंतर्निहित संग्रह को दर्शाते हैं; यहाँ वह संग्रह समूह ऐरे है। Apple का स्थानांतरण कॉलबैक दस्तावेज़।
समूहों और चाइल्ड आइटम को स्थिर पहचानकर्ता दें। Apple के ForEach में पहचाने गए एलिमेंट आवश्यक हैं, जो Identifiable के ज़रिए या स्पष्ट पहचानकर्ता की पाथ के ज़रिए दिए जा सकते हैं। इस डिज़ाइन में पहचानकर्ता मॉडल का हिस्सा हैं और स्थान बदलने पर भी नहीं बदलते। जिस सामग्री का क्रम बदला जा सकता है, उसकी पहचान के लिए ऐरे की स्थिति का उपयोग न करें। Apple का ForEach दस्तावेज़।
SwiftUI का Group व्यू नीचे इस्तेमाल किया गया ऐप मॉडल नहीं है। किसी कंटेनर को नाम देने या शीर्षक दिखाने से यह तय नहीं होता कि कौन-से चाइल्ड रिकॉर्ड उसके साथ स्थानांतरित होने चाहिए। पैरेंट का संग्रहीत children ऐरे इस संबंध को स्पष्ट करता है।
SwiftUI का पूरा उदाहरण बनाएँ
Xcode में SwiftUI इंटरफ़ेस और macOS 12 या उसके बाद के डिप्लॉयमेंट टारगेट वाला macOS ऐप प्रोजेक्ट बनाएँ। बनाई गई ऐप एंट्री फ़ाइल को नीचे दिए पूरे प्रोग्राम से बदलें; टारगेट में केवल एक @main ऐप घोषणा रखें। उदाहरण में जानबूझकर साधारण सूची और टेक्स्ट बटन रखे गए हैं, ताकि स्थानांतरण को आसानी से देखा जा सके।
import Foundation
import SwiftUI
private struct DemoItem: Identifiable {
let id: String
let title: String
}
private struct DemoGroup: Identifiable {
let id: String
let title: String
let children: [DemoItem]
}
@main
struct GroupMoveDemoApp: App {
var body: some Scene {
WindowGroup {
GroupCollectionView()
}
}
}
private struct GroupCollectionView: View {
@State private var groups: [DemoGroup] = [
DemoGroup(
id: "group-a", title: "Group A",
children: [
DemoItem(id: "a1", title: "Tile A1"),
DemoItem(id: "a2", title: "Tile A2")
]
),
DemoGroup(
id: "group-b", title: "Group B",
children: [
DemoItem(id: "b1", title: "Tile B1")
]
),
DemoGroup(
id: "group-c", title: "Group C",
children: []
)
]
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text("Arrange Groups")
.font(.title2)
.padding(.horizontal)
List {
ForEach(groups) { group in
VStack(alignment: .leading, spacing: 8) {
HStack {
Text(group.title).font(.headline)
Spacer()
Button("Move Up") {
moveOne(id: group.id, down: false)
}
.disabled(group.id == groups.first?.id)
Button("Move Down") {
moveOne(id: group.id, down: true)
}
.disabled(group.id == groups.last?.id)
}
.buttonStyle(BorderlessButtonStyle())
ForEach(group.children) { child in
Text(child.title)
.padding(.leading, 16)
}
}
.padding(.vertical, 8)
}
.onMove(perform: moveGroups)
}
}
.padding(.vertical)
.frame(minWidth: 520, minHeight: 320)
}
private func moveGroups(from source: IndexSet, to destination: Int) {
withAnimation {
groups.move(fromOffsets: source, toOffset: destination)
}
}
private func moveOne(id: String, down: Bool) {
guard let index = groups.firstIndex(where: { $0.id == id }) else {
return
}
let neighbor = down ? index + 1 : index - 1
guard groups.indices.contains(neighbor) else {
return
}
let destination = down ? index + 2 : index - 1
moveGroups(from: IndexSet(integer: index), to: destination)
}
}
बाहरी ForEach हर समूह के लिए एक VStack बनाता है। उसके भीतर का ForEach उस पंक्ति में चाइल्ड आइटम दिखाता है; चाइल्ड आइटम के लिए अलग स्थानांतरण हैंडलर नहीं है। दोनों स्थानांतरण बटन समूह की मौजूदा स्थिति ID से ढूँढ़ते हैं और वही बदलाव करते हैं जो onMove इस्तेमाल करता है।
ऐप चलाएँ और Group A के पास Move Down पर क्लिक करें। अपेक्षित क्रम Group B, Group A, Group C हो जाएगा और Tile A1 तथा Tile A2, Group A के नीचे ही दिखेंगे। उस समूह के पास Move Up पर क्लिक करने से शुरुआती क्रम वापस आ जाएगा। Group C यह भी दिखाता है कि बिना चाइल्ड आइटम वाला समूह भी स्थानांतरित किया जा सकता है।
यह प्रोग्राम मौजूदा व्यू के जीवनकाल तक क्रम को मेमोरी में रखता है; इसमें कोई स्थायी संग्रहण परत नहीं है। इसके संदर्भों और स्थानांतरण लॉजिक की जाँच की गई, लेकिन इसे यहाँ कंपाइल नहीं किया गया, क्योंकि उपलब्ध शेल में Swift कंपाइलर नहीं है। इसे इंटीग्रेट करने से पहले Mac पर Xcode में बिल्ड करें और इंटरफ़ेस जाँचें।
नीचे की ओर ले जाने पर अलग डेस्टिनेशन क्यों इस्तेमाल होता है?
move(fromOffsets:toOffset:) को दिया गया डेस्टिनेशन मूल संग्रह में एक इंसर्शन सीमा है। Apple इसे उस ऑफ़सेट के रूप में परिभाषित करता है जिसके पहले स्थानांतरित एलिमेंट डालने हैं; यह 0...count के भीतर होता है। किसी एक एलिमेंट का अपना ऑफ़सेट या उसके बाद वाला ऑफ़सेट देने से कोई बदलाव नहीं होता। Apple का संग्रह स्थानांतरण दस्तावेज़।
शुरुआती क्रम [A, B, C] के लिए, ये अलग-अलग कार्रवाइयाँ अंतर स्पष्ट करती हैं:
| इच्छित परिणाम | स्रोत ऑफ़सेट | move को दिया गया डेस्टिनेशन |
परिणाम |
|---|---|---|---|
A को B के नीचे ले जाएँ |
0 |
2 |
[B, A, C] |
B को A के ऊपर ले जाएँ |
1 |
0 |
[B, A, C] |
A को अंत में ले जाएँ |
0 |
3 |
[B, C, A] |
A को उसी जगह रहने दें |
0 |
1 |
[A, B, C] |
इसीलिए नीचे ले जाने वाला बटन index + 2 इस्तेमाल करता है: यह अगले पड़ोसी एलिमेंट से पहले या ऐरे के अंत में डालता है। ऊपर ले जाने वाला बटन index - 1 इस्तेमाल करता है। onMove से सीधे मिले ऑफ़सेट के लिए, बटन-विशिष्ट गणना दोबारा किए बिना उन्हें संग्रह विधि को दें।
उदाहरण की डेस्टिनेशन गणना एक साथ लगी स्थिति में एक एलिमेंट को स्थानांतरित करने पर लागू होती है। कई स्रोत ऑफ़सेट वाले स्थानांतरण कॉलबैक के लिए, संग्रह विधि के साथ पूरा IndexSet इस्तेमाल करें; दस्तावेज़ के अनुसार, यह स्थानांतरित एलिमेंट का क्रम बनाए रखती है। Apple का संग्रह स्थानांतरण दस्तावेज़।
किस इंटरफ़ेस के लिए अलग कार्यान्वयन चाहिए?
समूह किस तरह दिखता है और उपयोगकर्ता उसके चाइल्ड आइटम के साथ कैसे काम करते हैं, इसके अनुसार कार्यान्वयन चुनें। संयुक्त पंक्ति वाला उदाहरण तब उपयोगी है जब समूह को हमेशा एक ब्लॉक के रूप में साथ ले जाना हो। इसमें चाइल्ड आइटम को अलग सूची पंक्तियाँ बनाने के बजाय उस ब्लॉक के भीतर लेबल के रूप में रखा गया है।
| इंटरफ़ेस की आवश्यकता | उपयुक्त शुरुआती विकल्प | स्थानांतरण की ज़िम्मेदारी |
|---|---|---|
| समूह का शीर्षक और चाइल्ड आइटम एक स्थानांतरित किए जा सकने वाले ब्लॉक हों | हर समूह के लिए एक संयुक्त पंक्ति वाला बाहरी ForEach |
पैरेंट ऐरे का क्रम बदलें |
| नेस्टेड वंशजों वाला फैलाया जा सकने वाला पदानुक्रम | OutlineGroup |
इच्छित पैरेंट संग्रह के लिए मॉडल स्थानांतरण लागू करें |
| हर समूह के लिए एक सेक्शन वाला AppKit संग्रह | NSCollectionView |
डेटा स्रोत अपडेट करें, फिर सेक्शन स्थानांतरण की सूचना संग्रह व्यू को दें |
| किसी आइटम के भीतर कस्टम संपादन योग्य टेक्स्ट | आइटम के एडिटर के भीतर TextKit | समूह का क्रम आसपास के संग्रह मॉडल में रखें |
फैलाए जा सकने वाले ट्री के लिए स्थानांतरण के स्पष्ट नियम चाहिए
Apple OutlineGroup को पहचाने गए, ट्री-संरचित डेटा के व्यू के रूप में वर्णित करता है, जिसमें शाखाएँ खोलने और बंद करने के लिए डिस्क्लोज़र कंट्रोल होते हैं। यह macOS 11 और उसके बाद के संस्करणों में उपलब्ध है। इसके दस्तावेज़ में दिया गया प्रस्तुति व्यवहार आपके ऐप के लिए सबट्री का क्रम बदलने की कोई सामान्य नीति तय नहीं करता। Apple का OutlineGroup दस्तावेज़।
ट्री के लिए डेस्टिनेशन को स्पष्ट रूप से परिभाषित करें: पैरेंट को उसके सहोदर आइटमों के बीच ले जाना और उसे किसी दूसरे पैरेंट के भीतर ले जाना अलग कार्रवाइयाँ हैं। सहोदर आइटमों का क्रम बदलने से एक पैरेंट ऐरे बदलता है। री-पैरेंटिंग में नोड को उसके पुराने पैरेंट से हटाना, नए पैरेंट में डालना और उसी नोड के किसी वंशज के भीतर डेस्टिनेशन को अस्वीकार करना शामिल है। ये ट्री की संरचना से निकले ऐप-मॉडल के नियम हैं।
अगर काम सिर्फ़ पूरे शीर्ष-स्तर के समूहों को स्थानांतरित करना है, तो संयुक्त पंक्ति वाला कार्यान्वयन डेस्टिनेशन की यह अतिरिक्त नीति टालता है। अगर चाइल्ड आइटम को अलग से चुनने या उनका क्रम बदलने की ज़रूरत है, तो पदानुक्रम को दिखाई देने वाली पंक्तियों में समतल करने से पहले उन इंटरैक्शन को स्पष्ट रूप से डिज़ाइन करें।
AppKit किसी सेक्शन को उसके भीतर के आइटमों के साथ स्थानांतरित कर सकता है
अगर आपका इंटरफ़ेस NSCollectionView है और हर समूह को एक सेक्शन के रूप में दिखाया गया है, तो Apple moveSection(_:toSection:) उपलब्ध कराता है। इसके दस्तावेज़ में सेक्शन और उनके भीतर के आइटम को फिर से व्यवस्थित करना स्पष्ट रूप से शामिल है और इस विधि को कॉल करने से पहले डेटा स्रोत अपडेट करना आवश्यक है। यह API macOS 10.11 और उसके बाद के संस्करणों में उपलब्ध है। Apple का सेक्शन स्थानांतरण दस्तावेज़।
इस तरीके में, स्रोत और अंतिम डेस्टिनेशन सेक्शन इंडेक्स तय करें, समूह डेटा का क्रम बदलें और सेक्शन स्थानांतरण API से संग्रह व्यू को सूचना दें। SwiftUI की इंसर्शन-सीमा गणना को AppKit के अपेक्षित डेस्टिनेशन-सेक्शन इंडेक्स से अलग रखें। दोनों विधियाँ डेस्टिनेशन को अलग तरह से परिभाषित करती हैं।
AppKit संग्रह को SwiftUI में एम्बेड करने के लिए NSViewRepresentable इस्तेमाल करें, जिसे Apple AppKit व्यू बनाने और अपडेट करने के दस्तावेज़ीकृत रैपर के रूप में बताता है। उपयुक्त कोऑर्डिनेशन तरीके से संग्रह के इंटरैक्शन को वापस SwiftUI मॉडल तक पहुँचाएँ और बाहरी लेआउट को नियंत्रित करने दें। Apple का NSViewRepresentable दस्तावेज़।
TextKit किसी आइटम के भीतर टेक्स्ट लेआउट संभालता है
Apple NSTextLayoutManager को कस्टम टेक्स्ट लेआउट और प्रस्तुति के मैनेजर के रूप में परिभाषित करता है; यह macOS 12 और उसके बाद के संस्करणों में उपलब्ध है। इसकी ज़िम्मेदारियाँ टेक्स्ट कंटेनर, एलिमेंट और लेआउट फ़्रैगमेंट से जुड़ी हैं। इस संग्रह कार्य के लिए वास्तुशिल्प नतीजा यह है कि समूहों का क्रम संग्रह मॉडल में ही रखें, भले ही किसी चाइल्ड आइटम में कस्टम TextKit एडिटर हो। Apple का NSTextLayoutManager दस्तावेज़।
गलत तरीके से स्थानांतरित हो रहे समूह की जाँच कैसे करें?
सबसे छोटे दिखाई देने वाले उदाहरण से शुरू करें और एक कार्रवाई के बाद मॉडल की जाँच करें। नीचे की जाँच से क्रम की गलती और दिखाई देने वाली पंक्तियों तथा अंतर्निहित डेटा के बीच बेमेल का अंतर पता चलता है।
- सिर्फ़ शीर्षक स्थानांतरित होता है: देखें कि क्या चाइल्ड आइटम एक फ्लैट ऐरे में असंबंधित एंट्री के रूप में रखे गए हैं। वह पैरेंट ऑब्जेक्ट स्थानांतरित करें जिसके स्वामित्व में वे हैं, या फ्लैट संरचना के लिए ब्लॉक स्थानांतरण स्पष्ट रूप से लागू करें।
- नीचे ले जाने से कुछ नहीं होता: देखें कि क्या बटन
index + 1देता है। एक स्रोत आइटम के लिए, यह सीमा आइटम को उसी जगह रखती है। - फ़िल्टर करने के बाद गलत समूह स्थानांतरित होता है: देखें कि क्या दिखाई देने वाले ऑफ़सेट पूरे ऐरे पर लागू किए जा रहे हैं। बदलाव से पहले दिखाई देने वाले समूह ID को मुख्य मॉडल में खोजें और तय करें कि फ़िल्टर किए गए डेस्टिनेशन को कहाँ रखा जाना चाहिए।
- क्रम तुरंत वापस बदल जाता है: देखें कि क्या हर रेंडर पर दिखाई देने वाले ऐरे को फिर से सॉर्ट किया जा रहा है। मैन्युअल क्रम के लिए ऐसा क्रम चाहिए जिसे डिस्प्ले वास्तव में बनाए रखे।
- कोई चाइल्ड आइटम अलग से स्थानांतरित होता है: देखें कि स्थानांतरण हैंडलर कहाँ जोड़ा गया है। इस उदाहरण में, यह केवल बाहरी समूह संग्रह पर है।
इंटीग्रेशन से पहले ऊपर और नीचे की ओर स्थानांतरण, सूची के दोनों छोर, केवल एक समूह बचने की स्थिति और खाली समूह जाँचें। दिखावट के साथ मॉडल के नियम भी सत्यापित करें: वही समूह ID मौजूद रहें, हर समूह के वही चाइल्ड ID बने रहें और चाइल्ड आइटम का क्रम न बदले। ये जाँच सीधे इस आवश्यकता को परखती हैं कि पैरेंट और उसके चाइल्ड आइटम साथ स्थानांतरित हों।
Mac के कॉम्पैक्ट इंटरफ़ेस के लिए, हर स्थानांतरित की जा सकने वाली पंक्ति के लिए एक पैरेंट मॉडल और एक साझा रीऑर्डर फ़ंक्शन से शुरू करें। तब उपयोगकर्ता एक ही कार्रवाई में Group A को स्थानांतरित कर सकता है और उसके चाइल्ड आइटम जुड़े रहते हैं। जब इंटरफ़ेस को चाइल्ड आइटम के साथ अलग इंटरैक्शन या सेक्शन-आधारित संग्रह व्यवहार चाहिए, तब पदानुक्रम वाला या AppKit कार्यान्वयन जोड़ें।
स्रोत
- onMove(perform:) — Apple
- ForEach — Apple
- move(fromOffsets:toOffset:) — Apple
- OutlineGroup — Apple
- moveSection(_:toSection:) — Apple
- NSViewRepresentable — Apple
- NSTextLayoutManager — Apple