लिपटे हुए NSTextView को SwiftUI फ़्रेम में फ़िट करने के लिए, NSScrollView को प्रस्तुत करें, sizeThatFits के ज़रिए सीमित प्रस्तावित आयाम स्वीकार करें, और टेक्स्ट दस्तावेज़ को उसके भीतर लंबवत बढ़ने दें। लंबे टेक्स्ट को लपेटने के लिए टेक्स्ट कंटेनर को एडिटर की चौड़ाई के अनुसार रखें।

यह ट्यूटोरियल आपके अपने Mac ऐप के लिए सीमित दृश्य क्षेत्र वाला सादा टेक्स्ट एडिटर बनाता है। टेक्स्ट जोड़ने पर SwiftUI लेआउट बड़ा हुए बिना लंबवत स्क्रॉल होना चाहिए। पूरा उदाहरण macOS 13 या उसके बाद के संस्करण को लक्ष्य करता है, जहाँ Apple यह representable sizing callback उपलब्ध कराता है। Apple का sizeThatFits दस्तावेज़ीकरण

NSTextView SwiftUI फ़्रेम की अनदेखी करता हुआ क्यों दिख सकता है?

तीन आकारों का तालमेल ज़रूरी है। SwiftUI प्रस्तुत AppKit व्यू का आकार तय करता है। NSScrollView दृश्य क्षेत्र देता है। उसका document view टेक्स्ट रखता है, जबकि NSTextContainer टेक्स्ट लेआउट के लिए उपलब्ध क्षेत्र तय करता है। Apple document view को उस व्यू के रूप में बताता है जिसके भीतर scroll view अपने content view में स्क्रॉल करता है। Apple का documentView दस्तावेज़ीकरण

SwiftUI का .frame(width:height:) एक बाहरी लेआउट फ़्रेम बनाता है। यह अपने आप टेक्स्ट कंटेनर के रैप होने के व्यवहार को कॉन्फ़िगर नहीं करता। Apple यह भी बताता है कि किसी child के bounds बाहरी फ़्रेम से आगे तक जा सकते हैं। इसलिए जाँच के लिए बॉर्डर उपयोगी है: प्रस्तुत सामग्री अलग तरह से व्यवहार करे, तब भी बॉर्डर के आयाम अनुरोध के अनुसार हो सकते हैं। Apple का फ़्रेम दस्तावेज़ीकरण

मुख्य बाधा स्वामित्व की है। Apple स्पष्ट रूप से कहता है कि SwiftUI प्रस्तुत AppKit व्यू के frame और bounds को नियंत्रित करता है; इन गुणों को स्वयं बदलना SwiftUI से टकराता है। इसके बजाय representable sizing callback के ज़रिए अपना पसंदीदा आकार लौटाएँ। इस कार्यान्वयन में प्रस्तुत व्यू scroll view है, जबकि AppKit उसके आंतरिक टेक्स्ट दस्तावेज़ को प्रबंधित करता है। Apple का NSViewRepresentable दस्तावेज़ीकरण

लेआउट फ़्लैग बदलने से पहले व्यवहार तय करें:

वांछित व्यवहार बाहरी आकार निर्धारण नीति टेक्स्ट दस्तावेज़ नीति
निश्चित एडिटर पैनल दिए गए चौड़ाई और ऊँचाई स्वीकार करें क्षैतिज रूप से टेक्स्ट लपेटें; लंबवत बढ़ाएँ और स्क्रॉल करें
सीमित पैन भरने वाला एडिटर पैन के सीमित प्रस्तावित आयाम स्वीकार करें क्षैतिज रूप से टेक्स्ट लपेटें; लंबवत बढ़ाएँ और स्क्रॉल करें
पूरा टेक्स्ट दिखाने के लिए फैलता एडिटर प्रस्तावित चौड़ाई के लिए ऊँचाई की गणना करें सामग्री मापन और लेआउट अपडेट का तालमेल रखें

