SwiftUI सूची की किसी पंक्ति में ObservableObject आइटम के भीतर हुए बदलाव दिखाने के लिए, उस आइटम को @ObservedObject के साथ पंक्ति दृश्य में पास करें और उसकी बदल सकने वाली प्रॉपर्टियों को प्रकाशित करें। यह मार्गदर्शिका Mac के लिए एक छोटा लेबल संपादक बनाती है और संग्रह अपडेट, पंक्ति अवलोकन तथा पहचान की अलग-अलग भूमिकाएँ समझाती है।

आइटम बदलने पर पंक्ति अपरिवर्तित क्यों रहती है?

ऐसे स्टोर पर विचार करें जिसमें @Published var items: [LabelItem] प्रॉपर्टी हो, जहाँ हर LabelItem एक क्लास हो। कोई आइटम जोड़ने से ऐरे बदलता है। किसी मौजूदा आइटम को नया शीर्षक देने से संदर्भित ऑब्जेक्ट बदलता है। ये अलग-अलग बदलाव हैं: Swift ऐरे मान प्रकार हैं, जबकि क्लास इंस्टेंस संदर्भ अर्थविज्ञान का पालन करते हैं। क्लास संदर्भों वाले ऐरे में भी साझा इंस्टेंस के संदर्भ ही होते हैं। Swift में मान और संदर्भ प्रकार।

इसका व्यावहारिक परिणाम यह है कि ऐरे को प्रकाशित करने से उसमें मौजूद हर ऑब्जेक्ट की हर प्रॉपर्टी का अवलोकन अपने आप स्थापित नहीं होता। Apple की माइग्रेशन मार्गदर्शिका ऑब्जेक्ट के संग्रहों को ट्रैक करने को नए Observation सिस्टम का एक लाभ बताती है। मौजूदा ObservableObject कार्यान्वयन के लिए, हर पंक्ति को उस ऑब्जेक्ट से अपना अलग कनेक्शन दें जिसे वह दिखाती है। Observable Object प्रोटोकॉल से Observable मैक्रो पर माइग्रेट करना।

समस्या का पता लगाने का एक उपयोगी तरीका है दो ज़िम्मेदारियों की पहचान करना: कंटेनर दृश्य यह देखता है कि सूची में कौन-से आइटम होने चाहिए; हर पंक्ति अपने आइटम की बदल सकने वाली सामग्री को देखती है। Apple @ObservedObject को आने वाले ObservableObject के लिए रैपर बताता है, जिसके प्रकाशित बदलाव प्राप्तकर्ता दृश्य को अपडेट करने चाहिए। ObservedObject।

अवलोकित पंक्ति के साथ macOS का पूरा उदाहरण बनाएँ

इस संस्करण का उपयोग तब करें जब आपका ऐप पहले से Combine के ObservableObject का उपयोग करता हो, जिसमें macOS के 14 से पुराने संस्करणों का समर्थन भी शामिल है। इस उदाहरण के लिए macOS 11 या बाद का संस्करण चाहिए, क्योंकि इसका स्वामी @StateObject का उपयोग करता है, जिसे Apple macOS 11 से उपलब्ध बताता है। Swift कंपाइलर के वर्तमान संस्करण वाला Xcode इस्तेमाल करें। StateObject।

SwiftUI इंटरफ़ेस वाला macOS ऐप टार्गेट बनाएँ। जनरेट की गई ऐप एंट्री फ़ाइल को नीचे दिए पूरे प्रोग्राम से बदलें और अगर जनरेट किया गया कंटेंट व्यू अब आवश्यक न हो तो उसे हटा दें। टार्गेट में ठीक एक @main घोषणा रखें। उदाहरण के सभी लेबल काल्पनिक हैं और सभी इंटरैक्शन ऐप की अपनी विंडो के भीतर होते हैं।

import SwiftUI
import Combine
import Foundation

@MainActor
final class LabelItem: ObservableObject, Identifiable {
    let id = UUID()
    @Published var title: String
    @Published var isEnabled: Bool

    init(title: String, isEnabled: Bool = true) {
        self.title = title
        self.isEnabled = isEnabled
    }
}

@MainActor
final class LabelStore: ObservableObject {
    @Published var items: [LabelItem] = [
        LabelItem(title: "Amber"),
        LabelItem(title: "Cobalt", isEnabled: false)
    ]

