जब आपका कस्टम Mac सरफ़ेस कोई नामित कार्रवाई करता है, तो SwiftUI Button का उपयोग करें। जब इंटरैक्शन के लिए जेस्चर की जानकारी चाहिए, जैसे कैनवस पर स्थान, तो क्लिक जेस्चर का उपयोग करें। यह गाइड एक छोटा आकार संपादक बनाकर दिखाती है कि किसका उपयोग कहाँ करना चाहिए।

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

आपके सरफ़ेस को किस इंटरैक्शन की ज़रूरत है?

Mac ऐप बनाने वाले डेवलपर के लिए उपयोगी सवाल यह है कि इंटरैक्शन से कौन-सी जानकारी मिलनी चाहिए। Add circle लेबल वाली टाइल एक कमांड दर्शाती है। खाली ड्रॉइंग सरफ़ेस पर क्लिक करके वहाँ वृत्त रखना, कमांड के साथ स्थान भी दर्शाता है। यह अतिरिक्त इनपुट सही कार्यान्वयन को बदल देता है।

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

पाठक का अपेक्षित इंटरैक्शन शुरुआत यहाँ से करें कारण
Add circle लेबल वाली कस्टम टाइल पर क्लिक करना कस्टम लेबल वाला Button यह सरफ़ेस नामित कार्रवाई शुरू करता है।
टाइल को अलग पृष्ठभूमि और दबाने पर अलग रूप देना ButtonStyle यह मानक बटन इंटरैक्शन को बनाए रखते हुए रूप बदलता है।
कैनवस पर क्लिक करके उस बिंदु पर वृत्त रखना SpatialTapGesture इसके परिणाम में इंटरैक्शन का स्थान शामिल होता है।
निर्देशांक की ज़रूरत के बिना किसी कंटेंट व्यू पर दो क्लिक पहचानना onTapGesture(count: 2) यह मॉडिफ़ायर आवश्यक क्लिक संख्या स्वीकार करता है।
AppKit इंटरैक्शन में खास माउस बटन कॉन्फ़िगर करना NSClickGestureRecognizer इसमें माउस बटन और क्लिक संख्या कॉन्फ़िगर की जा सकती है।

Apple बटन को लेबल के साथ जुड़ी कार्रवाई के रूप में दस्तावेज़ित करता है। वह ButtonStyle के ज़रिए रूप बदलने और PrimitiveButtonStyle के ज़रिए इंटरैक्शन बदलने के बीच भेद करता है। उसके जेस्चर API में क्लिक संख्या और स्थानिक परिणाम अलग से दस्तावेज़ित हैं। Button, onTapGesture(count:perform:), SpatialTapGesture, NSClickGestureRecognizer

इसलिए कस्टम एक्शन टाइल की शुरुआत बटन से करें। जेस्चर तब चुनें जब आप बता सकें कि सरफ़ेस को किस अतिरिक्त इनपुट या पहचान क्षमता की ज़रूरत है।

व्यवहार को दोबारा बनाए बिना कस्टम बटन कैसे बनाएँ?

कमांड को बटन के एक्शन क्लोज़र में और ग्राफ़िक्स को उसके लेबल में रखें। दिखावट के लिए ButtonStyle इस्तेमाल करें। Apple के दस्तावेज़ के अनुसार, यह प्रोटोकॉल प्लेटफ़ॉर्म का मानक इंटरैक्शन व्यवहार बनाए रखता है; PrimitiveButtonStyle इंटरैक्शन व्यवहार जानबूझकर बदलने का अलग विस्तार बिंदु है। ButtonStyle

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

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

पूरा ऐप इन दो फ़ाइलों से बना है। पहली, ShapeSurfaceApp.swift:

import SwiftUI
import Foundation

@main
struct ShapeSurfaceApp: App {
    var body: some Scene {
        WindowGroup {
            ShapeSurfaceView()
        }
    }
}

private struct PlacedCircle: Identifiable {
    let id = UUID()
    let center: CGPoint
}

