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

इस ट्यूटोरियल में कंटेंट, हेडर और फ़ूटर क्षेत्रों वाला एक छोटा कंपोनेंट बनाया गया है। उद्देश्य चार उपयोगी कॉल का समर्थन करना है—केवल कंटेंट, किसी एक सहायक क्षेत्र के साथ कंटेंट, और दोनों के साथ कंटेंट—बिना डिफ़ॉल्ट क्लोज़र के बीच प्रतिस्पर्धा पर निर्भर हुए। दिखाए गए सभी मान स्थानीय, कृत्रिम टेक्स्ट हैं।

कई-बिल्डर वाला इनिशियलाइज़र अस्पष्टता-रहित कैसे होता है?

Swift का कई ट्रेलिंग क्लोज़र वाला सिंटैक्स पहले ट्रेलिंग क्लोज़र का आर्ग्युमेंट लेबल छोड़ देता है और बाद के ट्रेलिंग क्लोज़र के लिए लेबल अनिवार्य करता है। इसलिए, कंपोनेंट के नाम से ही उसके पहले बिल्डर का उद्देश्य स्पष्ट होना चाहिए और बाद के बिल्डरों के अलग-अलग लेबल होने चाहिए। यह सिंटैक्स Swift 5.3 में लागू हुआ था। SE-0279: कई ट्रेलिंग क्लोज़र

इस पैनल के लिए पूरा इनिशियलाइज़र init(content:header:footer:) है। कॉल साइट पर पहला ब्लॉक कंटेंट देता है; header: और footer: अन्य ब्लॉकों की पहचान करते हैं। उनकी घोषणा का क्रम आर्ग्युमेंट का क्रम तय करता है, जबकि कंपोनेंट का body दृश्य क्रम तय करता है। इसलिए कंटेंट पहला आर्ग्युमेंट हो सकता है, भले ही हेडर उसके ऊपर दिखे।

Apple का मौजूदा Section इंटरफ़ेस इसी कंटेंट-प्रथम व्यवस्था का अनुसरण करता है। इसके पूरे इनिशियलाइज़र में तीन बिल्डर क्लोज़र स्वीकार होते हैं, और अनुपस्थित क्षेत्रों के संयोजनों के लिए अलग इनिशियलाइज़र उपलब्ध हैं। यह इंटरफ़ेस डिज़ाइन का उपयोगी उदाहरण है; नीचे दिए गए कस्टम पैनल का लेआउट और व्यवहार अपना है। Apple: Section, Apple: init(content:header:footer:)

बिल्डर एट्रिब्यूट और पैरामीटर लेबल अलग-अलग समस्याएँ हल करते हैं। रिज़ल्ट-बिल्डर एट्रिब्यूट दिए गए क्लोज़र के बॉडी को रूपांतरित करता है; आर्ग्युमेंट लेबल तय करते हैं कि कॉल करने वाले पैरामीटरों की पहचान कैसे करें। @ViewBuilder जोड़ने से ट्रेलिंग क्लोज़र सिंटैक्स द्वारा छोड़ा गया पहला लेबल वापस नहीं आता। SE-0289: रिज़ल्ट बिल्डर

यह अंतर एक व्यावहारिक डिज़ाइन नियम देता है: ऐसे सहोदर इनिशियलाइज़र से बचें जिनके बीच का एकमात्र उपयोगी अंतर पहला ट्रेलिंग क्लोज़र लेबल हटने पर गायब हो जाता है। उदाहरण के लिए, बाकी मामलों में एक जैसे init(content:header:) और init(body:header:) नाम वाले दो इनिशियलाइज़र में पहला ब्लॉक बिना लेबल के और उसके बाद header: दिखेगा। यह ट्रेलिंग क्लोज़र नियमों का डिज़ाइन-संबंधी परिणाम है, और इस वजह से अधिक बिल्डर एट्रिब्यूट जोड़ने की ज़रूरत नहीं है।

कौन-सी कंपाइलर और SwiftUI सीमाएँ मायने रखती हैं?

तीन बातों का ध्यान रखें: कंपाइलर, चुना हुआ Swift भाषा मोड और SwiftUI SDK। कई ट्रेलिंग क्लोज़र Swift 5.3 में आए, जबकि औपचारिक रिज़ल्ट-बिल्डर सुविधा Swift 5.4 में लागू हुई। ये पड़ाव भाषा समर्थन बताते हैं; वे हर फ़्रेमवर्क इनिशियलाइज़र की उपलब्धता तय नहीं करते। SE-0279, SE-0289