    func reviseFirstItem() {
        guard let first = items.first else { return }
        first.title = first.title == "Amber" ? "Copper" : "Amber"
        first.isEnabled.toggle()
    }

    func addItem() {
        items.append(LabelItem(title: "New label"))
    }

    func removeLastItem() {
        guard !items.isEmpty else { return }
        items.removeLast()
    }
}

@MainActor
struct LabelRow: View {
    @ObservedObject var item: LabelItem

    var body: some View {
        HStack {
            TextField("Label", text: $item.title)
                .textFieldStyle(.roundedBorder)
                .frame(minWidth: 120)

            Text(item.title)
                .frame(minWidth: 100, alignment: .leading)

            Toggle("Enabled", isOn: $item.isEnabled)
        }
        .padding(.vertical, 4)
    }
}

@MainActor
struct LabelListView: View {
    @StateObject private var store = LabelStore()

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            HStack {
                Button("Change first label") {
                    store.reviseFirstItem()
                }
                .disabled(store.items.isEmpty)

                Button("Add label") {
                    store.addItem()
                }

                Button("Remove last") {
                    store.removeLastItem()
                }
                .disabled(store.items.isEmpty)
            }

            Text("Rows: \(store.items.count)")

            List(store.items) { item in
                LabelRow(item: item)
            }
        }
        .padding()
        .frame(minWidth: 540, minHeight: 300)
    }
}

@main
@MainActor
struct LabelRowsApp: App {
    var body: some Scene {
        WindowGroup {
            LabelListView()
        }
    }
}

स्टोर आइटम इंस्टेंस का स्वामी है। LabelListView @StateObject का उपयोग करके स्टोर बनाता और उसे बनाए रखता है; पंक्तियों को मौजूदा इंस्टेंस @ObservedObject के ज़रिए मिलते हैं। Apple बताता है कि स्टेट ऑब्जेक्ट, उसे घोषित करने वाले कंटेनर की पहचान के जीवनकाल तक बना रहता है, और सामान्यतः अवलोकित ऑब्जेक्ट नया डिफ़ॉल्ट मान बनाकर देने के बजाय इनपुट होना चाहिए। StateObject, ObservedObject।

टेक्स्ट फ़ील्ड और टॉगल, आइटम की प्रकाशित प्रॉपर्टियों के बाइंडिंग का उपयोग करते हैं। साथ का टेक्स्ट वही शीर्षक पढ़ता है, ताकि आपको दिखे कि संपादन और प्रदर्शन एक ही मॉडल साझा करते हैं। @MainActor इस उदाहरण के मॉडल और इंटरफ़ेस के काम को मुख्य actor पर रखता है; Apple बताता है कि इसका एक्ज़ीक्यूटर मुख्य डिस्पैच क्यू के समकक्ष है। ObservedObject, MainActor।

इस प्रोग्राम की दस्तावेज़ीकृत API के आधार पर समीक्षा की गई, लेकिन इसे यहाँ कंपाइल नहीं किया गया: उपलब्ध वातावरण में Swift टूलचेन नहीं है। अपने डिप्लॉयमेंट टार्गेट पर व्यवहार जाँचने के लिए इसे Xcode में बिल्ड करके चलाएँ।

आइटम संपादनों को संग्रह बदलावों से अलग जाँचें

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

  1. ऐप लॉन्च करें। इसमें शुरुआत में Amber और Cobalt नाम वाली दो पंक्तियाँ दिखनी चाहिए।
  2. Change first label पर क्लिक करें। पहली पंक्ति में Copper दिखना चाहिए और उसका Enabled टॉगल बदलना चाहिए। पंक्तियों की संख्या दो ही रहनी चाहिए।
  3. पहली पंक्ति के Label फ़ील्ड में संपादन करें। साथ का टेक्स्ट वही शीर्षक दिखाए।
  4. Add label पर क्लिक करें। New label नाम की तीसरी पंक्ति दिखनी चाहिए और संख्या बढ़नी चाहिए।
  5. सूची खाली होने तक Remove last पर क्लिक करें। हटाने और पहले लेबल वाले बटन अक्षम हो जाने चाहिए; लेबल जोड़ने का विकल्प उपलब्ध रहना चाहिए।

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

