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

यह मार्गदर्शिका अपने macOS ऐप में एक साधारण क्लिक-एंड-ड्रैग आयत टूल बनाने वाले डेवलपर को लक्षित करती है। सभी उदाहरण आकृतियाँ मेमोरी में रहती हैं। लक्ष्य यह है कि पॉइंटर मूवमेंट से पूरे ड्राइंग मॉडल को बार-बार बदलने से रोका जाए, फिर किसी भी शेष रेंडरिंग लागत की पहचान के लिए प्रोफाइलिंग का उपयोग किया जाए।

प्रतिबद्ध आकृतियों के जमा होते ही ड्राइंग धीमी क्यों हो जाती है?

तीन कार्यों को अलग करके शुरू करें: इनपुट प्राप्त करना, स्थिति अपडेट करना, और ज्यामिति रेंडर करना। एक ड्रैग अस्थायी ज्यामिति को बार-बार बदल सकता है। यदि वे परिवर्तन प्रतिबद्ध संग्रह को भी प्रतिस्थापित करते हैं, हर पथ को पुनर्निर्मित करते हैं, या एक व्यापक दृश्य पदानुक्रम को अमान्य करते हैं, तो प्रत्येक मूवमेंट से जुड़ा कार्य ड्राइंग के साथ बढ़ सकता है।

Apple बताता है कि SwiftUI निर्भरता को ट्रैक करता है और जब वे निर्भरताएँ बदलती हैं तो दृश्यों को अपडेट करता है। धीमे व्यक्तिगत दृश्य अपडेट और कई अनावश्यक अपडेट दोनों फ्रेम डेडलाइन चूक का कारण बन सकते हैं। अक्सर बदलने वाली पूर्वावलोकन स्थिति को प्रतिबद्ध सामग्री से अलग करना उस प्रलेखित मॉडल का एक वास्तुशिल्प अनुप्रयोग है। Optimize SwiftUI performance with Instruments

Canvas एक ग्राफिक्स संदर्भ के माध्यम से तत्काल-मोड ड्राइंग प्रदान करता है। Apple इसे जटिल डायनामिक ड्रॉइंग के लिए संभावित प्रदर्शन सुधार के रूप में पहचानता है, लेकिन यह प्रत्येक ड्रॉन तत्व के लिए इंटरैक्शन या एक्सेसिबिलिटी प्रदान नहीं करता। इनपुट हैंडलिंग को एक जानबूझकर ड्राइंग सतह से संलग्न करें; यदि ऐप को बाद में व्यक्तिगत रूप से संचालन योग्य आकृतियों की आवश्यकता हो तो अलग चयन और एक्सेसिबिलिटी समर्थन लागू करें। Canvas

उपयोगी सीमा इसलिए स्पष्ट है: इनपुट सतह जेस्चर का स्वामित्व रखती है, अस्थायी स्थिति पूर्वावलोकन का स्वामित्व रखती है, और पैरेंट प्रतिबद्ध आयतों का स्वामित्व रखता है। इन जिम्मेदारियों को विभाजित करने से परिहार्य निर्भरताएँ कम होती हैं। यह गारंटी नहीं देता कि सिस्टम प्रतिबद्ध कैनवास को कभी पुनः रेंडर नहीं करेगा।

पूर्वावलोकन को प्रतिबद्ध ड्राइंग से अलग करें

रेंडरिंग अनुकूलन चुनने से पहले इस स्वामित्व योजना का उपयोग करें:

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

अस्थायी ज्यामिति के लिए, @GestureState उपयोगी है क्योंकि SwiftUI इसे रीसेट करता है जब जेस्चर निष्क्रिय हो जाता है। onEnded के माध्यम से कमिट करें, जिसका कॉलबैक अंतिम जेस्चर मान प्राप्त करता है। Apple निर्दिष्ट करता है कि यह कॉलबैक केवल तभी चलता है जब जेस्चर सफलतापूर्वक समाप्त होता है; जेस्चर स्थिति सफाई को संभालती है चाहे यह कैसे समाप्त हो। GestureState, onEnded(_:)

