एक NSTextFieldDelegate असाइन करें, controlTextDidEndEditing(_:) लागू करें, और फ़ील्ड के stringValue को SwiftUI बाइंडिंग में कॉपी करें। एक कोऑर्डिनेटर उस कॉलबैक को व्यू की स्थिति से जोड़ता है, ताकि संपादन पूरा होने के बाद लेबल अपडेट हो सके।

यह ट्यूटोरियल एक छोटा Mac ऐप बनाता है, जिसमें दो संपादन योग्य लेबल और एक सारांश है जो उनके पक्के किए गए मान दिखाता है। काम यह है कि किसी के टाइप करते समय सारांश अपरिवर्तित रहे, फिर AppKit का संपादन सत्र पूरा होने पर अपडेट हो। Apple दस्तावेज़ित करता है कि नियंत्रण द्वारा अपने बदलाव पक्के करने के बाद संपादन-समाप्ति कॉलबैक होता है। Apple का controlTextDidEndEditing(_:) दस्तावेज़

कौन-सा इवेंट व्यू को अपडेट करे?

मौजूदा NSTextField के लिए, उसके कंट्रोल-संपादन डेलीगेट कॉलबैक का उपयोग करें। डेलीगेट को एक सूचना मिलती है जिसका object वह नियंत्रण होता है जिसने इसे भेजा है, जिससे हैंडलर फ़ील्ड की पहचान करके उसका मान पढ़ सकता है। Apple का textDidEndEditingNotification दस्तावेज़

इंटरफ़ेस के आवश्यक व्यवहार के अनुसार इवेंट चुनें:

वांछित व्यवहार उपयुक्त तरीका
AppKit टेक्स्ट फ़ील्ड का संपादन पूरा होने पर पक्के किए गए मान को अपडेट करना उसके डेलीगेट पर controlTextDidEndEditing(_:) लागू करें।
संपादन पूरा होने पर मौजूदा AppKit टार्गेट-एक्शन हैंडलर भेजना सेल की sendsActionOnEndEditing प्रॉपर्टी कॉन्फ़िगर करें।
नेटिव SwiftUI TextField से सबमिशन संभालना उसके दस्तावेज़ित सबमिशन व्यवहार के लिए .onSubmit जोड़ें।

इन तरीकों के अनुबंध अलग हैं। Apple दस्तावेज़ित करता है कि sendsActionOnEndEditing सक्षम करने पर Return दबाने, फ़ील्ड से Tab करके बाहर जाने, या किसी अन्य टेक्स्ट फ़ील्ड में क्लिक करने के बाद ऐक्शन भेजा जाता है; इसे अक्षम करने पर ऐक्शन केवल Return दबाने पर भेजा जाता है। SwiftUI दस्तावेज़ित करता है कि TextField पर .onSubmit Return दबाने पर प्रतिक्रिया देता है। इसलिए यह एम्बेड किए गए AppKit फ़ील्ड के लिए फ़ोकस छूटने पर सामान्य कॉलबैक की गारंटी नहीं देता। Apple का sendsActionOnEndEditing दस्तावेज़, Apple का onSubmit(of:_:) दस्तावेज़

इस ऐप में, डेलीगेट ही फ़ील्ड के टेक्स्ट को पक्का करने की एकमात्र जगह है। अगर बाद में आप टार्गेट-एक्शन हैंडलर भी जोड़ते हैं, तो दोनों हैंडलरों में एक ही कमिट साइड इफ़ेक्ट न करें।

SwiftUI और AppKit का पूरा उदाहरण बनाएँ

Swift और SwiftUI इंटरफ़ेस का उपयोग करके Xcode में macOS ऐप प्रोजेक्ट बनाएँ। इस उदाहरण के लिए हाल का Swift 6-सक्षम टूलचेन इस्तेमाल करें और डिप्लॉयमेंट टार्गेट macOS 13 या उसके बाद का रखें। बनाई गई ऐप एंट्री फ़ाइल को नीचे दिए पूरे प्रोग्राम से बदलें, और यदि बनाई गई ContentView.swift में कोई दूसरा ContentView घोषित है तो उसे हटाएँ। ठीक एक @main एंट्री पॉइंट रखें। Apple का App दस्तावेज़ इस एंट्री-पॉइंट संरचना का वर्णन करता है। Apple का App दस्तावेज़

