फ़ील्ड का मान लागू किए बिना कोई सुझाव डालने के लिए, सुझाव पूरा करने और सबमिट करने को अलग रखें। macOS 15 या बाद के संस्करणों में, SwiftUI मूल टेक्स्ट सुझाव देता है; पूर्वज सबमिट हैंडलर को रोकने के लिए submitScope() और अंतिम कार्रवाई के लिए एक स्पष्ट बटन इस्तेमाल करें।

यह ट्यूटोरियल आपके अपने Mac ऐप में एक छोटा लेबल पिकर बनाता है। Bl टाइप करने पर Blue Tile जैसे कृत्रिम लेबल दिखते हैं। कोई सुझाव चुनने से संपादन योग्य ड्राफ़्ट अपडेट होता है; Apply Label पर क्लिक करने से वह ड्राफ़्ट लागू किए गए मान में कॉपी हो जाता है। यह अंतर मायने रखता है: ऐप की कार्रवाई को रोकना और Return-कुंजी कमांड को संभालना अलग-अलग आवश्यकताएँ हैं।

इंटरैक्शन के अनुरूप API चुनें

Apple का textInputSuggestions(_:) मॉडिफ़ायर टेक्स्ट फ़ील्ड के लिए विकल्प दिखाता है। हर क्रियाशील सुझाव के लिए textInputCompletion(_:) ज़रूरी है, जो उसका प्रतिस्थापन स्ट्रिंग देता है। दोनों API macOS 15 और बाद के संस्करणों के लिए दस्तावेज़ीकृत हैं। बिना completion मॉडिफ़ायर वाला सुझाव दिख तो सकता है, लेकिन क्लिक पर प्रतिक्रिया नहीं देता। Apple: textInputSuggestions(_:), Apple: textInputCompletion(_:)

छोटे लेबल फ़ील्ड के लिए पूरा ड्राफ़्ट बदलना उपयुक्त है। Apple का सुझावों से जुड़ा दस्तावेज़ पूरे फ़ील्ड को बदलने का वर्णन करता है। यह न मानें कि यह API caret के सापेक्ष टोकन संपादक या इनलाइन suffix पूर्वावलोकन देती है; इनके लिए अधिक विशिष्ट संपादन डिज़ाइन चाहिए। Apple: textInputSuggestions(_:)

आपकी आवश्यकता उपयुक्त शुरुआती विकल्प
macOS 15 या बाद के संस्करणों पर पूरे लेबल सुझाएँ SwiftUI के मूल टेक्स्ट सुझाव
फ़ील्ड को पूर्वज सबमिट हैंडलर चलाने से रोकें फ़ील्ड पर submitScope()
कस्टम सुझाव लेआउट इस्तेमाल करें या पुराने deployment target को सपोर्ट करें ऐप के अपने सुझाव बटन, जो draft binding अपडेट करें
Return से हाइलाइट किया गया आइटम स्वीकार करें और उस कमांड को संभालें कमांड हैंडलिंग वाला ऐप-स्वामित्व वाला AppKit नियंत्रण
suffix का पूर्वावलोकन करें या insertion point पर मौजूद शब्द बदलें AppKit टेक्स्ट completion और स्पष्ट completion range

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

पूरा SwiftUI लेबल पिकर बनाएँ

Xcode में SwiftUI के साथ macOS App प्रोजेक्ट बनाएँ और ऐसा SDK इस्तेमाल करें जिसमें macOS 15 API शामिल हों। deployment target macOS 15 या बाद का रखें। जेनरेट की गई app और content-view घोषणाओं को नीचे दी गई एकल source फ़ाइल से बदलें और target में केवल एक @main app घोषणा रखें।

यह उदाहरण draft और appliedLabel को अलग रखता है। इसका स्थानीय मिलान नियम जानबूझकर सरल है: बड़े-छोटे अक्षरों का ध्यान रखते हुए prefix मिलान, खाली टेक्स्ट पर कोई सुझाव नहीं, और सटीक मिलान पर कोई सुझाव नहीं। Swift hasPrefix(_:) को string-prefix तुलना के रूप में दस्तावेज़ीकृत करता है; इससे व्यापक मिलान नियम चुनना ऐप का निर्णय होगा। Swift: Strings and Characters

