अपने Mac ऐप में किसी लेबल को फैलाए बिना मापने के लिए, जिस View का निरीक्षण करना है उसमें onGeometryChange जोड़ें। पुराने डिप्लॉयमेंट टारगेट के लिए, उसके बैकग्राउंड में GeometryReader रखें। मॉडिफ़ायर की स्थिति तय करती है कि आप कौन-सी चौड़ाई पढ़ेंगे।

यह वॉकथ्रू एक कृत्रिम लेबल को उसकी पैडिंग समेत मापता है और उसके आसपास के लेआउट को माप के नतीजे से स्वतंत्र रखता है। यह यह भी बताता है कि लेबल की मौजूदा चौड़ाई, उसकी आदर्श चौड़ाई और उसके कंटेनर की चौड़ाई में अंतर कैसे करें।

GeometryReader से लेआउट क्यों फैल जाता है?

SwiftUI लेआउट में प्रस्ताव और प्रतिक्रियाएँ शामिल होती हैं: पैरेंट जगह प्रस्तावित करता है और उसके चाइल्ड अपने गणना किए गए आकार बताते हैं। फ़्रेम जोड़ने से उस पदानुक्रम में एक और View जुड़ जाता है, इसलिए लेबल और उसके फ़्रेम की चौड़ाइयाँ अलग हो सकती हैं। Apple इस प्रक्रिया को एक सरल View का लेआउट करना में समझाता है।

GeometryReader एक ऐसा कंटेनर है जिसका पसंदीदा आकार लचीला होता है। इसलिए किसी कॉम्पैक्ट लेबल को इसमें लपेटने से पैरेंट के सामने प्रस्तुत लेआउट सहभागी बदल जाता है। आप रीडर के कंटेनर को माप रहे होते हैं, जो टेक्स्ट की ज़रूरत से कहीं अधिक जगह ले सकता है। Apple का GeometryReader दस्तावेज़ देखें।

निरीक्षण के लिए, माप को मौजूदा View में जोड़ें। जब बैकग्राउंड रीडर ज़रूरी हो, तो उसकी जगह मायने रखती है: Apple के अनुसार बैकग्राउंड को उस View का आकार मिलता है जिसमें उसे जोड़ा गया है। इससे लेबल माप का क्षेत्र निर्धारित करता है। अपने View में बैकग्राउंड जोड़ना देखें।

कार्यान्वयन चुनने से पहले तय करें कि आपके इंटरफ़ेस को किस सवाल का जवाब देना है:

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

यह अंतर एक आम गलती से बचाता है: सीमित चौड़ाई वाले, संभवतः कटे हुए लेबल की चौड़ाई को पूरे स्ट्रिंग को दिखाने के लिए ज़रूरी जगह समझ लेना।

macOS 13 और उसके बाद के संस्करणों पर मौजूदा चौड़ाई मापें

Apple के मौजूदा दस्तावेज़ में onGeometryChange(for:of:action:) का एकल-मान ऐक्शन ओवरलोड macOS 13 और उसके बाद के संस्करणों के लिए उपलब्ध है। यह ज्योमेट्री प्रॉक्सी को समानता-जाँच योग्य मान में बदलता है और उस मान के बदलने पर ऐक्शन चलाता है। ऐसा SDK इस्तेमाल करें जिसमें यह API शामिल हो और यहाँ दिखाया गया एक-पैरामीटर ऐक्शन रखें। onGeometryChange(for:of:action:) देखें।

Xcode में SwiftUI और macOS 13 या उसके बाद के डिप्लॉयमेंट टारगेट के साथ macOS ऐप प्रोजेक्ट बनाएँ। जनरेट की गई ऐप और content-view घोषणाओं को इस पूरे प्रोग्राम से बदलें और टारगेट में केवल एक @main ऐप घोषणा रखें। इस उदाहरण में केवल स्थानीय कृत्रिम लेबल इस्तेमाल किए गए हैं।

import SwiftUI
import Foundation

@main
struct WidthDemoApp: App {
    var body: some Scene {
        WindowGroup {
            WidthDemoView()
        }
    }
}

