अपने Mac ऐप के लिपटे NSTextView में इंसर्शन पॉइंट दिखाने के लिए, कीबोर्ड फ़ोकस वापस लाएँ और उसके मौजूदा चयन के साथ scrollRangeToVisible(_:) कॉल करें। कैरेट का रंग अलग से कॉन्फ़िगर करें। कैरेट को स्क्रीन पर बनाए रखना और उसकी ब्लिंकिंग रोकना, दोनों के लिए अलग-अलग नियंत्रण चाहिए।
यह गाइड Reveal insertion point बटन वाला एक छोटा स्थानीय एडिटर बनाती है। यह डेस्कटॉप पर होने वाले एक आम काम को संबोधित करती है: दस्तावेज़ में कहीं और स्क्रॉल करने के बाद अपनी संपादन स्थिति पर लौटना। इसका कार्यान्वयन मौजूदा चयन को बनाए रखता है और सामान्य टेक्स्ट इनपुट तथा कर्सर ड्रॉ करने का काम AppKit को सौंपता है।
पता लगाएँ कि इंसर्शन पॉइंट क्यों नहीं दिख रहा
दिखाई देने वाले लक्षण से शुरुआत करें। स्क्रॉलिंग, फ़ोकस, चयन और कर्सर की ऐनिमेशन एक जैसी लग सकती हैं, लेकिन हर स्थिति में अलग प्रतिक्रिया चाहिए।
| आपको क्या दिखता है | क्या जाँचें | उचित कार्रवाई |
|---|---|---|
| आपकी संपादन स्थिति दस्तावेज़ के दिखाई देने वाले हिस्से से बाहर है | टेक्स्ट व्यू का मौजूदा चयन | उस रेंज को दृश्य क्षेत्र में स्क्रॉल करें |
| किसी दूसरे कंट्रोल पर क्लिक करने से एडिटर में टाइपिंग रुक जाती है | विंडो का फ़र्स्ट रिस्पॉन्डर | अनुरोध किए जाने पर स्पष्ट रूप से फ़ोकस वापस लाएँ |
| कैरेट को बैकग्राउंड से अलग पहचानना मुश्किल है | कैरेट और बैकग्राउंड के रंग | टेक्स्ट व्यू के रंग गुण कॉन्फ़िगर करें |
| एक कैरेट दिखने के बजाय टेक्स्ट हाइलाइट होता है | क्या चयनित रेंज की लंबाई शून्य से अधिक है | चयन को बनाए रखें, जब तक उपयोगकर्ता उसे बदलने का अनुरोध न करे |
| एडिटर सक्रिय होने पर कैरेट गायब होकर फिर दिखता है | सामान्य कर्सर ऐनिमेशन | ब्लिंकिंग को स्क्रॉलिंग से अलग समझें |
Apple scrollRangeToVisible(_:) को इस तरह दस्तावेज़ित करता है कि यह किसी एनक्लोज़िंग स्क्रॉल व्यू को तब तक स्क्रॉल करता है, जब तक दी गई रेंज के शुरुआती वर्ण दिखाई न देने लगें। यह उस रेंज को बीच में लाने या किसी लंबे चयन का पूरा हिस्सा दिखाने की गारंटी नहीं देता। इसलिए यह खुद ग्लिफ़ के आयतों की गणना किए बिना इंसर्शन पॉइंट दिखाने के लिए उपयुक्त है। Apple का स्क्रॉलिंग दस्तावेज़।
फ़ोकस उस विंडो के नियंत्रण में होता है जिसमें व्यू मौजूद है। Apple स्पष्ट रूप से निर्देश देता है कि कॉलर टेक्स्ट व्यू के becomeFirstResponder() को सीधे कॉल करने के बजाय window.makeFirstResponder(textView) का उपयोग करें। नीचे दिया गया कार्यान्वयन इसी तरीके का पालन करता है और जाँचता है कि वास्तव में कौन-सा ऑब्जेक्ट फ़र्स्ट रिस्पॉन्डर बना। Apple का NSTextView दस्तावेज़, फ़र्स्ट-रिस्पॉन्डर ट्रांज़िशन का दस्तावेज़।
रिवील कमांड वाला स्थानीय एडिटर बनाएँ
Xcode में SwiftUI और Swift का उपयोग करके macOS App प्रोजेक्ट बनाएँ। इस उदाहरण के लिए डिप्लॉयमेंट टार्गेट macOS 13 या उसके बाद का रखें। जनरेट किए गए ऐप एंट्री पॉइंट को पहली फ़ाइल से बदलें और दूसरी फ़ाइल उसी ऐप टार्गेट में जोड़ें। ये दोनों फ़ाइलें मिलकर पूरा डेमो ऐप बनाती हैं।
यह उदाहरण नमूना टेक्स्ट का उपयोग करता है और उस व्यू के जीवनकाल तक संपादन AppKit व्यू में ही रखता है। इसमें दस्तावेज़ सहेजने का कार्यान्वयन या कोई बाहरी टेक्स्ट मॉडल नहीं है। ऐप बंद करने पर संपादन मिट जाते हैं। कोड को प्राप्त दस्तावेज़ के आधार पर जाँचा गया था; इसे यहाँ संकलित या चलाया नहीं गया, क्योंकि उपलब्ध परिवेश Linux है और इसमें AppKit SDK नहीं है।
SwiftUI विंडो बनाएँ
बटन एक अनुरोध संख्या बढ़ाता है। इससे रैपर को हर SwiftUI अपडेट पर एडिटर को दोबारा फ़ोकस या स्क्रॉल कराने के बजाय संसाधित करने के लिए एक विशिष्ट कमांड मिलता है।
// CaretDemoApp.swift
import SwiftUI
@main
struct CaretDemoApp: App {
var body: some Scene {
WindowGroup("Local Editor") {
EditorScreen()
}
}
}
struct EditorScreen: View {
@State private var revealRequest = 0
private let sampleText = (1...80)
.map { "Sample row \($0): blue square, green circle." }
.joined(separator: "\n")
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Button("Reveal insertion point") {
revealRequest += 1
}
CaretEditor(
initialText: sampleText,
revealRequest: revealRequest
)
}
.padding()
.frame(minWidth: 480, minHeight: 320)
}
}
स्क्रॉल होने वाले AppKit एडिटर को रैप करें
NSViewRepresentable वह ब्रिज है जिसे Apple, SwiftUI पदानुक्रम में AppKit व्यू को मैनेज करने के लिए देता है। यहाँ दर्शाया गया व्यू एक NSScrollView है, जिसमें NSTextView दस्तावेज़ व्यू के रूप में है। कोऑर्डिनेटर आख़िरी बार सँभाले गए अनुरोध को याद रखता है। Apple निर्माण के दौरान उपयोग होने वाली स्क्रॉल करने योग्य टेक्स्ट-व्यू फ़ैक्टरी देता है। NSViewRepresentable, scrollableTextView()।
// CaretEditor.swift
import AppKit
import SwiftUI
@MainActor
struct CaretEditor: NSViewRepresentable {
let initialText: String
let revealRequest: Int
@MainActor
final class Coordinator {
var lastHandledRequest: Int
init(lastHandledRequest: Int) {
self.lastHandledRequest = lastHandledRequest
}
}
func makeCoordinator() -> Coordinator {
Coordinator(lastHandledRequest: revealRequest)
}
func makeNSView(context: Context) -> NSScrollView {
let scrollView = NSTextView.scrollableTextView()
scrollView.hasVerticalScroller = true
guard let textView = scrollView.documentView as? NSTextView else {
preconditionFailure("Expected an NSTextView document view")
}
textView.isRichText = false
textView.isEditable = true
textView.isSelectable = true
textView.allowsUndo = true
textView.drawsBackground = true
textView.backgroundColor = .textBackgroundColor
textView.textColor = .textColor
textView.insertionPointColor = .textColor
textView.font = .systemFont(ofSize: 15)
textView.textContainerInset = NSSize(width: 12, height: 12)
textView.string = initialText
let end = (initialText as NSString).length
textView.setSelectedRange(NSRange(location: end, length: 0))
return scrollView
}
func updateNSView(_ scrollView: NSScrollView, context: Context) {
guard revealRequest != context.coordinator.lastHandledRequest else {
return
}
context.coordinator.lastHandledRequest = revealRequest
guard let textView = scrollView.documentView as? NSTextView,
let window = textView.window,
textView.acceptsFirstResponder else {
return
}
guard window.makeFirstResponder(textView),
window.firstResponder === textView else {
return
}
textView.scrollRangeToVisible(textView.selectedRange())
}
}
एडिटर नमूना टेक्स्ट के अंत में शून्य लंबाई वाले चयन से शुरू होता है। Reveal insertion point दबाने पर एडिटर फ़ोकस होता है और वह स्थिति दिखाई देती है। इसके बाद के प्रेस में मौजूदा चयन का उपयोग होता है, इसलिए कैरेट को कहीं और ले जाने पर कमांड द्वारा दिखाई जाने वाली स्थिति बदल जाती है। यह कमांड टेक्स्ट को बदलता नहीं और न ही चयन को हटाकर कहीं और ले जाता है।
अंतिम स्थिति NSString.length का उपयोग करती है, जो UTF-16 कोड यूनिट गिनता है। Swift का UTF-16 व्यू, इंडेक्स वाली NSString API से मेल खाता है; Swift का String.count कैरेक्टर गिनता है। AppKit टेक्स्ट रेंज बनाते समय इन निरूपणों को एकसमान रखें। रिवील कमांड के लिए ऑफ़सेट की गणना करने से बचने हेतु मौजूदा selectedRange() पढ़ें। NSString length, Swift String.UTF16View, Swift String.count।
SwiftUI अपडेट के दौरान संपादन की स्थिति बनाए रखें
मुख्य डिज़ाइन निर्णय यह है कि निर्माण और कमांड की ज़िम्मेदारियाँ अलग रखी जाएँ। makeNSView एडिटर को एक बार आरंभ करता है। updateNSView बदली हुई अनुरोध संख्या पर प्रतिक्रिया देता है और दस्तावेज़ की सामग्री को जस का तस छोड़ता है। यह AppKit व्यू बनाने और अपडेट करने के उस जीवनचक्र के अनुरूप है जिसे Apple बताता है। SwiftUI दर्शाए गए व्यू का लेआउट भी नियंत्रित करता है, इसलिए यह रैपर स्क्रॉल व्यू का फ़्रेम या बाउंड्स सेट नहीं करता। Apple का representable जीवनचक्र और लेआउट मार्गदर्शन।
इस डेमो में हर अपडेट पर textView.string = initialText असाइन करने से बचें। ऐसा करने पर उपयोगकर्ता का मौजूदा दस्तावेज़ मूल नमूने से बदल जाएगा। इसी तरह, बिना शर्त चयन को दस्तावेज़ के अंत पर सेट करने से रिवील ऑपरेशन संपादन की स्थिति बदल देगा।
SwiftUI स्टेट से जुड़े एडिटर के लिए सोच-समझकर दोतरफ़ा सिंक्रोनाइज़ेशन जोड़ें: कोऑर्डिनेटर के ज़रिए उपयोगकर्ता के संपादन आगे भेजें और तय करें कि आने वाले मॉडल बदलाव टेक्स्ट को कब बदलेंगे। इन बदलावों के दौरान मान्य चयन बनाए रखें और तय करें कि एक साथ हुए बदलावों का समाधान कैसे होगा। यह कैरेट दिखाने से अलग ज़िम्मेदारी है; नमूने में संपादन योग्य सामग्री का एक ही स्रोत जानबूझकर रखा गया है। Apple, डेलीगेट और टार्गेट-एक्शन इंटरैक्शन को SwiftUI तक पहुँचाने के तरीके के रूप में कोऑर्डिनेटर का दस्तावेज़ देता है। कोऑर्डिनेटर संबंधी मार्गदर्शन।
फ़ोकस गार्ड भी मायने रखते हैं। makeFirstResponder(_:) विफल हो सकता है, अगर मौजूदा रिस्पॉन्डर फ़ोकस छोड़ने से इनकार करे। Apple यह भी कहता है कि अनुरोधित रिस्पॉन्डर फ़ोकस स्वीकार न करे तो सफल रिटर्न के बावजूद विंडो स्वयं फ़र्स्ट रिस्पॉन्डर रह सकती है। window.firstResponder === textView की जाँच करके स्क्रॉल करने से पहले वास्तविक परिणाम की पुष्टि करें। फ़र्स्ट-रिस्पॉन्डर के रिटर्न मान और व्यवहार।
अगर एडिटर किसी विंडो से जुड़ा नहीं है, तो यह नमूना बिना कार्रवाई किए अनुरोध को संसाधित मान लेता है। विंडो से जुड़ जाने के बाद बटन फिर दबाएँ। शुरुआती फ़ोकस की सुविधा के लिए, असंबंधित अपडेट के दौरान बार-बार फ़ोकस वापस लेने के बजाय अटैचमेंट का ध्यान रखने वाला स्पष्ट अनुरोध डिज़ाइन करें।
कर्सर ड्रॉइंग बदले बिना रंग कॉन्फ़िगर करें
यह उदाहरण टेक्स्ट और इंसर्शन पॉइंट, दोनों के लिए .textColor और एडिटर बैकग्राउंड के लिए .textBackgroundColor असाइन करता है। ये टेक्स्ट और उसके पीछे के क्षेत्र के लिए दस्तावेज़ीकृत अर्थपूर्ण रंग हैं। insertionPointColor कैरेट का रंग सेट करने के लिए दस्तावेज़ीकृत प्रॉपर्टी है। आपके ऐप में समर्थित macOS के हर संस्करण और अपीयरेंस पर इसका रूप जाँचें। कैरेट का रंग, टेक्स्ट का रंग, टेक्स्ट बैकग्राउंड का रंग।
रंग कॉन्फ़िगरेशन से फ़ोकस का अनुरोध नहीं होता, दस्तावेज़ स्क्रॉल नहीं होता और बिना ब्लिंक वाला डिस्प्ले मोड नहीं चुना जाता। गायब कैरेट की जाँच करते समय इन ज़िम्मेदारियों को अलग रखें। उदाहरण के लिए, रंग बदलने से वह स्थिति दिखाई नहीं देगी जो स्क्रॉल व्यू के दृश्य क्षेत्र से बाहर है।
कैरेट की ज्यामिति निकालने के लिए यह नमूना layoutManager तक पहुँचने से भी बचता है। Apple दस्तावेज़ करता है कि macOS 12 और उसके बाद टेक्स्ट व्यू या टेक्स्ट कंटेनर में इस प्रॉपर्टी तक पहुँचने से व्यू, NSLayoutManager का उपयोग करने वाले कम्पैटिबिलिटी मोड में जा सकता है। चयन दिखाने के लिए उच्च-स्तरीय स्क्रॉलिंग API कॉल करने से सिर्फ़ रेंज दिखाने हेतु यह TextKit कम्पैटिबिलिटी बदलाव नहीं आता। TextKit कम्पैटिबिलिटी व्यवहार।
हमेशा दिखाई देने वाले कैरेट की सीमाएँ समझें
अगर “दृश्यमान रखें” का मतलब “कभी ब्लिंक न करे” है, तो स्क्रॉलिंग कमांड यह ज़रूरत पूरी नहीं करती। Apple के आधुनिक कर्सर दस्तावेज़ में स्वचालित ऐनिमेशन और हमेशा दृश्यमान रहने वाले डिस्प्ले मोड में अंतर बताया गया है।
macOS 14 और उसके बाद, NSTextInsertionIndicator में .automatic, .visible और .hidden डिस्प्ले मोड उपलब्ध हैं। Apple .visible को ऐसा मोड बताता है जो ऐनिमेशन बंद करके कर्सर को लगातार दिखाता है, जैसे ड्रैग के गंतव्य को दर्शाना। मानक AppKit टेक्स्ट व्यू सिस्टम के कर्सर UI का अपने आप उपयोग करते हैं। सिस्टम टेक्स्ट कर्सर अपनाने की गाइड, NSTextInsertionIndicator।
दस्तावेज़ीकृत कस्टम-कर्सर तरीके में अपना इंडिकेटर बनाना और उसकी स्थिति तय करना ज़रूरी है। उसका डिस्प्ले मोड उसी इंस्टेंस को नियंत्रित करता है; Apple की गाइड, NSTextView में आंतरिक रूप से मैनेज किए गए इंडिकेटर को .visible पर बदलने की सेटिंग नहीं देती। कस्टम तरीके को कस्टम टेक्स्ट UI के लिए आर्किटेक्चर का एक विकल्प मानें; इसमें कर्सर की ज्यामिति और सक्रिय स्थिति अपडेट करने की ज़िम्मेदारी भी शामिल है। यह इस रैपर में सीधे लगाया जा सकने वाला बदलाव नहीं है।
दो पुराने दिखने वाले API वह अनुपलब्ध सेटिंग नहीं देते। shouldDrawInsertionPoint को केवल पढ़ने योग्य दस्तावेज़ीकृत किया गया है। drawInsertionPoint(in:color:turnedOn:) कर्सर को ड्रॉ या मिटाता है और Apple कहता है कि कॉलर को इसे सीधे कॉल करने की ज़रूरत नहीं होनी चाहिए। इनमें से किसी को भी हमेशा दृश्यमान रखने के स्विच के रूप में दस्तावेज़ीकृत नहीं किया गया है। इंसर्शन-पॉइंट ड्रॉइंग की स्थिति, इंसर्शन-पॉइंट ड्रॉ करने का मेथड।
इस लेख में वर्णित सामान्य एडिटर कार्य के लिए AppKit की कर्सर ऐनिमेशन बनाए रखें और मौजूदा स्थिति पर लौटने का भरोसेमंद तरीका दें। कस्टम कर्सर कार्यान्वयन को अपने डिज़ाइन की ज़रूरत होगी और, NSTextInsertionIndicator का उपयोग करने पर, macOS 14 की उपलब्धता सीमा भी ध्यान में रखनी होगी।
अपने ऐप में व्यवहार की जाँच करें
स्क्रॉलिंग, चयन, फ़ोकस और अपीयरेंस की जाँच अलग-अलग करने के लिए नमूने का उपयोग करें। नीचे दिए गए बिंदु कार्यान्वयन से अपेक्षित व्यवहार हैं, चलाकर प्राप्त किए गए नतीजे नहीं।
- ऐप लॉन्च करें और
Reveal insertion pointदबाएँ। एडिटर को फ़ोकस मिलना चाहिए और उसकी अंतिम स्थिति दिखाई देनी चाहिए। - कैरेट को ले जाने के लिए किसी पुरानी पंक्ति में क्लिक करें। किसी दूसरी टेक्स्ट स्थिति पर क्लिक किए बिना स्क्रॉल करके दूर जाएँ, फिर बटन दबाएँ। उसे उस पुरानी स्थिति को दिखाना चाहिए।
- कई शब्द चुनें और बटन दबाएँ। चयन बना रहना चाहिए; कमांड उसे हटाकर कैरेट नहीं बनाता।
orange triangleजैसा पहचानने योग्य लेबल टाइप करें, फिर बटन को कई बार दबाएँ। संपादित सामग्री वैसी ही रहनी चाहिए।- विंडो का आकार बदलें और रिवील ऑपरेशन दोहराएँ। कमांड को किसी पुराने स्क्रीन निर्देशांक के बजाय मौजूदा रेंज का उपयोग करना चाहिए।
- समर्थित macOS रिलीज़ पर लाइट और डार्क, दोनों अपीयरेंस जाँचें। पुष्टि करें कि टेक्स्ट, बैकग्राउंड और कैरेट के रंग एक साथ उपयोग करने योग्य हैं।
अगर स्क्रॉलिंग सफल हो लेकिन कैरेट रुक-रुककर दिखाई दे, तो जाँचें कि आप सामान्य ब्लिंकिंग देख रहे हैं या सक्रिय टेक्स्ट चयन। अगर कमांड कुछ न करे, तो पहले विंडो-अटैचमेंट और फ़र्स्ट-रिस्पॉन्डर गार्ड जाँचें। सुधार को उसी चरण तक सीमित रखें जो विफल हुआ है: टेक्स्ट बनाए रखें, अनुरोध होने पर फ़ोकस वापस लाएँ और मौजूदा रेंज दिखाएँ।
स्रोत
- NSViewRepresentable — Apple
- NSTextView — Apple
- scrollableTextView() — Apple
- scrollRangeToVisible(_:) — Apple
- makeFirstResponder(_:) — Apple
- length — Apple
- String.UTF16View — Apple
- count — Apple
- insertionPointColor — Apple
- textColor — Apple
- textBackgroundColor — Apple
- कस्टम टेक्स्ट व्यू में सिस्टम टेक्स्ट कर्सर अपनाना — Apple
- NSTextInsertionIndicator — Apple
- shouldDrawInsertionPoint — Apple
- drawInsertionPoint(in:color:turnedOn:) — Apple