आयतों के लिए, अंतिम ज्यामिति के लिए केवल प्रारंभिक और वर्तमान स्थानों की आवश्यकता होती है। हर मध्यवर्ती पॉइंटर स्थान को बनाए रखने की कोई आवश्यकता नहीं है। Apple उन स्थितियों को अनुमानित अंत बिंदुओं से अलग दस्तावेज करता है; आयत को कमिट करते समय वास्तविक अंतिम स्थान का उपयोग करें। DragGesture.Value

एक पूर्ण इन-मेमोरी आयत टूल बनाएं

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

पहला ब्लॉक ऐप, प्रतिबद्ध संग्रह और उसके रेंडरर को परिभाषित करता है। पैरेंट केवल पूर्ण आयतों को प्राप्त करता है; यह सक्रिय ड्रैग स्थिति को नहीं पढ़ता।

import SwiftUI

@main
struct RectangleDrawingApp: App {
    var body: some Scene {
        WindowGroup {
            DrawingBoard()
        }
    }
}

struct DrawingBoard: View {
    @State private var rectangles: [CGRect] = []

    var body: some View {
        VStack(spacing: 8) {
            Text("Drag to draw a rectangle")
            Text("Committed rectangles: \(rectangles.count)")

            ZStack {
                Color.white

                CommittedRectangles(rectangles: rectangles)
                    .allowsHitTesting(false)

                RectangleGestureSurface { rectangle in
                    rectangles.append(rectangle)
                }
            }
            .clipped()
            .frame(minWidth: 480, minHeight: 320)
        }
        .padding()
    }
}

struct CommittedRectangles: View {
    let rectangles: [CGRect]

    var body: some View {
        Canvas { context, _ in
            for rectangle in rectangles {
                context.stroke(
                    Path(rectangle),
                    with: .color(.blue),
                    lineWidth: 2
                )
            }
        }
    }
}

निम्न ब्लॉक को उसी फ़ाइल में जोड़ें। पारदर्शी सतह ड्राइंग क्षेत्र को कवर करती है, हिट टेस्टिंग को एक स्पष्ट आयताकार आकार देती है, और एकमात्र ड्राइंग जेस्चर का स्वामित्व रखती है। Apple contentShape को हिट-टेस्टिंग आकार को परिभाषित करने के रूप में और allowsHitTesting(false) को एक दृश्य को हिट टेस्टिंग से बाहर करने के रूप में दस्तावेज करता है। contentShape(_:eoFill:), allowsHitTesting(_:)

struct RectangleGestureSurface: View {
    let commit: (CGRect) -> Void
    @GestureState private var preview: CGRect? = nil

    var body: some View {
        ZStack {
            Color.clear

            Canvas { context, _ in
                if let rectangle = preview {
                    context.stroke(
                        Path(rectangle),
                        with: .color(.orange),
                        lineWidth: 2
                    )
                }
            }
            .allowsHitTesting(false)
        }
        .contentShape(Rectangle())
        .gesture(
            DragGesture(minimumDistance: 0, coordinateSpace: .local)
                .updating($preview) { value, state, _ in
                    state = Self.rectangle(
                        from: value.startLocation,
                        to: value.location
                    )
                }
                .onEnded { value in
                    let rectangle = Self.rectangle(
                        from: value.startLocation,
                        to: value.location
                    )
                    commit(rectangle)
                }
        )
    }

    private static func rectangle(
        from start: CGPoint,
        to end: CGPoint
    ) -> CGRect {
        CGRect(
            x: min(start.x, end.x),
            y: min(start.y, end.y),
            width: abs(end.x - start.x),
            height: abs(end.y - start.y)
        )
    }
}

minimumDistance: 0 पहचान के लिए न्यूनतम यात्रा आवश्यकता को हटा देता है। स्थानीय निर्देशांक स्थान जेस्चर स्थितियों को समान आकार के पूर्वावलोकन कैनवास के साथ संरेखित रखता है। ये प्रलेखित जेस्चर कॉन्फ़िगरेशन विकल्प हैं, इवेंट आवृत्ति या डिस्प्ले विलंबता के बारे में गारंटी नहीं। DragGesture