उदाहरण का macOS 13 टार्गेट प्रोजेक्ट का चुनाव है, ब्रिज के पहली बार आने का संस्करण नहीं: Apple के अनुसार NSViewRepresentable macOS 10.15 से उपलब्ध है। AppKit के कारण यह कार्यान्वयन macOS टार्गेट के लिए विशिष्ट है। Apple का NSViewRepresentable दस्तावेज़

import SwiftUI
import AppKit

@main
@MainActor
struct EditingDemoApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

@MainActor
struct ContentView: View {
    @State private var cardTitle = "Blue Card"
    @State private var shelfLabel = "Shelf A"

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Card title")
            CommitTextField(value: $cardTitle, label: "Card title")
                .frame(height: 24)

            Text("Shelf label")
            CommitTextField(value: $shelfLabel, label: "Shelf label")
                .frame(height: 24)

            Divider()

            Text("Committed values")
                .font(.headline)
            Text("Card: \(cardTitle)")
            Text("Shelf: \(shelfLabel)")
        }
        .padding(20)
        .frame(minWidth: 420)
    }
}

@MainActor
struct CommitTextField: NSViewRepresentable {
    @Binding var value: String
    let label: String

    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }

    func makeNSView(context: Context) -> NSTextField {
        let field = NSTextField(string: value)
        field.placeholderString = label
        field.delegate = context.coordinator
        return field
    }

    func updateNSView(_ field: NSTextField, context: Context) {
        context.coordinator.parent = self
        field.placeholderString = label
        field.isEnabled = context.environment.isEnabled

        // Leave the active editing buffer alone.
        if field.currentEditor() == nil, field.stringValue != value {
            field.stringValue = value
        }
    }

    static func dismantleNSView(
        _ field: NSTextField,
        coordinator: Coordinator
    ) {
        field.delegate = nil
    }

    @MainActor
    final class Coordinator: NSObject, NSTextFieldDelegate {
        var parent: CommitTextField

        init(parent: CommitTextField) {
            self.parent = parent
            super.init()
        }

        func controlTextDidEndEditing(_ notification: Notification) {
            guard let field = notification.object as? NSTextField else {
                return
            }

            let committedValue = field.stringValue
            if parent.value != committedValue {
                parent.value = committedValue
            }
        }
    }
}

यह कृत्रिम स्थानीय मानों वाला ऐप का पूरा उदाहरण है। इसे प्राप्त API दस्तावेज़ों के आधार पर जाँचा गया, लेकिन यहाँ कंपाइल या चलाया नहीं गया: उपलब्ध निष्पादन परिवेश Linux है और इसमें Swift टूलचेन या macOS SDK नहीं है। इसे Mac पर Xcode में बनाएँ और चलाकर देखें।

मान सारांश तक कैसे पहुँचता है

हर पैरेंट @State मान अपने फ़ील्ड रैपर को बाइंडिंग देता है। संपादन समाप्त होने पर कोऑर्डिनेटर उस बाइंडिंग के माध्यम से मान लिखता है। फिर SwiftUI सारांश को अपडेट करता है क्योंकि वह बदली हुई स्थिति को पढ़ता है। Apple स्थिति के आधार पर व्यू अपडेट होने और चाइल्ड व्यू को पैरेंट के स्वामित्व वाली स्थिति बदलने देने के लिए बाइंडिंग के उपयोग—दोनों का दस्तावेज़ीकरण करता है। Apple का State दस्तावेज़

makeNSView संपादन योग्य AppKit नियंत्रण बनाता है और कोऑर्डिनेटर को उसका डेलीगेट असाइन करता है। updateNSView SwiftUI से उस नियंत्रण तक आने वाले बदलावों को संभालता है। डेलीगेट कॉलबैक विपरीत दिशा को संभालता है। AppKit व्यू होस्ट करने के लिए Apple के इंटीग्रेशन मार्गदर्शन में भी यही कोऑर्डिनेटर और बाइंडिंग व्यवस्था इस्तेमाल होती है। Apple का “Use SwiftUI with AppKit” सत्र