import SwiftUI

@main
struct LabelCompletionApp: App {
    var body: some Scene {
        WindowGroup {
            LabelCompletionView()
        }
    }
}

struct LabelCompletionView: View {
    private let labels = [
        "Blue Tile",
        "Blue Tray",
        "Green Tile",
        "Round Tray"
    ]

    @State private var draft = ""
    @State private var appliedLabel = ""
    @State private var applyCount = 0
    @FocusState private var fieldIsFocused: Bool

    private var matches: [String] {
        guard !draft.isEmpty else { return [] }
        return labels.filter {
            $0.hasPrefix(draft) && $0 != draft
        }
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Choose a label")
                .font(.headline)

            TextField("Type Bl to see suggestions", text: $draft)
                .textFieldStyle(.roundedBorder)
                .focused($fieldIsFocused)
                .textInputSuggestions {
                    ForEach(matches, id: \.self) { label in
                        Text(label)
                            .textInputCompletion(label)
                    }
                }
                .submitScope()

            Button("Apply Label") {
                appliedLabel = draft
                applyCount += 1
            }
            .disabled(draft.isEmpty)

            Text("Draft: \(draft)")
            Text("Applied: \(appliedLabel)")
            Text("Apply count: \(applyCount)")
        }
        .padding(24)
        .frame(width: 420)
        .onAppear {
            fieldIsFocused = true
        }
    }
}

यह एक पूरा ऐप उदाहरण है, जिसमें इसके नमूना लेबल और state शामिल हैं। प्राप्त API दस्तावेज़ों के आधार पर इसकी जाँच की गई, लेकिन यहाँ इसे compile या run नहीं किया गया, क्योंकि Swift toolchain और macOS UI runtime उपलब्ध नहीं थे।

सुझाव चुनने से लेबल क्यों लागू नहीं होता

हर completion टेक्स्ट फ़ील्ड के लिए एक स्ट्रिंग देता है, जिसकी binding draft की ओर है। appliedLabel को असाइन करने या applyCount को बढ़ाने वाला एकमात्र कोड Apply Label बटन की कार्रवाई है। इसलिए इस उदाहरण में ड्राफ़्ट अपडेट करने का उस अंतिम कार्रवाई तक कोई रास्ता नहीं है।

फ़ील्ड पर submitScope() भी है। Apple इस मॉडिफ़ायर को ऐसे दस्तावेज़ीकृत करता है कि यह सबमिशन ट्रिगर को view hierarchy में ऊपर के submit मॉडिफ़ायर तक पहुँचने से रोकता है। यह तब मायने रखता है जब इस component को बाद में किसी बड़े form में पूर्वज onSubmit के साथ एम्बेड किया जाए। Apple: submitScope(_:)

शुरुआती focus @FocusState से आता है। Apple दस्तावेज़ीकृत करता है कि इसकी bound value को प्रोग्रामेटिक रूप से असाइन करने पर संबंधित view पर focus चला जाता है। यहाँ focus संपादन को सुविधाजनक बनाता है; इससे यह नियंत्रित नहीं होता कि लेबल लागू होगा या नहीं। Apple: FocusState

उदाहरण जानबूझकर किन चीज़ों की अनुमति देता है

लेबल सूची सुझाव देती है, वैध इनपुट को सीमित नहीं करती। Square Tile टाइप करने पर भी Apply Label सक्षम रहता है। यदि आपका इंटरफ़ेस केवल सूचीबद्ध लेबल की अनुमति देता है, तो validation नियम स्पष्ट रूप से बदलें—उदाहरण के लिए, बटन तभी सक्षम करें जब ड्राफ़्ट catalog की किसी प्रविष्टि के बराबर हो। बिना बताए बेमेल ड्राफ़्ट को पहले सुझाव से न बदलें।

यह उदाहरण पूरा लेबल भी बदल देता है। यदि ड्राफ़्ट में कई शब्द हों, तो completion चुनने का अर्थ फ़ील्ड का पूरा मान चुनना समझा जाना चाहिए। आसपास का टेक्स्ट बनाए रखने वाले token editor को selection-aware प्रतिस्थापन चाहिए।

समझें कि “सबमिट किए बिना” का क्या मतलब है

