प्रोग्रामेटिक रूप से बनाए गए NSTextView में टाइपिंग सक्षम करने के लिए, संपादन की अनुमति दें, उसे अपनी विंडो से जोड़ें और उस विंडो का makeFirstResponder(_:) कॉल करें। विंडो key बनने में भी सक्षम होनी चाहिए। यहाँ पूरा उदाहरण और समस्या-निवारण के लिए केंद्रित चरण दिए गए हैं।

टाइपिंग काम करने से पहले किन बातों का सही होना ज़रूरी है?

यह मार्गदर्शिका एक काम पर केंद्रित है: एक छोटी Mac ऐप विंडो बनाना जिसमें व्यक्ति कई पंक्तियों वाला पाठ टाइप और संपादित कर सके। इसमें सीधे AppKit का उपयोग किया गया है, फिर SwiftUI में एडिटर एम्बेड करते समय जीवनचक्र से जुड़े विकल्पों की व्याख्या की गई है।

तीन अलग-अलग शर्तें मायने रखती हैं। टेक्स्ट व्यू को संपादन की अनुमति होनी चाहिए, विंडो को कीबोर्ड इवेंट प्राप्त होने चाहिए, और उस विंडो के भीतर टेक्स्ट व्यू ही इच्छित first responder होना चाहिए। Apple ऐप के keyWindow को उस विंडो के रूप में परिभाषित करता है जो इस समय कीबोर्ड इवेंट प्राप्त कर रही है; केवल विंडो दिखाने से यह तय नहीं होता कि किस व्यू में पाठ संपादित होना चाहिए। Apple: keyWindow

isEditable = true सेट करने से संपादन की अनुमति मिलती है और टेक्स्ट व्यू को चुना भी जा सकता है। Apple के दस्तावेज़ के अनुसार टेक्स्ट व्यू डिफ़ॉल्ट रूप से संपादन योग्य होते हैं, लेकिन इसे स्पष्ट रूप से सेट करने से उदाहरण का कॉन्फ़िगरेशन साफ़ रहता है। केवल संपादन की अनुमति देने से कीबोर्ड का लक्ष्य तय नहीं होता। Apple: isEditable

फ़ोकस के लिए, कंटेनर विंडो का makeFirstResponder(textView) उपयोग करें। Apple स्पष्ट रूप से डेवलपरों को टेक्स्ट व्यू का becomeFirstResponder() सीधे कॉल करने के बजाय यह मेथड इस्तेमाल करने का निर्देश देता है। इससे विंडो responders के बीच बदलाव का समन्वय कर पाती है। Apple: NSTextView

Swift में पूरी संपादन योग्य विंडो बनाएँ

नीचे दिया गया स्वतंत्र प्रोग्राम macOS 14 या उसके बाद के संस्करण के लिए है और वर्तमान Swift टूलचेन तथा macOS SDK का उपयोग करता है। इसकी उदाहरणार्थ विंडो का शीर्षक Local Editor है और शुरुआती पाठ Sample note है। पाठ मेमोरी में रहता है; इस उदाहरण में सहेजने की सुविधा नहीं है।

पूरा प्रोग्राम EditorDemo.swift नाम से सहेजें। इसे स्वतंत्र एक्ज़ीक्यूटेबल के रूप में उपयोग करें, किसी मौजूदा ऐप में दूसरा एंट्री पॉइंट जोड़ने के लिए नहीं। Swift में केवल एक प्रोग्राम एंट्री पॉइंट हो सकता है; नीचे दिया गया @main टाइप वही उपलब्ध कराता है। The Swift Programming Language: Attributes

import AppKit

@MainActor
final class EditorDelegate: NSObject, NSApplicationDelegate {
    private var editorWindow: NSWindow?

    func applicationDidFinishLaunching(_ notification: Notification) {
        let window = NSWindow(
            contentRect: NSRect(x: 0, y: 0, width: 640, height: 420),
            styleMask: [.titled, .closable, .miniaturizable, .resizable],
            backing: .buffered,
            defer: false
        )
        window.title = "Local Editor"
        window.isReleasedWhenClosed = false

        let scrollView = NSTextView.scrollableTextView()
        guard let textView = scrollView.documentView as? NSTextView else {
            fatalError("Expected an NSTextView document view")
        }

        textView.isEditable = true
        textView.isSelectable = true
        textView.isRichText = false
        textView.allowsUndo = true
        textView.font = NSFont.systemFont(ofSize: 16)
        textView.textContainerInset = NSSize(width: 12, height: 12)
        textView.string = "Sample note\n"
        textView.setSelectedRange(
            NSRange(location: (textView.string as NSString).length, length: 0)
        )

        scrollView.frame = NSRect(x: 0, y: 0, width: 640, height: 420)
        scrollView.autoresizingMask = [.width, .height]
        window.contentView = scrollView
        window.initialFirstResponder = textView
        editorWindow = window

        let mainMenu = NSMenu()
        let appMenuItem = NSMenuItem()
        mainMenu.addItem(appMenuItem)
        let appMenu = NSMenu()
        appMenu.addItem(NSMenuItem(
            title: "Quit Local Editor",
            action: #selector(NSApplication.terminate(_:)),
            keyEquivalent: "q"
        ))
        appMenuItem.submenu = appMenu
        NSApp.mainMenu = mainMenu

        window.center()
        window.makeKeyAndOrderFront(nil)
        NSApp.activate()

        guard textView.window === window,
              textView.acceptsFirstResponder else {
            print("Editor is not ready to receive focus")
            return
        }

        let accepted = window.makeFirstResponder(textView)
        print("Focus request returned:", accepted)
        print("Editor is first responder:", window.firstResponder === textView)
        print("Window is key:", window.isKeyWindow)
    }