पहली दो पंक्तियाँ इस निष्पादन योग्य उदाहरण का लक्ष्य हैं। तीसरी के लिए अलग content-height नीति चाहिए; प्रस्तावित ऊँचाई स्वीकार करने से यह व्यवहार लागू नहीं होता।

सीमित क्षेत्र वाला, टेक्स्ट लपेटने वाला एडिटर बनाएँ

Xcode में macOS SwiftUI ऐप बनाएँ और उसका deployment target macOS 13 या उसके बाद के संस्करण पर सेट करें। नीचे दी गई फ़ाइल को FramedTextEditor.swift के रूप में जोड़ें। दूसरा कोड ब्लॉक ऐप का entry point और नमूना सामग्री देता है; दोनों फ़ाइलें मिलकर पूरा उदाहरण बनाती हैं।

कॉन्फ़िगरेशन Apple के दस्तावेज़ित लंबवत स्क्रॉलिंग वाले विन्यास का अनुसरण करता है: text view लंबवत बढ़ता है, उसकी चौड़ाई उसके superview के अनुसार रहती है, और उसका text container text view की चौड़ाई के अनुसार रहता है। Apple की असेंबली गाइड संग्रहित है, इसलिए उदाहरण वर्तमान Swift APIs का उपयोग करता है और NSTextView(usingTextLayoutManager: true) के ज़रिए TextKit 2 स्पष्ट रूप से चुनता है। Apple अपने TextKit प्रस्तुतीकरण में initializer के इस विकल्प का दस्तावेज़ देता है। Apple की scrolling text-view गाइड, TextKit और text views में नया क्या है

import AppKit
import SwiftUI

@MainActor
struct FramedTextEditor: NSViewRepresentable {
    @Binding var text: String

    func makeCoordinator() -> Coordinator {
        Coordinator($text)
    }

    func makeNSView(context: Context) -> NSScrollView {
        let scrollView = NSScrollView()
        scrollView.borderType = .noBorder
        scrollView.hasVerticalScroller = true
        scrollView.hasHorizontalScroller = false
        scrollView.autohidesScrollers = true

        let textView = NSTextView(usingTextLayoutManager: true)
        // This is the internal document, not the represented root view.
        textView.setFrameSize(scrollView.contentSize)
        textView.minSize = .zero
        textView.maxSize = NSSize(
            width: CGFloat.greatestFiniteMagnitude,
            height: CGFloat.greatestFiniteMagnitude
        )
        textView.isVerticallyResizable = true
        textView.isHorizontallyResizable = false
        textView.autoresizingMask = [.width]
        textView.isRichText = false
        textView.importsGraphics = false
        textView.font = .systemFont(ofSize: 14)
        textView.textContainerInset = NSSize(width: 8, height: 8)

        guard let container = textView.textContainer else {
            preconditionFailure("Expected a text container")
        }
        container.size = NSSize(
            width: max(1, scrollView.contentSize.width),
            height: CGFloat.greatestFiniteMagnitude
        )
        container.widthTracksTextView = true
        container.heightTracksTextView = false

        textView.string = text
        textView.delegate = context.coordinator
        scrollView.documentView = textView
        return scrollView
    }

    func updateNSView(_ nsView: NSScrollView, context: Context) {
        context.coordinator.text = $text
        guard let textView = nsView.documentView as? NSTextView else {
            preconditionFailure("Expected an NSTextView document")
        }
        if textView.string != text {
            textView.string = text
        }
    }

    func sizeThatFits(
        _ proposal: ProposedViewSize,
        nsView: NSScrollView,
        context: Context
    ) -> CGSize? {
        let ideal = CGSize(width: 320, height: 180)
        let proposed = proposal.replacingUnspecifiedDimensions(by: ideal)
        return CGSize(
            width: proposed.width.isFinite
                ? max(0, proposed.width) : ideal.width,
            height: proposed.height.isFinite
                ? max(0, proposed.height) : ideal.height
        )
    }

    static func dismantleNSView(
        _ nsView: NSScrollView,
        coordinator: Coordinator
    ) {
        (nsView.documentView as? NSTextView)?.delegate = nil
    }