struct WidthDemoView: View {
    @State private var measuredWidth: CGFloat = 0
    @State private var offeredWidth: Double = 260
    @State private var showLongLabel = false

    private var label: String {
        showLongLabel
            ? "Sample collection of blue circles"
            : "Blue circles"
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text(label)
                .font(.system(size: 16))
                .lineLimit(1)
                .padding(.horizontal, 12)
                .padding(.vertical, 8)
                .background(Color.blue.opacity(0.12))
                .onGeometryChange(for: CGFloat.self) { proxy in
                    proxy.size.width
                } action: { newWidth in
                    measuredWidth = newWidth
                }
                .frame(
                    width: CGFloat(offeredWidth),
                    alignment: .leading
                )

            Text(String(
                format: "Measured badge: %.1f pt",
                Double(measuredWidth)
            ))
            .monospacedDigit()
            .frame(width: 340, alignment: .leading)

            Slider(value: $offeredWidth, in: 120...340)

            Button("Change sample label") {
                showLongLabel.toggle()
            }
        }
        .frame(width: 360, alignment: .leading)
        .padding(20)
    }
}

ऑब्ज़र्वर proxy.size.width पढ़ता है; Apple GeometryProxy.size को कंटेनर View का आकार परिभाषित करता है। यहाँ निरीक्षण किए गए View में टेक्स्ट और पैडिंग शामिल हैं, जबकि इसके बाद का फ़्रेम अलग से नियंत्रित चौड़ाई का प्रस्ताव देता है। GeometryProxy.size देखें।

माप एक अलग रीडआउट को अपडेट करता है। यह मापे जा रहे बैज की चौड़ाई तय नहीं करता। डेमो का निश्चित क्षेत्र और रीडआउट की चौड़ाई नतीजे को देखना आसान बनाते हैं और डायग्नोस्टिक टेक्स्ट को आसपास के स्टैक का आकार बदलने से रोकते हैं।

केवल CGFloat लौटाने से निरीक्षण चौड़ाई पर केंद्रित रहता है: केवल ऊँचाई बदलने पर बदला हुआ मान नहीं मिलता। Apple ज्योमेट्री को आपके लिए ज़रूरी खास डेटा में बदलने की सलाह देता है, क्योंकि ज्योमेट्री बार-बार बदल सकती है। onGeometryChange(for:of:action:) देखें।

इन उदाहरणों को प्राप्त API घोषणाओं से मिलाकर देखा गया। यहाँ इन्हें कंपाइल या चलाया नहीं गया, क्योंकि उपलब्ध वातावरण Swift टूलचेन के बिना Linux है; Mac पर Xcode में बिल्ड करें और इंटरफ़ेस देखें।

माप मॉडिफ़ायर कहाँ लगाना चाहिए?

मॉडिफ़ायर चेन को एक के बाद एक लिपटे हुए Views की शृंखला की तरह पढ़ें। माप उस आख़िरी आकार-प्रभावी मॉडिफ़ायर के तुरंत बाद होना चाहिए जिसे आप शामिल करना चाहते हैं। Apple का लेआउट विवरण बताता है कि फ़्रेम Views को कैसे लपेटते हैं और पैडिंग उनके कॉन्टेंट के लिए उपलब्ध जगह कैसे बदलती है। एक सरल View का लेआउट करना देखें।

उदाहरण में माप की जगह यह क्या देखता है
दोनों पैडिंग मॉडिफ़ायर से पहले टेक्स्ट View की मौजूदा लेआउट चौड़ाई
दोनों पैडिंग मॉडिफ़ायर के बाद पैडिंग वाले बैज की मौजूदा चौड़ाई
स्पष्ट चौड़ाई वाले फ़्रेम के बाद बाहरी फ़्रेम की चौड़ाई
आसपास के VStack पर स्टैक की चौड़ाई

उदाहरण के लिए, मान लें कि मौजूदा प्रस्ताव के तहत टेक्स्ट View 96 पॉइंट की चौड़ाई बताता है। दोनों क्षैतिज किनारों पर 12-12 पॉइंट जोड़ने से पैडिंग वाली चौड़ाई 120 पॉइंट हो जाती है। 260 पॉइंट का बाहरी फ़्रेम एक अलग माप है। ये संख्याएँ मॉडिफ़ायर की सीमाएँ समझाने के लिए हैं; ये नमूना स्ट्रिंग के मापे गए नतीजे नहीं हैं।

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