Apple का मौजूदा दस्तावेज़ीकरण टूलचेन की सीमा भी बताता है: Xcode 26 और उससे पुराने संस्करण स्थापित व्यू-बिल्डिंग व्यवहार का उपयोग करते हैं, जबकि Xcode 27 और उसके बाद के संस्करण इन बिल्डरों से प्रकार-निरपेक्ष कंटेंट बनाते हैं। नए व्यवहार के लिए Apple @ContentBuilder वर्तनी इस्तेमाल करने का निर्देश देता है। ContentBuilder, ViewBuilder का टाइप एलियस है; स्वीकार्य कंटेंट का निर्धारण फिर भी बाहरी API की शर्तें करती हैं। Apple: ViewBuilder, Apple: ContentBuilder

नीचे दिया पूरा नमूना Xcode 26 के आधार के लिए @ViewBuilder वर्तनी इस्तेमाल करता है और macOS 11 या उसके बाद के संस्करण को लक्षित करता है। दस्तावेज़ित Xcode 27 इंटरफ़ेस शैली अपनाते समय इनिशियलाइज़र पैरामीटर के एट्रिब्यूट को @ContentBuilder से बदलें। तीनों सामान्य View शर्तें बनाए रखें: यह कंपोनेंट तीनों क्षेत्रों को व्यू लेआउट के भीतर रखता है।

कंपोनेंट के अनुबंध में बिल्डर द्वारा बनाया गया टपल प्रकार नाम से न लिखें। Apple ने नए बिल्डर कार्यान्वयन को अपनाते समय स्पष्ट TupleView शर्तों से जुड़ी स्रोत-असंगतियों का दस्तावेज़ दिया है। सामान्य क्षेत्र प्रकारों से यह पैनल उस निर्भरता से बचता है। Apple: TN3211, State और ContentBuilder के लिए SwiftUI स्रोत-असंगतियों का समाधान

चार स्पष्ट इनिशियलाइज़र आकारों वाला पैनल बनाएँ

हर क्षेत्र के लिए स्वतंत्र सामान्य प्रकार इस्तेमाल करें। एक ही सामान्य पैरामीटर दोबारा इस्तेमाल करने पर सभी क्षेत्रों का ठोस प्रकार समान होना ज़रूरी होगा। यह अनावश्यक पाबंदी है, क्योंकि एक क्षेत्र में टेक्स्ट का एक व्यू हो सकता है और दूसरे में कई व्यू।

अनुपस्थित क्षेत्र के लिए उसके सामान्य पैरामीटर को EmptyView तक सीमित करें। Apple EmptyView की यही भूमिका बताता है: सामान्य व्यू प्रकार में अनुपस्थित चाइल्ड का प्रतिनिधित्व करना। नीचे के सीमित इनिशियलाइज़र कॉल करने वालों से अनिर्दिष्ट सहायक प्रकार का अनुमान लगाने को कहने के बजाय इस विकल्प को स्पष्ट करते हैं। Apple: EmptyView

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

import SwiftUI

struct BuilderPanel<Content: View, Header: View, Footer: View>: View {
    private let content: Content
    private let header: Header
    private let footer: Footer

    init(
        @ViewBuilder content: () -> Content,
        @ViewBuilder header: () -> Header,
        @ViewBuilder footer: () -> Footer
    ) {
        self.content = content()
        self.header = header()
        self.footer = footer()
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            header
            content
            footer
        }
        .padding()
    }
}

extension BuilderPanel where Footer == EmptyView {
    init(
        @ViewBuilder content: () -> Content,
        @ViewBuilder header: () -> Header
    ) {
        self.init(
            content: content,
            header: header,
            footer: { EmptyView() }
        )
    }
}

extension BuilderPanel where Header == EmptyView {
    init(
        @ViewBuilder content: () -> Content,
        @ViewBuilder footer: () -> Footer
    ) {
        self.init(
            content: content,
            header: { EmptyView() },
            footer: footer
        )
    }
}

extension BuilderPanel where Header == EmptyView, Footer == EmptyView {
    init(@ViewBuilder content: () -> Content) {
        self.init(
            content: content,
            header: { EmptyView() },
            footer: { EmptyView() }
        )
    }
}