struct ShapeSurfaceView: View {
    @State private var circles: [PlacedCircle] = []
    private let limit = 6

    private var isFull: Bool {
        circles.count >= limit
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Button {
                let x = 35 + CGFloat(circles.count) * 50
                addCircle(at: CGPoint(x: x, y: 100))
            } label: {
                HStack(spacing: 12) {
                    Circle()
                        .fill(Color.blue)
                        .frame(width: 24, height: 24)
                    Text("Add circle")
                }
                .frame(width: 280, height: 64)
                .contentShape(RoundedRectangle(cornerRadius: 12))
            }
            .buttonStyle(SurfaceButtonStyle())
            .keyboardShortcut("a", modifiers: [.command, .shift])
            .disabled(isFull)
            .opacity(isFull ? 0.4 : 1)

            Text("Click the canvas to place a circle. Maximum: 6.")

            ZStack(alignment: .topLeading) {
                Color.gray.opacity(0.1)

                ForEach(circles) { circle in
                    Circle()
                        .fill(Color.blue)
                        .frame(width: 24, height: 24)
                        .position(circle.center)
                }
            }
            .frame(width: 320, height: 200)
            .contentShape(Rectangle())
            .gesture(
                SpatialTapGesture()
                    .onEnded { value in
                        addCircle(at: value.location)
                    }
            )
            .disabled(isFull)
            .border(Color.gray)
            .accessibilityElement(children: .ignore)
            .accessibilityLabel("Circle canvas")
            .accessibilityValue("\(circles.count) of \(limit) circles")

            Text(isFull
                 ? "Six circles placed. Clear to start again."
                 : "\(circles.count) of \(limit) circles placed")

            Button("Clear circles") {
                circles.removeAll()
            }
            .disabled(circles.isEmpty)
        }
        .padding(24)
    }

    private func addCircle(at point: CGPoint) {
        guard !isFull else { return }

        // Keep each 24-point circle inside the 320-by-200 canvas.
        let center = CGPoint(
            x: min(max(point.x, 12), 308),
            y: min(max(point.y, 12), 188)
        )
        circles.append(PlacedCircle(center: center))
    }
}

दूसरी, SurfaceButtonStyle.swift:

import SwiftUI

struct SurfaceButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .background(
                RoundedRectangle(cornerRadius: 12)
                    .fill(Color.blue.opacity(
                        configuration.isPressed ? 0.25 : 0.1
                    ))
            )
    }
}

स्टाइल फ़ीडबैक दिखाने के लिए configuration.isPressed पढ़ती है; यह कभी खुद एक्शन कॉल नहीं करती। Apple इस प्रॉपर्टी को बटन की मौजूदा दबाई गई अवस्था के रूप में परिभाषित करता है। इससे रेंडरिंग और एक्शन चलाने के काम अलग-अलग जगह रहते हैं। isPressed

छह वृत्तों की सीमा, ऐप के उदाहरण के लिए रखा गया एक नियम है। दोनों इनपुट रास्ते addCircle(at:) का उपयोग करते हैं, जो जोड़ने से पहले क्षमता जाँचता है। निष्क्रिय दिखावट सीमा का संकेत देती है, और साझा गार्ड बदलाव का नियम एकसमान रखता है। Apple disabled(_:) को ऐसा मॉडिफ़ायर बताता है जो व्यू के साथ इंटरैक्शन, जैसे क्लिक और जेस्चर, नियंत्रित करता है। disabled(_:)

कैनवस में स्थानिक क्लिक जेस्चर क्यों है?

कैनवस को उस बिंदु की ज़रूरत है जहाँ उपयोगकर्ता ने क्लिक किया। बुनियादी onTapGesture(count:perform:) ओवरलोड एक्शन देता है, लेकिन स्थान पैरामीटर नहीं। SpatialTapGesture स्पष्ट रूप से स्थान बताता है, और इसका इनिशियलाइज़र डिफ़ॉल्ट रूप से स्थानीय कोऑर्डिनेट स्पेस में एक इंटरैक्शन लेता है। onTapGesture(count:perform:), SpatialTapGesture, init(count:coordinateSpace:)

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