macOS 12 टारगेट के लिए बैकग्राउंड रीडर इस्तेमाल करें

macOS 12 डिप्लॉयमेंट टारगेट के लिए, चौड़ाई को प्रेफ़रेंस के ज़रिए भेजने हेतु बैकग्राउंड GeometryReader इस्तेमाल करें। नीचे इस्तेमाल की गई ट्रेलिंग-क्लोज़र वाली बैकग्राउंड API macOS 12 से उपलब्ध है। Apple बैकग्राउंड इस्तेमाल करने की सलाह देता है जब संशोधित View को लेआउट पर हावी होना चाहिए। background(alignment:content:) देखें।

यह एक पूरा वैकल्पिक प्रोग्राम है। दूसरी ऐप घोषणा जोड़ने के बजाय पिछले उदाहरण को इससे बदलें।

import SwiftUI
import Foundation

private struct BadgeWidthKey: PreferenceKey {
    static var defaultValue: CGFloat { 0 }

    static func reduce(
        value: inout CGFloat,
        nextValue: () -> CGFloat
    ) {
        value = nextValue()
    }
}

@main
struct LegacyWidthDemoApp: App {
    var body: some Scene {
        WindowGroup {
            LegacyWidthDemoView()
        }
    }
}

struct LegacyWidthDemoView: View {
    @State private var measuredWidth: CGFloat = 0
    @State private var showLongLabel = false

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text(showLongLabel ? "Blue circles collection" : "Blue circles")
                .font(.system(size: 16))
                .lineLimit(1)
                .padding(.horizontal, 12)
                .padding(.vertical, 8)
                .background(Color.blue.opacity(0.12))
                .background {
                    GeometryReader { proxy in
                        Color.clear.preference(
                            key: BadgeWidthKey.self,
                            value: proxy.size.width
                        )
                    }
                }
                .onPreferenceChange(BadgeWidthKey.self) { newWidth in
                    measuredWidth = newWidth
                }

            Text(String(
                format: "Measured badge: %.1f pt",
                Double(measuredWidth)
            ))
            .frame(width: 340, alignment: .leading)

            Button("Change sample label") {
                showLongLabel.toggle()
            }
        }
        .frame(width: 360, alignment: .leading)
        .padding(20)
    }
}

PreferenceKey मान को पूर्वज Views तक पहुँचाता है और onPreferenceChange उस मान में हुए बदलाव प्राप्त करता है। रीडर अपने content closure के भीतर state असाइन करने के बजाय माप भेजता है। Apple के PreferenceKey और onPreferenceChange(_:perform:) दस्तावेज़ देखें।

यह reducer जानबूझकर मानता है कि केवल एक बैज मान भेज रहा है। इसी key को कई चाइल्ड के लिए दोबारा इस्तेमाल करने पर उनके मान मिल जाएँगे और यह कार्यान्वयन उनमें से आख़िरी reduced मान रखेगा। अलग-अलग लेबल का स्वतंत्र हिसाब रखने के लिए, एक scalar को कई Views का प्रतिनिधि बनाने के बजाय मापों का key वाला संग्रह बनाएँ।

आप कैसे जाँच सकते हैं कि माप स्वतंत्र रहता है?

पहले प्रोग्राम को छोटी निदान प्रक्रिया के रूप में इस्तेमाल करें:

  1. Blue circles और पर्याप्त प्रस्तावित चौड़ाई से शुरू करें। रंगीन बैज की तुलना रीडआउट से करें।
  2. स्लाइडर खिसकाएँ। बाहरी फ़्रेम स्वतंत्र रूप से बदलेगा; पर्याप्त जगह रहने पर बैज कॉम्पैक्ट रह सकता है।
  3. Change sample label चुनें, फिर प्रस्तावित चौड़ाई घटाएँ। पूरे स्ट्रिंग की आदर्श चौड़ाई की अपेक्षा करने के बजाय मौजूदा सीमित बैज देखें।
  4. ऑब्ज़र्वर को स्पष्ट फ़्रेम के नीचे ले जाएँ। अब उसे उस फ़्रेम की चौड़ाई बतानी चाहिए।
  5. बैज का माप कहीं और इस्तेमाल करने से पहले ऑब्ज़र्वर को फ़्रेम के ऊपर वापस रखें।