असाइनमेंट context.coordinator.parent = self कोऑर्डिनेटर में संग्रहित representable को मौजूदा रखता है। होस्ट किए गए व्यू के जीवनकाल तक कोऑर्डिनेटर बना रहता है, जबकि representable के मान बदल सकते हैं। Apple updateNSView के दौरान उस संग्रहित मान को रीफ़्रेश करने का स्पष्ट उदाहरण देता है। Apple की कोऑर्डिनेटर जीवनचक्र व्याख्या

SwiftUI अपडेट के दौरान ड्राफ़्ट को सुरक्षित रखें

मुख्य गार्ड field.currentEditor() == nil है। यह उदाहरण जानबूझकर अधूरे टेक्स्ट को AppKit में रखता है, जबकि SwiftUI अंतिम पक्का किया हुआ मान बनाए रखता है। संपादन गार्ड के बिना, SwiftUI अपडेट के दौरान पक्का किया हुआ मान फ़ील्ड में कॉपी करने से अधूरा ड्राफ़्ट बदल सकता है।

Apple दस्तावेज़ित करता है कि currentEditor() नियंत्रण का फ़ील्ड एडिटर लौटाता है, या यदि कोई फ़ील्ड एडिटर न हो तो nil लौटाता है। फ़ील्ड एडिटर एक NSTextView है जिसे विंडो के नियंत्रण साझा करते हैं। इसीलिए यह जाँचना कि विंडो का firstResponder NSTextField के बराबर है या नहीं, नियंत्रण से उसके एडिटर के बारे में पूछने का अच्छा विकल्प नहीं है। Apple का currentEditor() दस्तावेज़

इससे मिलने वाला व्यवहार एक स्पष्ट डिज़ाइन नीति है:

स्थिति उदाहरण का व्यवहार
फ़ील्ड निष्क्रिय है और उसकी बाइंडिंग बदलती है अगले representable अपडेट पर बाइंडिंग को फ़ील्ड में कॉपी करें।
फ़ील्ड का एडिटर सक्रिय है संपादन बफ़र सुरक्षित रखें।
अलग टेक्स्ट के साथ संपादन समाप्त होता है पूरा हुआ टेक्स्ट बाइंडिंग में लिखें।
बिना बदलाव के संपादन समाप्त होता है अनावश्यक स्थिति असाइनमेंट छोड़ दें।

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

इस काम के लिए TextKit लेआउट को कस्टमाइज़ करने की ज़रूरत नहीं है। यहाँ प्रासंगिक सीमा नियंत्रण का संपादन सत्र और उसका फ़ील्ड एडिटर है। Apple की टेक्स्ट आर्किटेक्चर गाइड NSTextField, उसके डेलीगेट और साझा NSTextView फ़ील्ड एडिटर के बीच संबंध समझाती है; वह गाइड संग्रहित है, जबकि मौजूदा currentEditor() संदर्भ साझा-एडिटर मॉडल की पुष्टि करता है। Apple की “Text Editing” गाइड, Apple का मौजूदा फ़ील्ड-एडिटर संदर्भ

ऐप में संपादन-समाप्ति व्यवहार जाँचें

ऐप बनाने के बाद उदाहरण जाँचने के लिए यह क्रम अपनाएँ। ये कार्यान्वयन और दस्तावेज़ित संपादन व्यवहार से निकली अपेक्षित जाँचें हैं, चलाने के बताए गए परिणाम नहीं।

  1. Blue Card को Green Card से बदलें। टाइप करते समय सारांश में अभी भी Card: Blue Card दिखना चाहिए।
  2. Return दबाएँ। सारांश बदलकर Card: Green Card होना चाहिए।
  3. कार्ड का शीर्षक फिर संपादित करें, फिर Shelf label फ़ील्ड में क्लिक करें। पूरा हुआ कार्ड शीर्षक सारांश में दिखना चाहिए।
  4. शेल्फ़ का मान संपादित करें, फिर Tab दबाएँ। पुष्टि करें कि शेल्फ़ का सारांश अपडेट होता है और कीबोर्ड नेविगेशन देखें।
  5. खाली मान पक्का करें। संबंधित सारांश में बिना किसी मान के उसका उपसर्ग दिखना चाहिए; उदाहरण खाली स्ट्रिंग स्वीकार करता है।
  6. टेक्स्ट बदले बिना संपादन पूरा करें। इक्वैलिटी जाँच को दूसरा स्थिति असाइनमेंट रोकना चाहिए।