पूरा इनिशियलाइज़र पैनल मान के इनिशियलाइज़ेशन के दौरान हर बिल्डर को एक बार कॉल करके लौटाए गए मान संग्रहित करता है। सुविधा इनिशियलाइज़र उसी को आगे कॉल करते हैं। यह समय-व्यवहार इस कार्यान्वयन का गुण है: यह वादा नहीं करता कि इंटरफ़ेस के पूरे जीवनकाल में बिल्डर केवल एक बार चलेगा, क्योंकि नया पैनल मान बनाने पर उसका इनिशियलाइज़र फिर कॉल होता है।

इस दूसरे ब्लॉक को उसी फ़ाइल में जोड़ें। यह हर समर्थित आकार को आज़माता है, जिसमें एक बिल्डर के भीतर कई स्टेटमेंट भी शामिल हैं।

struct PanelDemo: View {
    var body: some View {
        ScrollView {
            VStack(alignment: .leading, spacing: 16) {
                BuilderPanel {
                    Text("Canvas A")
                }

                BuilderPanel {
                    Text("Canvas B")
                    Text("Grid: 12 columns")
                } header: {
                    Text("Layout")
                }

                BuilderPanel {
                    Text("Canvas C")
                } footer: {
                    Text("Units: points")
                }

                BuilderPanel {
                    Text("Canvas D")
                } header: {
                    Text("Preview")
                } footer: {
                    Text("Scale: 100 percent")
                }
            }
            .padding()
        }
        .frame(minWidth: 360, minHeight: 440)
    }
}

@main
struct BuilderPanelDemoApp: App {
    var body: some Scene {
        WindowGroup {
            PanelDemo()
        }
    }
}

चुने हुए SDK के साथ अनुबंध की पुष्टि करने के लिए Xcode में ऐप बनाकर चलाएँ। लिस्टिंग का निरीक्षण किया गया, लेकिन यहाँ इसे कंपाइल या निष्पादित नहीं किया गया; शोध परिवेश में न Swift कंपाइलर था, न macOS SwiftUI SDK।

हर कॉल को ठोस प्रकार के अनुबंध से मिलाएँ

कॉल-आकार की तालिका उपयोगी निर्णय-सहायिका है। यह बताती है कि कौन-सा इनिशियलाइज़र चुना जाना चाहिए और अनुपस्थित क्षेत्रों को कौन-सा ठोस प्रकार मिलता है। ये नमूने की घोषणाओं से निकली अपेक्षाएँ हैं, जिन्हें आप अपने प्रोजेक्ट में जाँच सकते हैं।

दिए गए क्षेत्र इनिशियलाइज़र लेबल अनुपस्थित क्षेत्र की शर्तें
केवल कंटेंट content: Header == EmptyView, Footer == EmptyView
कंटेंट और हेडर content:header: Footer == EmptyView
कंटेंट और फ़ूटर content:footer: Header == EmptyView
तीनों content:header:footer: कोई नहीं

हर छोटा इनिशियलाइज़र अपने द्वारा घोषित हर क्लोज़र को अनिवार्य करता है। कोई डिफ़ॉल्ट आर्ग्युमेंट तीन-बिल्डर वाले इनिशियलाइज़र को केवल-कंटेंट कॉल के लिए प्रतिस्पर्धा नहीं करने देता। हेडर-मात्र और फ़ूटर-मात्र सहायक रूप दूसरे लेबल के आधार पर अलग होते हैं, भले ही दोनों क्लोज़र Text लौटाएँ।

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

कई डिफ़ॉल्ट बिल्डर क्लोज़र से क्यों बचें?

डिफ़ॉल्ट आर्ग्युमेंट उपयोगी होते हैं, लेकिन कई डिफ़ॉल्ट क्लोज़र पैरामीटर मिलान के ऐसे नियम जोड़ते हैं जो Swift भाषा मोड पर निर्भर करते हैं। SE-0286 फ़ॉरवर्ड मैचिंग, डिफ़ॉल्ट पैरामीटर छोड़ने की संगतता-आधारित युक्ति और पुराने मोड में बैकवर्ड मैचिंग के फ़ॉलबैक का दस्तावेज़ देता है। इसमें Swift 5.8 में लागू हुए ForwardTrailingClosures आगामी फ़ीचर फ़्लैग का भी वर्णन है। SE-0286: ट्रेलिंग क्लोज़र के लिए फ़ॉरवर्ड-स्कैन मिलान

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