मापी गई चौड़ाई को उसी View की चौड़ाई तय करने वाले सूत्र में शामिल न करें। उदाहरण के लिए, उसकी चौड़ाई को मापी गई चौड़ाई में पैडिंग जोड़कर सेट करने से बार-बार बड़ा मान लेआउट में वापस जा सकता है। यह आपके बनाए निर्भरता क्रम से होता है: निरीक्षण state बदलता है, state आकार बदलती है और नया आकार एक और निरीक्षण पैदा करता है।

अगर आपका असल काम पूरे लेबल और कॉम्पैक्ट विकल्प में से चुनना है, तो macOS 13 और उसके बाद के संस्करणों में ViewThatFits सीमित अक्षों में फिट होने वाले आदर्श आकार वाला पहला चाइल्ड चुन सकता है। इससे चौड़ाई को स्टोर करने की ज़रूरत ही खत्म हो सकती है। ViewThatFits देखें।

अगर कई चाइल्ड को साथ में मापना और स्थित करना है, तो macOS 13 और उसके बाद के संस्करणों में कस्टम Layout इस्तेमाल करें। इसके माप और प्लेसमेंट मेथड कंटेनर को सबव्यू स्थित करने से पहले उनसे पूछताछ करने देते हैं, जिससे यह निर्णय लेआउट प्रक्रिया के भीतर रहता है। Layout देखें।

क्या आदर्श आकार या AppKit टेक्स्ट मेट्रिक्स इसी सवाल का जवाब दे सकते हैं?

.fixedSize(horizontal: true, vertical: false) का इस्तेमाल तभी करें जब आप टेक्स्ट की आदर्श चौड़ाई बनाए रखना चाहते हों। Apple के दस्तावेज़ के अनुसार इससे टेक्स्ट अपने पैरेंट की सीमाओं से बाहर जा सकता है। माप पाने के लिए इसे जोड़ने से प्रदर्शित View का आकार तय होने का व्यवहार बदल जाता है, इसलिए मूल सीमित लेआउट जस का तस नहीं रहता। fixedSize(horizontal:vertical:) देखें।

चौड़ाई मापने के लिए अक्षरों की गिनती भी उपयुक्त नहीं है। Swift का String.count, विस्तारित ग्रैफ़ीम क्लस्टर से दर्शाए गए Character मान गिनता है; यह टाइपोग्राफ़िक आयाम नहीं देता। उस गिनती को अनुमानित अक्षर चौड़ाई से गुणा करने पर SwiftUI View का माप नहीं मिलता। Strings and Characters देखें।

AppKit में ड्रॉइंग के काम के लिए, NSAttributedString.size() उस एट्रिब्यूटेड स्ट्रिंग को ड्रॉ करने के लिए ज़रूरी आकार लौटाता है, जिसमें भिन्नात्मक आयाम भी हो सकते हैं। Apple View का आकार तय करते समय नतीजे को ऊपर की ओर राउंड करने की सलाह देता है। यह टेक्स्ट ड्रॉ करने के सवाल का जवाब देता है; इसमें SwiftUI View की पैडिंग या घेरने वाला फ़्रेम शामिल नहीं होता। size() देखें।

मौजूदा TextKit 1 लेआउट के लिए, NSLayoutManager.usedRect(for:) उसके टेक्स्ट कंटेनर में वर्तमान में लेआउट किए गए ग्लिफ़ द्वारा घेरा गया क्षेत्र बताता है। यह अपने आप ग्लिफ़ नहीं बनाता और न ही लेआउट करता है; अगर पहले लेआउट पूरा करना हो तो ensureLayout(for:) कॉल करें। ये API उस कॉन्फ़िगर किए गए टेक्स्ट कंटेनर को मापते हैं, इसलिए उनके नतीजे SwiftUI ज्योमेट्री से अलग रखें। usedRect(for:) और ensureLayout(for:) देखें।

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

स्रोत