    func applicationShouldTerminateAfterLastWindowClosed(
        _ sender: NSApplication
    ) -> Bool {
        true
    }
}

@main
@MainActor
enum EditorDemo {
    static func main() {
        let application = NSApplication.shared
        application.setActivationPolicy(.regular)
        let delegate = EditorDelegate()
        application.delegate = delegate
        withExtendedLifetime(delegate) {
            application.run()
        }
    }
}

Xcode या Command Line Tools इंस्टॉल किए हुए Mac पर, उस डायरेक्टरी से इसे कंपाइल करके लॉन्च करें जिसमें फ़ाइल मौजूद है:

xcrun swiftc -parse-as-library \
  -target "$(uname -m)-apple-macos14.0" \
  -framework AppKit EditorDemo.swift -o EditorDemo
./EditorDemo

इस उदाहरण की जाँच प्राप्त दस्तावेज़ों के आधार पर की गई, लेकिन इस शोध परिवेश में इसे कंपाइल या चलाकर नहीं देखा जा सका; यह Linux पर चलता है और इसमें Swift कंपाइलर नहीं है। नीचे दिए गए चरणों से macOS पर विंडो का व्यवहार सत्यापित करें।

यह सेटअप कीबोर्ड इनपुट की समस्या कैसे हल करता है

सुविधाजनक मेथड scrollableTextView() एक NSScrollView लौटाता है; कोड documentView के ज़रिए उसका टेक्स्ट व्यू प्राप्त करता है। टेक्स्ट एडिटर को स्क्रॉल व्यू के भीतर रखने से इस छोटे उदाहरण में कई पंक्तियों वाली सामग्री के लिए एक सरल कंटेनर मिलता है। यह फ़ैक्टरी macOS 10.14 से उपलब्ध है। Apple: scrollableTextView()

कोड फ़ोकस का अनुरोध करने से पहले उस कंटेनर को जोड़ता है। यह initialFirstResponder भी सेट करता है, जिसे Apple विंडो पहली बार दिखने पर चुने जाने वाले व्यू के रूप में परिभाषित करता है। बाद में किया गया makeFirstResponder(_:) कॉल एडिटर को स्पष्ट रूप से चुनने का अनुरोध करता है और जाँचने के लिए परिणाम देता है। शुरुआती फ़ोकस और बाद का फ़ोकस अनुरोध, विंडो के जीवनचक्र के अलग-अलग चरणों के लिए हैं। Apple: initialFirstResponder, Apple: makeFirstResponder(_:)

विंडो में titled style है, ताकि वह key बन सके। makeKeyAndOrderFront(nil) विंडो दिखाने और key स्थिति पाने, दोनों का अनुरोध करता है। Apple इसे orderFront(_:) से अलग बताता है, जो key window बदले बिना विंडो का क्रम बदलता है। Apple: canBecomeKey, Apple: makeKeyAndOrderFront(_:)

activate() कॉल के कारण इस खास प्रोग्राम के लिए macOS 14 या उसके बाद का संस्करण आवश्यक है। Apple सक्रिय करने को गारंटी नहीं, बल्कि अनुरोध बताता है। यदि विंडो दिखे लेकिन उसमें टाइप न हो, तो उसकी विंडो पर क्लिक करके ऐप सक्रिय करें और फिर जाँचें। तुरंत छपे डायग्नोस्टिक संदेश को इस बात की गारंटी न मानें कि सक्रियण पूरा हो चुका है। Apple: activate()

एंट्री पॉइंट और डेलीगेट पर @MainActor लगा है, जिसका executor Apple मुख्य dispatch queue के समकक्ष परिभाषित करता है। सेटअप पूरा होने के बाद AppKit को उपयोगकर्ता इवेंट भेजने देने के लिए प्रोग्राम run() के साथ AppKit का इवेंट लूप भी शुरू करता है। Apple: MainActor, Apple: run()