    @MainActor
    final class Coordinator: NSObject, NSTextViewDelegate {
        var text: Binding<String>

        init(_ text: Binding<String>) {
            self.text = text
            super.init()
        }

        func textDidChange(_ notification: Notification) {
            guard let textView = notification.object as? NSTextView else {
                return
            }
            if text.wrappedValue != textView.string {
                text.wrappedValue = textView.string
            }
        }
    }
}

जनरेट किए गए ऐप entry point और content view को इस दूसरी फ़ाइल से बदलें। लक्ष्य में केवल एक @main घोषणा रखें। सभी लेबल और टेक्स्ट काल्पनिक हैं, और उदाहरण पूरी तरह अपनी ऐप विंडो के भीतर चलता है।

import SwiftUI

@main
@MainActor
struct FrameSizingDemoApp: App {
    var body: some Scene {
        WindowGroup {
            EditorDemo()
        }
    }
}

@MainActor
struct EditorDemo: View {
    @State private var compact = false
    @State private var text = Array(
        repeating: "Sample note: arrange the blue tiles beside the green tiles.",
        count: 16
    ).joined(separator: "\n\n")

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            Toggle("Compact editor", isOn: $compact)

            FramedTextEditor(text: $text)
                .frame(width: compact ? 280 : 420, height: 180)
                .border(Color.secondary)

            Text("Character count: \(text.count)")
                .font(.caption)
        }
        .padding(20)
        .frame(minWidth: 460, minHeight: 280, alignment: .topLeading)
    }
}

इन फ़ाइलों का निरीक्षण किया गया है, लेकिन यहाँ इन्हें कंपाइल या चलाया नहीं गया: उपलब्ध वातावरण Linux है और इसमें Swift executable या macOS frameworks नहीं हैं। नीचे दिए गए सत्यापन क्रम के अनुसार Xcode में ऐप बनाएँ और जाँचें।

आकार निर्धारण और टेक्स्ट रैप करने की सेटिंग कैसे काम करती हैं?

दस्तावेज़ का मापन किए बिना सीमित प्रस्ताव स्वीकार करें

इस एडिटर के लिए, सीमित प्रस्तावित चौड़ाई और ऊँचाई पसंदीदा viewport आयाम बन जाते हैं। निर्दिष्ट न किए गए या असीमित आयामों के लिए 320 × 180 fallback केवल उदाहरण के लिए चुना गया डिज़ाइन विकल्प है। यह मापा हुआ टेक्स्ट आकार नहीं है। शून्य को शून्य ही रहने दें, ताकि न्यूनतम आकार की जाँच मनमाने धनात्मक न्यूनतम में न बदल जाए।

SwiftUI एक ही layout pass के दौरान अलग-अलग आकार पूछ सकता है। इसके मानक प्रस्तावों में न्यूनतम, अधिकतम और आदर्श आकार की पूछताछ शामिल है। यह नमूना जानबूझकर असीमित प्रस्ताव के लिए सीमित fallback देता है; इसे सीमित एडिटर पैनल के लिए बनाया गया है, हर तरह के कंटेनर के लिए सामान्य आकार निर्धारण नीति के रूप में नहीं। इसके बजाय nil लौटाने से SwiftUI का डिफ़ॉल्ट आकार निर्धारण एल्गोरिदम चुना जाएगा। Apple का ProposedViewSize दस्तावेज़ीकरण, Apple का representable sizing callback

यह callback scroll view का फ़्रेम नहीं बदलता, टेक्स्ट को नहीं बदलता, और SwiftUI state नहीं लिखता। इसका काम आकार संबंधी प्रश्न का उत्तर देना है। इसलिए इस कार्यान्वयन में टेक्स्ट की लंबाई बाहरी एडिटर की ऊँचाई तय नहीं करती।

चौड़ाई को viewport के अनुसार रखें और ऊँचाई को सामग्री के अनुसार चलने दें