अनुपस्थित सहायक क्षेत्र और खाली सहायक क्लोज़र भी इंटरफ़ेस के दो अलग विकल्प हैं। केवल-कंटेंट इनिशियलाइज़र दोनों सहायकों के लिए स्पष्ट रूप से EmptyView चुनता है। खाली बिल्डर देने पर SDK का बिल्डर कार्यान्वयन क्लोज़र का परिणाम तय करता है। इस कंपोनेंट में अप्रयुक्त क्षेत्रों को छोड़ें और सुविधा इनिशियलाइज़र को अनुपस्थिति का दस्तावेज़ित प्रकार देने दें।

इस तरीके की एक सीमा है। जब कंटेंट हमेशा अनिवार्य हो, तब तीन क्षेत्र चार उपयोगी संयोजन देते हैं। कई स्वतंत्र रूप से वैकल्पिक क्षेत्रों वाले कंपोनेंट के लिए बहुत बड़ा ओवरलोड समूह चाहिए होगा। ऐसे ओवरलोड जोड़ने से पहले तय करें कि पाठकों को वास्तव में किन संयोजनों की ज़रूरत है; अर्थपूर्ण कंपोनेंट विकल्पों का छोटा समूह इस्तेमाल और रखरखाव में आसान हो सकता है।

अस्पष्ट कॉल का निदान कैसे करें?

सबसे छोटे पैनल कॉल से शुरू करें जो समस्या को दोहराता है। फिर यह क्रम अपनाएँ; इससे आर्ग्युमेंट मिलान और बिल्डर के अंदर की विफलताओं में अंतर पता चलता है।

  1. सभी क्लोज़र आर्ग्युमेंट को कोष्ठकों के भीतर लिखें। घोषणा के क्रम में content:, header: और footer: स्पष्ट लिखें। अगर इससे कॉल सही हो जाए, तो देखें कि ट्रेलिंग सिंटैक्स ने कौन-सा अंतर हटाया था।
  2. अस्थायी रूप से हर क्षेत्र को एक Text एक्सप्रेशन से बदलें। अगर तब कॉल चलने लगे, तो मूल एक्सप्रेशन एक-एक करके वापस रखें, ताकि विफल बिल्डर बॉडी का पता चले।
  3. इनिशियलाइज़र के आकारों की तुलना करें। प्रतिस्पर्धी डिफ़ॉल्ट, क्लोज़र के उलटे क्रम और केवल पहले बिल्डर के लेबल से अलग होने वाले ओवरलोड देखें।
  4. अनुपस्थित क्षेत्र की शर्तें जाँचें। हर छोड़े गए सामान्य क्षेत्र का चुने गए इनिशियलाइज़र में अपेक्षित ठोस प्रकार होना चाहिए।
  5. टूलचेन की सीमा जाँचें। Xcode 27 पर जाने के बाद, इनिशियलाइज़र समूह को फिर से डिज़ाइन करने से पहले स्पष्ट बिल्डर-परिणाम शर्तें और पुराने टपल आउटपुट प्रकारों पर निर्भर टकराते नाम जाँचें।

कोष्ठकों के भीतर लेबल लिखने से वे आर्ग्युमेंट नाम बने रहते हैं जिन्हें पहला ट्रेलिंग क्लोज़र छोड़ देता है। Swift दोनों रूपों का समर्थन करता है, इसलिए पूरी तरह लेबल वाला रूप निदान के लिए उपयोगी है और जहाँ वह कॉल को पढ़ना आसान बनाए, वहाँ अंतिम वर्तनी के रूप में भी उपयुक्त है। The Swift Programming Language: क्लोज़र

नए SDK के कंपाइलर संदेशों के लिए Apple विशेष रूप से उन प्रकारों के नाम योग्य बनाने की सलाह देता है जो अलग-अलग मॉड्यूल में टकराते हैं, और पुराने टपल आउटपुट प्रकारों पर निर्भर शर्तों को अपडेट करने का निर्देश देता है। ये समस्याएँ अच्छी तरह डिज़ाइन किए गए इनिशियलाइज़र कॉल के भीतर भी हो सकती हैं; अधिक ओवरलोड जोड़ने से मूल समस्या हल नहीं होगी। Apple: TN3211

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

स्रोत