दिखाई देने वाला एडिटर टाइपिंग स्वीकार न करे तो समस्या का पता लगाएँ

नीचे दिए गए चरणों को क्रम से अपनाएँ। इससे व्यू का जुड़ाव, विंडो की पात्रता, responder का चयन और संपादन की अनुमति अलग-अलग जाँची जाती है, इसलिए हर परिणाम से अगली कार्रवाई स्पष्ट होती है।

जाँच परिणाम का अर्थ अगली कार्रवाई
textView.window === window एडिटर इच्छित विंडो से जुड़ा है। यदि परिणाम false हो, तो फ़ोकस का अनुरोध करने से पहले व्यू hierarchy जोड़ना पूरा करें।
window.canBecomeKey विंडो key स्थिति पाने के योग्य है। यदि परिणाम false हो, तो उसकी style या custom subclass जाँचें।
window.isKeyWindow ऐप के भीतर यह विंडो इस समय key है। ऐप सक्रिय करें और इच्छित विंडो चुनें, फिर दोबारा जाँचें।
textView.acceptsFirstResponder एडिटर फ़ोकस अनुरोध स्वीकार करता है। यदि परिणाम false हो, तो कॉन्फ़िगरेशन और subclass overrides जाँचें।
window.firstResponder === textView यही एडिटर first-responder स्थिति में है। यदि परिणाम false हो, तो फ़ोकस बदलाव और मौजूदा responder जाँचें।
textView.isEditable उपयोगकर्ता के संपादन सक्षम हैं। यदि संपादन करना है, तो इसे true सेट करें।

Responder की जाँच खास तौर पर उपयोगी है, क्योंकि makeFirstResponder(_:) में एक सूक्ष्मता है: यदि अनुरोधित responder first-responder स्थिति लेने से मना कर दे और विंडो स्वयं first responder बन जाए, तो यह true लौटा सकता है। Apple यह भी कहता है कि स्पष्ट रूप से अनुरोध करने से पहले acceptsFirstResponder जाँचें। इसलिए लौटाए गए मान और उसके बाद बने firstResponder की पहचान, दोनों देखें। Apple: makeFirstResponder(_:)

false परिणाम का अर्थ हो सकता है कि मौजूदा first responder ने पद छोड़ने से मना कर दिया। उस responder के व्यवहार और संपादन से जुड़े किसी delegate की जाँच करें। उदाहरण के लिए, Apple दस्तावेज़ में बताया गया है कि टेक्स्ट व्यू का delegate textShouldEndEditing(_:) के ज़रिए संपादन समाप्त करने से मना कर सकता है। मना करने का कारण हल किए बिना वही फ़ोकस अनुरोध दोहराने से समस्या हल नहीं होगी। Apple: makeFirstResponder(_:), Apple: NSTextView

एक बुनियादी मैन्युअल जाँच के लिए, Blue square टाइप करें, Return दबाएँ, Green circle टाइप करें, फिर तीर कुंजियों और Delete से पाठ संपादित करें। विंडो का आकार बदलें और टाइप करना जारी रखें। ये प्रस्तावित सत्यापन चरण हैं, परीक्षण के बताए गए परिणाम नहीं। काम पूरा होने पर विंडो बंद करें; नमूना प्रोग्राम तब समाप्त हो जाता है।

बिना बॉर्डर वाली विंडो में क्या बदलता है?

बिना बॉर्डर वाली दिखाई देने वाली विंडो फिर भी key बनने में असमर्थ हो सकती है। Apple के दस्तावेज़ के अनुसार, titled style के बिना सामान्य NSWindow डिफ़ॉल्ट रूप से key नहीं बनती। समर्थित अनुकूलन यह है कि NSWindow subclass में canBecomeKey को override करें और जब उस विंडो को कीबोर्ड इनपुट प्राप्त करना हो, तब true लौटाएँ। Apple: canBecomeKey

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

SwiftUI के भीतर यही एडिटर कैसे काम करना चाहिए?

SwiftUI hierarchy में AppKit टेक्स्ट व्यू को जोड़ने के लिए NSViewRepresentable उपयोग करें। Apple के अनुसार, makeNSView(context:) बनाने और शुरुआती कॉन्फ़िगरेशन की ज़िम्मेदारी निभाता है, updateNSView(_:context:) मौजूदा स्थिति के अपडेट संभालता है, और coordinator delegate या target-action संदेशों का समन्वय करता है। Apple: NSViewRepresentable

इस एडिटर के लिए एक व्यावहारिक डिज़ाइन है कि scroll view को represented view के रूप में लौटाएँ और उसके document text view का संदर्भ coordinator या custom container में रखें। बनाते समय संपादन की अनुमति कॉन्फ़िगर करें। यदि आसपास के SwiftUI इंटरफ़ेस को binding चाहिए, तो delegate के ज़रिए उपयोगकर्ता के टेक्स्ट बदलाव आगे भेजें।