SwiftUI का onSubmit(of:_:) दस्तावेज़ कहता है कि उपयोगकर्ता Return दबाने पर टेक्स्ट फ़ील्ड अपनी submit कार्रवाई चलाता है। Submit कार्रवाइयाँ किसी एक view या बड़े hierarchy से जुड़ी हो सकती हैं। जब autocomplete चयन से अनपेक्षित ऐप कार्रवाई शुरू होती दिखे, तो दोनों जगह जाँचें। Apple: onSubmit(of:_:)

submitScope() पूर्वज हैंडलर के लिए एक सीमा तय करता है। इसके दस्तावेज़ में यह वादा नहीं है कि कोई आंतरिक submit event नहीं होगा, संपादन जारी रहेगा, या हर keyboard command संभाल लिया जाएगा। इसी तरह, Apple के completion मॉडिफ़ायर टेक्स्ट बदलने का वर्णन करते हैं, Return-कुंजी व्यवहार को सार्वभौमिक रूप से दबाने का नहीं। इंटरैक्शन चुनते समय इन सीमाओं का ध्यान रखें। Apple: submitScope(_:), Apple: textInputCompletion(_:)

किसी मौजूदा component के लिए इन रास्तों को अलग-अलग जाँचें:

  1. यदि Return से मान कभी लागू नहीं होना चाहिए, तो फ़ील्ड के अपने onSubmit से अंतिम ऐप कार्रवाई हटाएँ।
  2. पूर्वज submit कार्रवाई रोकने के लिए फ़ील्ड पर submitScope() रखें।
  3. बटन के keyboard shortcuts अलग से जाँचें, ख़ासकर स्पष्ट default कार्रवाई।
  4. किसी change observer या model setter की जाँच करें, जो ड्राफ़्ट बदलते ही अंतिम कार्रवाई करता हो।

खाली स्थानीय onSubmit closure, submit सीमा का दस्तावेज़ीकृत विकल्प नहीं है। Apple propagation रोकने के लिए विशेष रूप से submitScope() देता है। यदि completion सक्रिय न होने पर ही ऐप को Return पर submit करना चाहिए, तो उस सशर्त keyboard नीति के लिए अतिरिक्त state और command handling चाहिए। Apple: onSubmit(of:_:)

Return को स्वीकार करके रोकने के लिए AppKit इस्तेमाल करें

ऐप के स्वामित्व वाले NSTextField के लिए AppKit control(_:textView:doCommandBy:) देता है। इसका text-view argument नियंत्रण का field editor होता है। delegate किसी bound command को संभाल सकता है और true लौटा सकता है; false लौटाने पर वह delegate उसे बिना संभाले छोड़ देता है। यह टेक्स्ट फ़ील्ड के commands को अनुकूलित करने का दस्तावेज़ीकृत extension point है। Apple: control(_:textView:doCommandBy:)

कस्टम completion controller इस extension point का उपयोग इस क्रम में कर सकता है:

  1. ट्रैक करें कि सुझाव सूची खुली है या नहीं और कौन-सा आइटम हाइलाइट है।
  2. जब वैध हाइलाइट किए गए आइटम के साथ Return command आए, तो वह आइटम ड्राफ़्ट में डालें।
  3. सुझाव सूची बंद करें और ऐप के draft model को सिंक्रनाइज़ करें।
  4. true लौटाएँ, क्योंकि controller ने command संभाल लिया।
  5. जिन commands को controller नहीं संभालता, उनके लिए false लौटाएँ।

यह क्रम delegate contract से निकला implementation डिज़ाइन है, कोई अंतर्निहित autocomplete नीति नहीं। सूची बंद होने पर Return से क्या होना चाहिए, यह अलग से तय करें। यदि इसके बजाय आपके पास स्वतंत्र NSTextView है, तो संबंधित hook textView(_:doCommandBy:) है; Apple कहता है कि true लौटाने से text view वह command खुद नहीं करता। Apple: textView(_:doCommandBy:)