Return आपके अनुमानित फ़ोकस बदलाव के बिना भी संपादन पूरा कर सकता है। Apple दस्तावेज़ित करता है कि संपादन-समाप्ति सूचना के बाद NSTextField अपना ऐक्शन और Return से जुड़े अन्य हैंडलिंग प्रयास करता है, और संभव है कि उसका टेक्स्ट चुन ले। Tab और Shift-Tab अगले या पिछले की-व्यू पर जाने का प्रयास करते हैं। इंटरफ़ेस जाँचते समय संपादन पूरा होने और फ़ोकस कहाँ जाने को अलग-अलग व्यवहार मानें। Apple का textDidEndEditing(_:) दस्तावेज़

कॉलबैक न आने पर समस्या का पता लगाएँ

पहले पुष्टि करें कि field.delegate अब भी कोऑर्डिनेटर को संदर्भित करता है। यह प्रॉपर्टी वीक है, इसलिए अलग स्वामी के बिना अस्थायी डेलीगेट ऑब्जेक्ट उपलब्ध नहीं रहेगा। यहाँ representable कोऑर्डिनेटर आवश्यक जीवनकाल प्रदान करता है। Apple का delegate दस्तावेज़, Apple की कोऑर्डिनेटर जीवनचक्र व्याख्या

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

यदि आपके ऐप में वैलिडेशन जोड़ा गया है, तो control(_:textShouldEndEditing:) जाँचें। इसका दस्तावेज़ित रिटर्न मान तय करता है कि संपादन समाप्त हो सकता है या नहीं। फ़ील्ड छोड़ने की अस्वीकृत कोशिश को सफल कमिट न मानें। Apple का control(_:textShouldEndEditing:) दस्तावेज़

रद्द करने के रास्ते भी देखें। Apple स्पष्ट रूप से कहता है कि लंबित संपादन छोड़ने के लिए abortEditing() को कॉल करने पर controlTextDidEndEditing(_:) लागू नहीं होता। इसलिए रद्द करने के ऑपरेशन के लिए स्थिति का अपना सोच-समझकर तय किया गया व्यवहार चाहिए। Apple का संपादन-समाप्ति कॉलबैक दस्तावेज़

ऐप के स्पष्ट कमांड से संपादन पूरा करने का अनुरोध करें

भविष्य के ऐसे Done कमांड के लिए, जिसके पास इस फ़ील्ड का संदर्भ हो, समर्थित रिस्पॉन्डर तरीका यह है कि उसकी अपनी विंडो से makeFirstResponder(nil) कॉल करने को कहें। आगे बढ़ने से पहले लौटाए गए Boolean को जाँचें: Apple दस्तावेज़ित करता है कि मौजूदा रिस्पॉन्डर रिसाइन करने से इनकार कर सकता है, जिससे यह मेथड false लौटाता है। इस एडिटर की वास्तविक विंडो इस्तेमाल करें; यह न मानें कि ऐप-वाइड की विंडो इसी एडिटर की है। Apple का makeFirstResponder(_:) दस्तावेज़

इस ब्रिज के AppKit ऐक्सेस और UI कॉलबैक मुख्य ऐक्टर पर रखें। मौजूदा डेलीगेट घोषणा मुख्य-ऐक्टर से पृथक है, और Swift दस्तावेज़ित करता है कि MainActor ऐसा एक्ज़ीक्यूटर इस्तेमाल करता है जो मुख्य डिस्पैच क्यू के समकक्ष है। उदाहरण यह पृथक्करण सीधे @MainActor से व्यक्त करता है; यह संपादन पूरा होने और बाइंडिंग में लिखने के बीच कोई स्थगित टास्क नहीं जोड़ता। Apple की कॉलबैक घोषणा, Swift का MainActor दस्तावेज़

कार्यान्वयन में कमिट की एक स्पष्ट सीमा है: AppKit संपादन पूरा करता है, कोऑर्डिनेटर फ़ील्ड पढ़ता है, और बाइंडिंग उस पर निर्भर SwiftUI टेक्स्ट को अपडेट करती है। representable अपडेट के दौरान ड्राफ़्ट सुरक्षित रखने से यह सीमा रोज़मर्रा के लेबल संपादन के लिए उपयोगी बनती है और कस्टम टेक्स्ट-लेआउट सिस्टम की ज़रूरत नहीं पड़ती।

स्रोत