अगर आइटम जोड़ना काम करता है, लेकिन शीर्षक बदलने पर पंक्ति अपडेट नहीं होती, तो उस पंक्ति में शीर्षक पढ़ने वाली प्रॉपर्टी की घोषणा देखें। इस आर्किटेक्चर में उसे आइटम @ObservedObject के ज़रिए मिलना चाहिए और बदल सकने वाला शीर्षक @Published होना चाहिए। प्रकाशन उस प्रॉपर्टी से जुड़ा होता है जिस पर वह रैपर लगा है; यह पूरे ऑब्जेक्ट ग्राफ़ पर लागू होने वाली पुनरावर्ती घोषणा नहीं है। Published, ObservedObject।

बची हुई समस्याओं का उपयोगी क्रम में निदान करें

पुष्टि करें कि लिखने वाला कोड और पंक्ति एक ही इंस्टेंस साझा करते हैं

LabelRow(item:) को पास किए गए ऑब्जेक्ट और क्रिया द्वारा बदले गए ऑब्जेक्ट का पता लगाएँ। उसी शीर्षक वाला एक और LabelItem बनाने से अलग इंस्टेंस बनता है। इसी तरह, शीर्षक को स्थानीय पंक्ति स्टेट में कॉपी करने से सिंक्रोनाइज़ करने के लिए एक और मान बन जाता है। इस संपादक के लिए, जब तक जानबूझकर ड्राफ़्ट बनाकर बाद में कमिट करने का वर्कफ़्लो आवश्यक न हो, सीधे साझा आइटम से बाइंड करें। यह मानों की प्रतिलिपि बनाने और क्लास संदर्भ साझा करने के बीच Swift के दस्तावेज़ीकृत अंतर के अनुरूप है। Swift में मान और संदर्भ प्रकार।

सामग्री बदलने पर पहचान स्थिर रखें

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

रूटीन रीफ़्रेश उपाय के रूप में बदलता हुआ .id(UUID()) जोड़ने से बचें। Apple बताता है कि दृश्य की पहचान बदलने पर उसका स्टेट रीसेट होता है, जिसमें फ़ोकस स्टेट भी शामिल है। संपादित शीर्षक दिखाने के लिए पंक्ति को फिर से बनाना, सामग्री के संपादन में एक अलग जीवनचक्र बदलाव जोड़ता है; पहले अवलोकन पथ स्थापित करें। StateObject।

पैरेंट के सारांशों को अतिरिक्त उपभोक्ता मानें

मान लें कि आप सूची के ऊपर सक्षम आइटमों की संख्या जोड़ते हैं। यहाँ दिखाए गए पुराने डिज़ाइन में LabelStore का अवलोकन करने से पैरेंट को हर आइटम के isEnabled प्रकाशक का अवलोकन अपने आप नहीं मिलता। इसलिए केवल पंक्तियों को ठीक करने से उस सारांश की निर्भरताएँ स्थापित नहीं होतीं।

इसके अपडेट डिज़ाइन का चुनाव स्पष्ट रूप से करें: स्टोर में चाइल्ड नोटिफ़िकेशन एकत्र करें, ऐसे मान प्रकार के आइटम इस्तेमाल करें जिनके संपादन प्रकाशित संग्रह के ज़रिए वापस लिखे जाएँ, या डिप्लॉयमेंट टार्गेट अनुमति दे तो Observation अपनाएँ। संग्रह और आइटम के अंतर से निकलने वाले ये आर्किटेक्चर विकल्प हैं; उदाहरण में केवल प्रत्यक्ष पंक्ति अवलोकन लागू है। Apple की माइग्रेशन मार्गदर्शिका नए सिस्टम की संग्रहों और किसी दृश्य द्वारा वास्तव में पढ़ी गई प्रॉपर्टियों को ट्रैक करने की क्षमता बताती है। Observable Object प्रोटोकॉल से Observable मैक्रो पर माइग्रेट करना।

अगर आप प्रकाशक-आधारित एकत्रीकरण लागू करते हैं, तो नोटिफ़िकेशन के समय का ध्यान रखें। Apple के अनुसार @Published, willSet के दौरान प्रकाशित करता है: सब्सक्राइबर को आने वाला मान, सहेजी गई प्रॉपर्टी बदलने से पहले मिलता है। उस कॉलबैक के भीतर प्रॉपर्टी को दोबारा पढ़ने पर उसका पिछला मान मिल सकता है। उस घटना से गणना करते समय प्राप्त मान का उपयोग करें। Published।