टेक्स्ट completion के लिए NSTextView.insertCompletion(_:forPartialWordRange:movement:isFinal:) replacement और selection में बदलाव का समर्थन करता है। isFinal: false के साथ यह प्रस्तावित suffix चुनता है; isFinal: true के साथ यह पूर्ण किए गए शब्द के बाद selection को समेट देता है। यह flag completion की स्थिति बताता है, ऐप सबमिट होने की नहीं। movement argument बताता है कि completion navigation या selection किस तरह हुई। Apple: insertCompletion(_:forPartialWordRange:movement:isFinal:)

सही टेक्स्ट range बनाए रखें

AppKit का rangeForUserCompletion insertion point तक, आंशिक शब्द की शुरुआत की पहचान करता है। इच्छित replacement के अनुरूप range इस्तेमाल करें और संपादन या caret खिसकने के बाद इसे फिर से निकालें। टेक्स्ट बदलने के बाद पहले से कैप्चर की गई range का आँख मूँदकर दोबारा इस्तेमाल न करें। Apple: rangeForUserCompletion

टेक्स्ट ranges को bridge करते समय, NSString की length के लिए Swift की character count का विकल्प न लें। Swift के दस्तावेज़ में बताया गया है कि String extended grapheme clusters गिनता है, जबकि NSString की length UTF-16 code units गिनती है। दोनों मान अलग हो सकते हैं। SwiftUI उदाहरण में पूरे फ़ील्ड की binding बदलने से यह range conversion पूरी तरह टल जाता है। Swift: Strings and Characters)

सुझाव डालने और लागू करने को अलग-अलग जाँचें

हर समर्थित macOS संस्करण पर नतीजा जाँचने के लिए दिख रहे ड्राफ़्ट, लागू किए गए मान और counter का उपयोग करें। नीचे दिए गए उदाहरण के लिए अपेक्षित जाँचें हैं, रिपोर्ट किए गए परीक्षण नतीजे नहीं।

कार्रवाई अपेक्षित दिखाई देने वाला नतीजा
Bl टाइप करें ड्राफ़्ट Bl हो; मेल खाने वाले लेबल दिखें
Blue Tile पर क्लिक करें ड्राफ़्ट Blue Tile हो जाए; लागू किया गया मान और count अपरिवर्तित रहें
संपादन के दौरान Return दबाएँ ऐप ड्राफ़्ट लागू न करे
Apply Label पर क्लिक करें लागू किया गया मान ड्राफ़्ट की कॉपी हो; count एक बार बढ़े
बाद में ड्राफ़्ट संपादित करें पिछला लागू किया गया मान दिखता रहे
ड्राफ़्ट साफ़ करें सुझाव खाली हों और Apply Label अक्षम हो
bl टाइप करें उदाहरण के बड़े-छोटे अक्षरों वाले नियम के तहत कोई मिलान न हो

मूल सुझाव सूची में keyboard से चयन करके भी देखें और फ़ील्ड को उसके वास्तविक parent view में एम्बेड करने के बाद जाँचें दोहराएँ। Apple बताता है कि macOS विंडो को खिसकाने जैसी घटनाएँ मूल सुझाव इंटरफ़ेस को खारिज कर सकती हैं, इसलिए उसके बिना भी ड्राफ़्ट उपयोग करने योग्य रहना चाहिए। Apple: textInputSuggestions(_:)

यदि आपका deployment target macOS 15 से पुराना है, तो availability-गेट वाला native view और fallback दें, या हर जगह ऐप के अपने सुझाव बटन इस्तेमाल करें। fallback बटन अपना लेबल सीधे ड्राफ़्ट में असाइन कर सकता है और अंतिम कार्रवाई Apply Label पर छोड़ सकता है। इससे ड्राफ़्ट और लागू करने का वही अलगाव मिलता है, जबकि keyboard navigation और प्रस्तुति आपके component की ज़िम्मेदारी रहती है।

इस लेबल-पिकर कार्य के लिए उपयोगी सीमा दिखाई देने योग्य है: completion स्वीकार करने से संपादन योग्य ड्राफ़्ट बदलता है, और केवल स्पष्ट अंतिम कार्रवाई से लागू किया गया लेबल बदलता है। इस सीमा के लिए SwiftUI के मूल सुझाव और submit scope इस्तेमाल करें; जब Return को खुद संभालना आवश्यकता हो, तो AppKit command handling अपनाएँ।

स्रोत