चौड़ाई की दोनों सेटिंग अलग-अलग काम करती हैं। आंतरिक text view का .width autoresizing mask उसकी चौड़ाई को superview में बदलावों के अनुसार बदलता है। text container का widthTracksTextView = true उसकी layout चौड़ाई को text view में बदलावों के अनुसार बदलता है। एडिटर के संकरा होने पर दोनों संबंध महत्वपूर्ण होते हैं। Apple का autoresizingMask दस्तावेज़ीकरण, Apple का widthTracksTextView दस्तावेज़ीकरण

ऊर्ध्वाधर दिशा में, isVerticallyResizable = true document view को टेक्स्ट के अनुसार ऊँचाई बदलने देता है। बड़ी text-container ऊँचाई ऊर्ध्वाधर लेआउट के लिए जगह देती है, जबकि heightTracksTextView = false उस लेआउट सीमा को हर document-view resize से बाँधने से बचाता है। scroll view सीमित दृश्य क्षेत्र बना रहता है। Apple का vertical resizing दस्तावेज़ीकरण, Apple का heightTracksTextView दस्तावेज़ीकरण

बॉर्डर और टेक्स्ट के बीच कुछ जगह रहने की अपेक्षा करें। उदाहरण स्पष्ट रूप से आठ-पॉइंट का text-container inset जोड़ता है। टेक्स्ट में container की line-fragment padding से भी अतिरिक्त inset हो सकता है, इसलिए viewport की चौड़ाई या text view की चौड़ाई, दोनों में से कोई भी अक्षरों द्वारा घेरा गया सटीक क्षेत्र ज़रूरी नहीं है। Apple का textContainerInset दस्तावेज़ीकरण

टेक्स्ट अपडेट को आकार संबंधी बातचीत से अलग रखें

Coordinator textDidChange के ज़रिए संपादन बदलाव आगे भेजता है, और binding उन्हें parent के state से जोड़ती है। updateNSView SwiftUI से वापस आने वाले बदलाव संभालता है। इसकी equality जाँच यह सुनिश्चित करती है कि सामान्य संपादन के बाद वही मान लौटने पर एडिटर की string फिर से न बदली जाए। Apple का text-change delegate दस्तावेज़ीकरण, Apple का Binding दस्तावेज़ीकरण

आने वाली अलग string को पूरी सामग्री का प्रतिस्थापन माना जाता है। अगर आपका ऐप संपादन के दौरान भी प्रोग्राम के ज़रिए टेक्स्ट बदलता है, तो इस नमूने को बढ़ाने से पहले तय करें कि selection, undo history और जारी composition को कैसे सुरक्षित रखना है। प्रदर्शन वाला उदाहरण केवल एडिटर के ज़रिए string बदलता है।

उदाहरण अपने UI types और coordinator को @MainActor से चिह्नित करता है। Swift की concurrency गाइड main-actor isolation को UI state की सुरक्षा करने और चिह्नित कोड को उसी actor पर चलाने के लिए बाध्य करने वाले तंत्र के रूप में बताती है। The Swift Programming Language: Concurrency

नतीजे की पुष्टि करें और बाकी लेआउट समस्याएँ खोजें

नमूना ऐप में ये जाँचें करें। ये हर लक्षण को SwiftUI फ़्रेम की विफलता मानने के बजाय viewport का आकार, टेक्स्ट रैप होना, स्क्रॉलिंग और state सिंक्रनाइज़ेशन अलग-अलग जाँचती हैं।

  1. एडिटर का बॉर्डर देखें। अनुरोध किया गया शुरुआती एडिटर क्षेत्र 420 × 180 पॉइंट है।
  2. Compact editor चालू करें। चौड़ाई 280 पॉइंट होनी चाहिए, जबकि ऊँचाई 180 पॉइंट ही रहेगी। वाक्य अधिक पंक्तियों में लिपटने चाहिए।
  3. दिए गए पैराग्राफ़ों में स्क्रॉल करें। दस्तावेज़ उसी बॉर्डर के भीतर चलना चाहिए।
  4. अतिरिक्त पंक्तियाँ टाइप करें। एडिटर की अनुरोधित ऊँचाई बढ़े बिना अक्षरों की गिनती बदलनी चाहिए।
  5. टेक्स्ट मिटाएँ। दस्तावेज़ में बहुत कम सामग्री होने पर भी viewport को अपने अनुरोधित आयाम बनाए रखने चाहिए।