आयत सहायक सभी चार ड्रैग दिशाओं को सामान्य करता है। एक उदाहरण जांच के रूप में, (90, 80) से (30, 20) तक ड्रैग करने से (30, 20) का मूल और (60, 60) का आकार उत्पन्न होता है। पूर्वावलोकन और कमिट समान गणना का उपयोग करते हैं, इसलिए पॉइंटर को छोड़ने से ज्यामिति सूत्र नहीं बदलता।

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

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

कैश जोड़ने से पहले शेष लागत का निदान करें

उदाहरण ड्रैग अपडेट पथ से प्रतिबद्ध-मॉडल उत्परिवर्तन को हटा देता है। इसका प्रतिबद्ध रेंडरर अभी भी संग्रह के माध्यम से लूप करता है जब भी वह रेंडरर निष्पादित होता है। कम स्थिति प्रसार को कम रेंडरिंग कार्य से अलग करें: एक अलग दृश्य एक प्रलेखित स्थायी छवि कैश नहीं है।

यह पता लगाने के लिए नियंत्रित तुलना का उपयोग करें कि कौन सी लागत शेष है। 0, 500, और 5,000 आयतों जैसी चित्रात्मक इन-मेमोरी आबादी चुनें। आयत आयाम, विंडो आकार, डिस्प्ले स्केल, और बिल्ड कॉन्फ़िगरेशन को सुसंगत रखें। ये संख्याएँ परीक्षण इनपुट हैं, क्षमता सीमाएँ या मापे गए परिणाम नहीं।

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

Apple का SwiftUI प्रोफाइलिंग सत्र Long View Body Updates, Long Representable Updates, और कारण और प्रभाव ग्राफ़ टूल्स का वर्णन करता है, साथ ही Time Profiler। निर्भरता ग्राफ़ का उपयोग यह जांचने के लिए करें कि ड्रैग के दौरान प्रतिबद्ध दृश्य क्यों अपडेट होते हैं; महंगी ज्यामिति निर्माण या रेंडरिंग कार्य खोजने के लिए CPU नमूनों का उपयोग करें। Apple ने उस इंस्ट्रूमेंट वर्कफ़्लो को Instruments 26 में पेश किया। Optimize SwiftUI performance with Instruments

व्यापक स्टाल के लिए, Apple का WWDC26 प्रतिक्रियाशीलता सत्र Time Profiler से शुरू होता है और व्यस्त निष्पादन को प्रतीक्षा में बिताए गए समय से अलग करता है। कार्य को एक्ज़ीक्यूटर्स के बीच ले जाने या रेंडरिंग स्टैक को बदलने से पहले उस अंतर को लागू करें। Profile, fix, and verify: Improve app responsiveness with Instruments

निम्न निर्णय सहायता उन प्रोफाइलिंग टूल्स और ऊपर के स्वामित्व डिज़ाइन से एक इंजीनियरिंग निष्कर्ष है:

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

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

कमिट सीमा पर सरणी कॉपी की जांच करें

Swift सरणियों में मूल्य शब्दार्थ होते हैं और कॉपी-ऑन-राइट स्टोरेज का उपयोग करते हैं। प्रतियां स्टोरेज साझा कर सकती हैं जब तक कि उत्परिवर्तन के लिए स्वतंत्र स्टोरेज की आवश्यकता न हो; क्लास इंस्टेंस वाली सरणियाँ अभी भी उन इंस्टेंस के संदर्भ साझा करती हैं। न तो किसी दृश्य को सरणी पास करना और न ही किसी सरणी में जोड़ना को हमेशा हर तत्व की कॉपी करने के रूप में वर्णित किया जाना चाहिए। Array

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

अतुल्यकालिक प्रस्तुति को एक मापे गए विकल्प के रूप में मानें

Apple rendersAsynchronously को कैनवास को अपनी सामग्री को अपने पैरेंट के सामने अतुल्यकालिक रूप से प्रस्तुत करने की अनुमति के रूप में परिभाषित करता है। वह विवरण तेज़ जेस्चर डिलीवरी का वादा नहीं करता, जेस्चर हैंडलर को बैकग्राउंड एक्ज़ीक्यूटर पर नहीं ले जाता, या संग्रह ट्रैवर्सल को समाप्त नहीं करता। इनपुट और स्थिति सीमाएँ स्थापित करने के बाद इसे अलग से मूल्यांकन करें। rendersAsynchronously