बटन और कैनवस का कीबोर्ड व्यवहार भी जानबूझकर अलग है। बटन का शॉर्टकट उसी बटन को सक्रिय करने जैसी कार्रवाई करता है और अगले स्लॉट में वृत्त जोड़ता है। यह पॉइंटर स्थान नहीं बनाता। Apple के दस्तावेज़ के अनुसार, कीबोर्ड शॉर्टकट किसी कंट्रोल की प्राथमिक कार्रवाई चलाते हैं। वह यह भी बताता है कि एक ही शॉर्टकट कई कंट्रोल को देने पर पहला मेल खाने वाला कंट्रोल चुना जाता है। उदाहरण वाली विंडो में यह शॉर्टकट एक ही बार दें। keyboardShortcut(_:modifiers:)

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

हिट टेस्टिंग को कस्टम सरफ़ेस से कैसे मेल कराएँ?

लेबल का आकार तय करने के बाद इच्छित इंटरैक्शन क्षेत्र तय करें। उदाहरण में बटन लेबल का फ़्रेम 280-बाय-64 है और उसके बाद गोल आयताकार contentShape है। कैनवस पहले अपना 320-बाय-200 फ़्रेम तय करता है और फिर आयताकार कंटेंट शेप घोषित करता है। Apple contentShape(_:eoFill:) को हिट टेस्टिंग में इस्तेमाल होने वाले आकार के रूप में परिभाषित करता है। contentShape(_:eoFill:)

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

जब कोई सरफ़ेस प्रतिक्रिया देना बंद करे, तो इन बातों को क्रम से जाँचें:

  1. ज्यामिति जाँचें। लेबल के टेक्स्ट, ग्राफ़िक और खाली अंदरूनी हिस्से पर क्लिक करें। पुष्टि करें कि कंटेंट शेप इच्छित सीमाएँ बताता है।
  2. निष्क्रिय पूर्वज व्यू जाँचें। निष्क्रिय कंटेनर अपने अंदर के सक्रिय व्यू को भी निष्क्रिय कर देता है, इसलिए स्थानीय .disabled(false) शायद इंटरैक्शन बहाल न कर पाए। disabled(_:)
  3. सजावटी ओवरले जाँचें। अगर कोई व्यू केवल सजावट के लिए है, तो उसे .allowsHitTesting(false) के साथ हिट टेस्टिंग से बाहर रखा जा सकता है। इसे सजावट पर लागू करें, इंटरैक्टिव सरफ़ेस पर नहीं। allowsHitTesting(_:)
  4. एक-दूसरे से टकराते हैंडलर जाँचें। जेस्चर की प्राथमिकता बदलने से पहले तय करें कि कार्रवाई किस व्यू की ज़िम्मेदारी है।

एक-दूसरे से टकराते हैंडलर और स्वामित्व की गलतियों से कैसे बचें?

हर अपेक्षित सक्रियण रास्ते के लिए एक हैंडलर इस्तेमाल करें। उदाहरण में कस्टम बटन अपनी कमांड संभालता है और कैनवस स्थान-आधारित प्लेसमेंट संभालता है। इनके पैरेंट पर कोई दूसरा प्लेसमेंट जेस्चर नहीं लगा है। इस संरचना में एक क्लिक का इच्छित असर समझना आसान रहता है।

सिर्फ़ उसी बदलाव फ़ंक्शन तक पहुँचने के लिए बटन पर दूसरा टैप हैंडलर न जोड़ें। इसी तरह, कई कंट्रोल के चारों ओर पैरेंट जेस्चर जोड़ने से पहले तय करें कि पैरेंट और चाइल्ड—दोनों का इंटरैक्शन होना चाहिए या नहीं। Apple का simultaneousGesture उदाहरण दिखाता है कि पैरेंट और चाइल्ड हैंडलर दोनों चल सकते हैं। इसके बजाय highPriorityGesture जुड़े हुए जेस्चर को अधिक प्राथमिकता देता है। ये मॉडिफ़ायर इंटरैक्शन नीति तय करते हैं; एक-दूसरे की जगह इस्तेमाल किए जा सकने वाले सुधार नहीं हैं। simultaneousGesture(_:including:), highPriorityGesture(_:including:)

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