लक्षण पहले जाँचें
बॉर्डर का आकार सही है, लेकिन लंबी पंक्तियाँ नहीं लिपटतीं दस्तावेज़ का width autoresizing mask और widthTracksTextView जाँचें
टेक्स्ट जोड़ने से आसपास का लेआउट बड़ा हो जाता है पुष्टि करें कि representable scroll view लौटाता है और sizeThatFits से content height नहीं लौटाता
बाद की पंक्तियों तक पहुँचा नहीं जा सकता vertical scrolling, vertical document resizing और text-container height limit जाँचें
अलग parent layout में एडिटर संकरा रहता है उस parent द्वारा प्रस्तावित चौड़ाई देखें; callback सीमित प्रस्ताव स्वीकार करता है, अतिरिक्त जगह नहीं गढ़ता
असंबंधित state अपडेट के दौरान टेक्स्ट की स्थिति बदलती है textView.string को बिना शर्त असाइन करने की जाँच करें

ये जाँचें ऊपर बताए आकार संबंधों से निकला निदान क्रम हैं, रिपोर्ट किए गए runtime test परिणाम नहीं।

सामग्री-आधारित ऊँचाई या पुराने macOS संस्करणों के लिए क्या बदलता है?

हर पंक्ति दिखाने के लिए फैलने वाले एडिटर का काम अलग है। उसे प्रस्तावित चौड़ाई पर content-height गणना और किसी भी अधिकतम ऊँचाई के लिए नीति चाहिए। आंतरिक दस्तावेज़ में केवल vertical resizing चालू करने से SwiftUI को नई पसंदीदा ऊँचाई की सूचना नहीं मिलती। इसी तरह, प्रस्तावित ऊँचाई स्वीकार करना—जैसा यह उदाहरण करता है—टेक्स्ट से तय होने वाली ऊँचाई की गणना नहीं कर सकता।

मापन जोड़ते समय चुना हुआ text engine बनाए रखें। Apple बताता है कि NSTextView instance के layoutManager तक पहुँचने से TextKit 1 compatibility mode सक्रिय हो सकता है। text-container usage bounds के लिए इसका दस्तावेज़ित TextKit 2 समकक्ष NSTextLayoutManager.usageBoundsForTextContainer है। इस सीमित एडिटर को मापन के लिए इनमें से किसी API की ज़रूरत नहीं है। Apple का TextKit migration मार्गदर्शन

macOS संस्करण की सीमा भी मायने रखती है। NSViewRepresentable macOS 10.15 से उपलब्ध है, लेकिन इसका sizeThatFits callback macOS 13 में आवश्यक है। पुराने deployment target के लिए scrolling-document व्यवस्था बनाए रखें और पुराने representable के डिफ़ॉल्ट आकार निर्धारण व्यवहार का उपयोग करें, साथ में उस target के लिए AppKit sizing priorities या उपयुक्त container रखें। वहाँ इस callback से लेआउट नियंत्रित होने की अपेक्षा न करें; उस कार्यान्वयन को अलग से सत्यापित करें। Apple का representable दस्तावेज़ीकरण, Apple का sizing callback उपलब्धता दस्तावेज़ीकरण

सीमित Mac एडिटर के लिए ज़िम्मेदारियाँ स्पष्ट रखें: SwiftUI दृश्य आयत तय करता है, scroll view बढ़ते दस्तावेज़ को रखता है, और text container उपलब्ध चौड़ाई के अनुसार चलता है। यह व्यवस्था निश्चित ऊँचाई वाले एडिटर में टेक्स्ट मापन को शामिल किए बिना फ़्रेम आकार निर्धारण की समस्या पहचानने का ठोस तरीका देती है।

स्रोत