AppKit को ड्राइंग सतह का स्वामित्व कब लेना चाहिए?

जब कार्य के लिए सीधे रेस्पॉन्डर-मेथड हैंडलिंग या ड्राइंग क्षेत्रों की स्पष्ट अमान्यता की आवश्यकता हो तो एक कस्टम AppKit सतह का उपयोग करें। Apple NSView को ड्राइंग और इवेंट हैंडलिंग के लिए बुनियादी ढांचे के रूप में प्रलेखित करता है, और कस्टम व्यवहार के लिए इसे सबक्लास करने का समर्थन करता है। यह एक समर्थित विकल्प है, अपनी स्वयं की रेंडरिंग जिम्मेदारियों के साथ। NSView

AppKit कार्यान्वयन के लिए, समान क्रम को संरक्षित करें: mouseDown(with:) में अस्थायी ज्यामिति आरंभ करें, इसे mouseDragged(with:) में अपडेट करें, और mouseUp(with:) में कमिट करें। ज्यामिति की गणना से पहले event.locationInWindow को प्राप्त करने वाले दृश्य के निर्देशांक में बदलें। Apple की संग्रहीत माउस-इवेंट मार्गदर्शिका इस क्रम और निर्देशांक रूपांतरण को प्रलेखित करती है; वर्तमान NSView संदर्भ समर्थित रेस्पॉन्डर विधियों को प्रलेखित करता है। Handling Mouse Events, NSView

needsDisplay = true सेट करके डिस्प्ले का अनुरोध करें; Apple निर्दिष्ट करता है कि दृश्य को अगले इवेंट-लूप पास पर फिर से प्रदर्शित किया जाता है। क्षेत्र-आधारित डिज़ाइन के लिए, पुराने और नए पूर्वावलोकन बाउंड्स के संघ को अमान्य करें, स्ट्रोक को शामिल करने के लिए विस्तारित करें। क्षेत्रीय अमान्यता केवल ट्रैवर्सल को कम करती है यदि ड्राइंग कार्यान्वयन भी अप्रभावित सामग्री को संसाधित करने से बचाता है। needsDisplay, Handling Mouse Events

NSViewRepresentable के साथ सतह को एम्बेड करें: इसे makeNSView में बनाएं, बाहरी स्थिति को updateNSView में समेटें, और SwiftUI को परिवर्तन वापस संप्रेषित करने के लिए एक समन्वयक का उपयोग करें। अपडेट के दौरान कॉलबैक रूटिंग को ताज़ा करें, और केवल इसलिए एक सक्रिय ड्राफ्ट को रीसेट करने से बचें क्योंकि SwiftUI एक और प्रतिनिधित्व योग्य मान प्रदान करता है। Apple यह भी बताता है कि SwiftUI प्रतिनिधित्व किए गए दृश्य के लेआउट को नियंत्रित करता है; इसके प्रबंधित फ्रेम या बाउंड्स को सीधे ओवरराइट न करें। NSViewRepresentable

ड्राइंग इनपुट के लिए एक स्वामी चुनें। यदि AppKit ड्राइंग अनुक्रम को संभालता है, तो उस अनुक्रम को ओवरलैपिंग SwiftUI ड्रैग जेस्चर के माध्यम से भी कमिट न करें। सीधे NSView सबक्लास के लिए, Apple उन कार्यान्वयनों को निर्देश देता है जो सूचीबद्ध माउस इवेंट्स को संभालते हैं, उन संभाले गए इवेंट्स के लिए सुपरक्लास कार्यान्वयन को कॉल न करें। NSView

पहला सुधार छोटा और अवलोकन योग्य रखें: स्थानीय पूर्वावलोकन स्थिति, एकल कमिट सीमा, और अपरिवर्तित इनपुट के साथ एक प्रतिबद्ध रेंडरर। फिर Instruments में बढ़ते सिंथेटिक ड्रॉइंग की तुलना करें। परिणामी ट्रेस आपको बताता है कि अगला उपयोगी परिवर्तन निर्भरता प्रबंधन, संग्रह स्वामित्व, ज्यामिति तैयारी, या रेंडरिंग से संबंधित है या नहीं।

स्रोत