अगर बाद में कोई AppKit अडैप्टर कॉलबैक स्टोर करे, तो उसके कैप्चर संबंध अलग से जाँचें। Swift क्लोज़र आसपास के मान कैप्चर कर सकते हैं; क्लोज़र स्टोर करने वाला ऐसा क्लास, जिसका क्लोज़र उसी इंस्टेंस को कैप्चर करे, मज़बूत रेफ़रेंस चक्र बना सकता है। वास्तविक ऑब्जेक्ट जीवनकाल के अनुसार कैप्चर लिस्ट चुनें। बटन को जेस्चर में बदलने से कॉलबैक का स्वामित्व हल नहीं होता। Closures

AppKit अगला उपयुक्त कदम कब है?

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

डिप्लॉयमेंट टार्गेट और आपके स्वामित्व वाले कंपोनेंट के अनुसार इंटीग्रेशन रास्ता चुनें:

  • macOS 26 या उसके बाद, NSGestureRecognizerRepresentable के ज़रिए .gesture(...) AppKit रिकग्नाइज़र को सीधे SwiftUI पदानुक्रम में जोड़ता है। SwiftUI एक्शन टार्गेट इंस्टॉल करता है; रिप्रेज़ेंटेबल का एक्शन हैंडलर लागू करें और निर्देशांक बदलने के लिए उसका कॉन्टेक्स्ट कन्वर्टर इस्तेमाल करें। Apple चेतावनी देता है कि रिकग्नाइज़र के साथ जुड़ा व्यू, SwiftUI व्यू से अलग ज्यामिति रख सकता है। NSGestureRecognizerRepresentable
  • SwiftUI में होस्ट किए गए AppKit व्यू के लिए, NSViewRepresentable आपके व्यू को बनाने, अपडेट करने और हटाने का प्रबंधन करता है। कॉलबैक के लिए उसका कोऑर्डिनेटर इस्तेमाल करें और SwiftUI को रिप्रेज़ेंट किए गए व्यू का लेआउट नियंत्रित करने दें। यह API macOS 10.15 से उपलब्ध है। NSViewRepresentable

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

तैयार Mac इंटरैक्शन में क्या जाँचें?

उदाहरण को Xcode में बिल्ड करें और बड़े व्यू पदानुक्रम में ढालने से पहले दिखाई देने वाले व्यवहार जाँचें:

  • कस्टम बटन के टेक्स्ट और खाली अंदरूनी हिस्से पर क्लिक करें। हर सक्रियण से एक वृत्त जुड़ना चाहिए और गिनती एक बार अपडेट होनी चाहिए।
  • सक्रिय विंडो में Command–Shift–A दबाएँ। इससे बटन की स्लॉट-आधारित कार्रवाई होनी चाहिए।
  • कैनवस के अलग-अलग स्थानों पर, किनारों के पास भी, क्लिक करें। कोड में बताए गए मार्जिन के साथ वृत्त स्थानीय निर्देशांकों के अनुसार आने चाहिए।
  • छह वृत्तों तक पहुँचें। प्लेसमेंट के दोनों रास्तों से और वृत्त जुड़ना रुकना चाहिए, जबकि Clear circles उपलब्ध रहना चाहिए।
  • कैनवस साफ़ करें। प्लेसमेंट फिर उपलब्ध होना चाहिए।
  • VoiceOver में बटन देखें और Mac की मौजूदा सेटिंग के अनुसार कीबोर्ड नेविगेशन जाँचें। स्टाइल देने के बाद भी सुनिश्चित करें कि उसकी कार्रवाई समझ में आती है।

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

स्रोत