macOS 14 या बाद के संस्करण पर Observation पर कब स्विच करना चाहिए?

macOS 14 या उसके बाद का न्यूनतम डिप्लॉयमेंट टार्गेट होने पर SwiftUI, Observation सिस्टम का समर्थन करता है। @Observable मॉडल के साथ, SwiftUI दृश्य के मूल्यांकन के दौरान पढ़ी गई अवलोकनीय प्रॉपर्टियों को ट्रैक करता है। Apple की मार्गदर्शिका खास तौर पर अवलोकनीय ऑब्जेक्टों के संग्रह का उदाहरण देती है, जिनके अलग-अलग शीर्षक बदलने पर दिखाए गए शीर्षक अपडेट होते हैं। अपने ऐप में मॉडल डेटा प्रबंधित करना।

इस उदाहरण को माइग्रेट करने के लिए, LabelItem और LabelStore दोनों को import Observation के साथ @Observable का उपयोग करने के लिए बदलें, उनके ObservableObject अनुरूपण हटाएँ और @Published निकाल दें। स्वामी की स्टोर प्रॉपर्टी को @StateObject से बदलकर @State करें। इस पंक्ति में संपादन योग्य कंट्रोल हैं, इसलिए इसके आने वाले आइटम के रैपर को @ObservedObject से बदलकर @Bindable करें। ये बदलाव Apple के दस्तावेज़ीकृत माइग्रेशन पथ के अनुरूप हैं। Observable Object प्रोटोकॉल से Observable मैक्रो पर माइग्रेट करना।

केवल प्रदर्शन करने वाली पंक्ति किसी अवलोकनीय आइटम को साधारण सहेजी गई प्रॉपर्टी के ज़रिए प्राप्त कर सकती है और body में उसका शीर्षक पढ़ सकती है। @Bindable, TextField और Toggle जैसे कंट्रोलों के लिए ज़रूरी बाइंडिंग देता है; इसका उद्देश्य @ObservedObject से अलग है। केवल नए Observation सिस्टम का उपयोग करने वाले मॉडल को @ObservedObject में रैप न करें; इसकी जेनेरिक सीमा के लिए ObservableObject आवश्यक है। Bindable, ObservedObject।

निर्णय सीधा है: मौजूदा Combine मॉडल को उसकी वर्तमान डिप्लॉयमेंट सीमा के साथ ठीक करने के लिए अवलोकित-पंक्ति वाला समाधान रखें; अगर macOS 14 न्यूनतम संस्करण के रूप में स्वीकार्य है और प्रॉपर्टी-स्तर पर निर्भरता ट्रैक करना बाकी इंटरफ़ेस के अनुकूल है, तो Observation पर विचार करें।

अगर पंक्ति में AppKit टेक्स्ट व्यू हो तो क्या बदलता है?

SwiftUI उदाहरण मानक कंट्रोलों का उपयोग करता है और इसे कस्टम TextKit एकीकरण की ज़रूरत नहीं है। अगर आपकी अपनी पंक्ति पहले से NSViewRepresentable के साथ किसी AppKit दृश्य को रैप करती है, तो मॉडल अवलोकन की पुष्टि के बाद दूसरी सीमा भी जाँचें: Apple निर्देश देता है कि संबंधित SwiftUI स्टेट बदलने पर AppKit दृश्य का कॉन्फ़िगरेशन updateNSView(_:context:) में अपडेट करें। मूल दृश्य को एक बार बनाना आगे होने वाले कॉन्फ़िगरेशन अपडेट लागू नहीं करता। updateNSView(_:context:)।

एम्बेड किए गए NSTextView के लिए, इस मॉडल-से-दृश्य सिंक्रोनाइज़ेशन को टेक्स्ट लेआउट से अलग समझें। Apple, NSTextView को AppKit टेक्स्ट सिस्टम का फ़्रंट एंड बताता है और macOS 12 या बाद के संस्करण में इसकी layoutManager प्रॉपर्टी को स्पष्ट रूप से ऐक्सेस किए जाने पर एक कम्पैटिबिलिटी स्विच का दस्तावेज़ देता है। पंक्ति का अवलोकन कनेक्शन न मिलने की समस्या जाँचते समय लेआउट इंजन में बदलाव न जोड़ें। NSTextView।

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

स्रोत