जुड़ाव को निर्माण से अलग घटना मानें। फ़ोकस का अनुरोध करने से पहले जाँचें कि एडिटर का window nil नहीं है। AppKit subclass hierarchy में बदलाव देखने के लिए viewDidMoveToWindow() का उपयोग कर सकती है; Apple दस्तावेज़ के अनुसार, वहाँ nil window का अर्थ है कि व्यू इस समय किसी विंडो में नहीं है। इससे जुड़ाव का ध्यान रखने वाला फ़ोकस अनुरोध लागू करने के लिए एक दस्तावेज़ित lifecycle hook मिलता है। Apple: viewDidMoveToWindow()

कार्यान्वयन नीति के रूप में, इच्छित प्रस्तुति के लिए शुरुआती फ़ोकस एक बार माँगें, या Focus Editor जैसी स्पष्ट कार्रवाई के जवाब में। हर SwiftUI अपडेट पर बिना शर्त makeFirstResponder(_:) कॉल करने से बचें: इससे व्यक्ति के किसी अन्य कंट्रोल को चुनने के बाद फ़ोकस बार-बार वापस चला जाएगा। अनुरोध के समय कंटेनर विंडो जाँचें; यह न मानें कि पहले संग्रहीत विंडो अब भी उचित है।

हर अपडेट पर एडिटर की string को रीसेट करने से भी बचें। binding-आधारित wrapper में, आने वाली स्थिति की मौजूदा टेक्स्ट से तुलना करें और सामग्री बदलने से पहले marked text तथा selection का ध्यान रखें। स्वतंत्र उदाहरण अपनी string को एक बार आरंभ करता है; समन्वयित एडिटर के लिए अपडेट की स्पष्ट नीति चाहिए।

SwiftUI को represented root view का आकार तय करने दें। Apple चेतावनी देता है कि उस व्यू का frame या bounds सीधे बदलना, SwiftUI के layout प्रबंधन से टकराता है। इसलिए स्वतंत्र AppKit प्रोग्राम में सीधे frame सेट करने का तरीका, SwiftUI द्वारा प्रबंधित root view पर लागू करने वाला आकार-निर्धारण पैटर्न नहीं है। Apple: NSViewRepresentable

क्या टाइपिंग स्वीकार करने के लिए custom TextKit stack चाहिए?

इस काम के लिए NSTextView के मौजूदा संपादन व्यवहार का उपयोग करें। Apple इसे टेक्स्ट सिस्टम का front end बताता है, जो पाठ बनाता है और चयन, संशोधन, इनपुट प्रबंधन तथा key bindings संभालता है। सामान्य टाइपिंग के लिए इन सुविधाओं को दोबारा बनाने की ज़रूरत नहीं है। Apple: NSTextView

TextKit इंजन का चुनाव कीबोर्ड फ़ोकस से अलग निर्णय है। Apple के दस्तावेज़ के अनुसार, init(frame:) डिफ़ॉल्ट रूप से NSLayoutManager का उपयोग करता है, जबकि हाथ से बनाया गया टेक्स्ट व्यू macOS 12 या उसके बाद के संस्करण में निर्धारित initializer के ज़रिए NSTextLayoutManager का उपयोग कर सकता है। अपनी layout आवश्यकताओं के आधार पर इंजन चुनें; इंजन बदलना एडिटर को first responder बनाने का विकल्प नहीं है। Apple: init(frame:textContainer:)

एक निदान पर ध्यान देना ज़रूरी है: layoutManager पढ़ना अनिवार्य रूप से निष्क्रिय निरीक्षण नहीं है। Apple के दस्तावेज़ के अनुसार, नए सिस्टम का उपयोग करने वाले टेक्स्ट व्यू या टेक्स्ट कंटेनर पर इसे एक्सेस करने से NSLayoutManager का उपयोग करने वाला compatibility mode शुरू हो सकता है। उदाहरण इसे क्वेरी करने से बचता है और यह मानकर नहीं चलता कि scrollable सुविधा फ़ैक्टरी कौन-सा इंजन चुनती है। Apple: NSTextView

काम करने वाला परिणाम स्पष्ट है: इच्छित ऐप विंडो सक्रिय और key है, उसका संपादन योग्य टेक्स्ट व्यू first responder है, और टाइपिंग से दिखाया गया पाठ बदलता है। पहले छोटी विंडो में यह व्यवहार स्थापित करें, फिर SwiftUI एकीकरण, custom layout या अतिरिक्त कंट्रोल जोड़ते समय वही जाँचें बनाए रखें।

स्रोत