आस्पेक्ट-फिट किए गए SwiftUI डायग्राम के साथ ओवरले संरेखित रखने के लिए, फिट किए गए डायग्राम को ही मापें और मार्करों को उसी स्थानीय निर्देशांक क्षेत्र में रखें। खाली जगह जोड़ने वाले किसी भी बाहरी फ़्रेम से पहले मापन ओवरले जोड़ें।
इस ट्यूटोरियल में एक कृत्रिम डायग्राम और दो क्रमांकित मार्करों वाला छोटा macOS ऐप बनाया गया है। उद्देश्य यह है कि विंडो का आकार बदलने पर भी हर मार्कर अपने डायग्राम फ़ीचर के केंद्र पर बना रहे। इसमें सामान्यीकृत निर्देशांकों का उपयोग किया गया है, इसलिए एनोटेशन की स्थिति विंडो के आयामों से स्वतंत्र रहती है।
आस्पेक्ट-फिट किए गए डायग्राम से ओवरले क्यों खिसक जाते हैं?
आस्पेक्ट फ़िट उपलब्ध जगह में डायग्राम को समायोजित करते हुए उसके अनुपात बनाए रखता है। कंटेनर के अनुपात के आधार पर, इससे डायग्राम के ऊपर-नीचे या दोनों ओर खाली जगह रह सकती है। Apple इस व्यवहार को उपलब्ध जगह में इमेज फ़िट करना में दिखाता है।
जब आर्टवर्क और उसके मार्कर अलग-अलग आयतों का उपयोग करते हैं, तब वे खिसक जाते हैं। उदाहरण के लिए, 600 × 700 पॉइंट के कंटेनर के भीतर आर्टवर्क 600 × 375 पॉइंट की केंद्रित आयत में हो सकता है। यदि मार्कर की स्थिति कंटेनर की ऊँचाई के अंश से तय की जाए, तो गणना में आसपास की खाली जगह भी शामिल हो जाती है।
मॉडिफ़ायर का क्रम तय करता है कि आप किस आयत को मापते हैं। Apple बताता है कि व्यू मॉडिफ़ायर उस व्यू को अपने भीतर लपेटते हैं जिस पर वे लागू होते हैं, और फ़्रेम अपने चाइल्ड को किसी दूसरी आयत के भीतर रखता है। इसलिए किसी फैलाए गए फ़्रेम के बाद जोड़ा गया ओवरले, सीधे फ़िट किए गए आर्टवर्क पर जोड़े गए ओवरले से अलग लेआउट सीमा को मापता है। Apple का View दस्तावेज़ और लचीले फ़्रेम का दस्तावेज़ देखें।
इस काम के लिए, यह क्रम अपनाएँ:
- लचीला आर्टवर्क बनाएँ।
.fitका उपयोग करके उसका स्पष्ट आस्पेक्ट अनुपात लागू करें।GeometryReaderवाला ओवरले जोड़ें।- उस रीडर के आकार का उपयोग करके मार्करों की स्थिति तय करें।
- बाहरी फ़्रेम और आसपास की पैडिंग जोड़ें।
Apple ओवरले का उपयोग तब करने की सलाह देता है जब संशोधित व्यू को लेआउट पर हावी होना चाहिए। इससे डायग्राम लेआउट का आधार बनता है और एनोटेशन उसके ऊपर परत के रूप में आते हैं। ZStack में सिबलिंग व्यू रखने का लेआउट व्यवहार अलग होता है; उसमें यह तय करने पर अधिक ध्यान देना पड़ता है कि आकार कौन तय कर रहा है। overlay(alignment:content:) देखें।
पूरा macOS उदाहरण बनाएँ
Xcode में SwiftUI इंटरफ़ेस वाला macOS ऐप प्रोजेक्ट बनाएँ। इसका डिप्लॉयमेंट टार्गेट macOS 12 या उसके बाद का रखें, क्योंकि यहाँ इस्तेमाल किया गया क्लोज़र-आधारित ओवरले API macOS 12 से उपलब्ध है। जनरेट की गई ऐप एंट्री-पॉइंट फ़ाइल को नीचे दिए गए पूरे प्रोग्राम से बदलें। टार्गेट में केवल एक @main ऐप घोषणा रखें। API की उपलब्धता overlay(alignment:content:) में दर्ज है।
यह कृत्रिम आर्टवर्क 800 × 500 के डिज़ाइन क्षेत्र को दर्शाता है, जिससे इसका आस्पेक्ट अनुपात 1.6 होता है। मार्कर निर्देशांक उस क्षेत्र के अंश हैं: क्षैतिज शून्य बायाँ किनारा है, ऊर्ध्वाधर शून्य ऊपरी किनारा है, और एक विपरीत किनारे को दर्शाता है। किसी एसेट फ़ाइल या बाहरी पैकेज की आवश्यकता नहीं है।
import SwiftUI
@main
struct DiagramAlignmentApp: App {
var body: some Scene {
WindowGroup {
DiagramExample()
.frame(minWidth: 360, minHeight: 260)
}
}
}
struct DiagramMarker: Identifiable {
let id: Int
let label: String
let x: CGFloat
let y: CGFloat
}
struct DiagramExample: View {
let markers = [
DiagramMarker(id: 1, label: "1", x: 0.25, y: 0.40),
DiagramMarker(id: 2, label: "2", x: 0.75, y: 0.60)
]
var body: some View {
Rectangle()
.fill(Color.white)
.overlay {
DiagramArtwork()
.stroke(Color.gray, lineWidth: 2)
}
.aspectRatio(800.0 / 500.0, contentMode: .fit)
.overlay {
GeometryReader { geometry in
ForEach(markers) { marker in
Circle()
.fill(Color.blue)
.frame(width: 28, height: 28)
.overlay {
Text(marker.label)
.font(.caption.bold())
.foregroundColor(.white)
}
.position(
x: marker.x * geometry.size.width,
y: marker.y * geometry.size.height
)
}
}
}
.border(Color.gray)
.frame(
maxWidth: .infinity,
maxHeight: .infinity,
alignment: .center
)
.padding(24)
.background(Color.gray.opacity(0.12))
}
}
struct DiagramArtwork: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.addRect(CGRect(
x: rect.minX + 0.10 * rect.width,
y: rect.minY + 0.20 * rect.height,
width: 0.30 * rect.width,
height: 0.40 * rect.height
))
path.addRect(CGRect(
x: rect.minX + 0.60 * rect.width,
y: rect.minY + 0.40 * rect.height,
width: 0.30 * rect.width,
height: 0.40 * rect.height
))
path.move(to: CGPoint(
x: rect.minX + 0.25 * rect.width,
y: rect.minY + 0.40 * rect.height
))
path.addLine(to: CGPoint(
x: rect.minX + 0.75 * rect.width,
y: rect.minY + 0.60 * rect.height
))
return path
}
}
ऐप चलाएँ और विंडो का आकार क्षैतिज तथा ऊर्ध्वाधर दोनों दिशाओं में बदलें। मार्कर 1 पहले आयत के केंद्र पर और मार्कर 2 दूसरे आयत के केंद्र पर बना रहना चाहिए। पतली सीमा फ़िट किए गए डायग्राम की सीमा दिखाती है; इसके आसपास का धूसर क्षेत्र बाहरी लेआउट का हिस्सा है।
यह मॉडिफ़ायर क्रम क्यों काम करता है
आधार आयत लचीला आर्टवर्क देती है, और .aspectRatio(800.0 / 500.0, contentMode: .fit) उसके अनुपात को सीमित करता है। Apple आस्पेक्ट-रैशियो तर्क को चौड़ाई को ऊँचाई से भाग देकर मिलने वाला मान परिभाषित करता है और बताता है कि फ़िट किया गया परिणाम रिसाइज़ किए जा सकने वाले कंटेंट पर लागू होता है। aspectRatio(_:contentMode:) देखें।
मापन ओवरले इस सीमा के तुरंत बाद आता है। GeometryReader अपने आकार और निर्देशांक क्षेत्र के आधार पर अपना कंटेंट तय करता है, जबकि geometry.size कंटेनर का आकार बताता है। यहाँ, वह कंटेनर फ़िट किए गए आयत को घेरता है। इसलिए सामान्यीकृत निर्देशांकों को उसकी चौड़ाई और ऊँचाई से गुणा करने पर स्थानीय मार्कर स्थितियाँ मिलती हैं। GeometryReader और GeometryProxy.size देखें।
अंतिम फैलाया गया फ़्रेम, डायग्राम और मार्करों की पूरी संरचना को उपलब्ध क्षेत्र के बीच में रखता है। चूँकि मार्कर पहले से ही भीतरी आयत से जुड़े हैं, उन्हें बाहरी खाली हाशियों की गणना करने की ज़रूरत नहीं होती।
ड्रॉइंग और स्थिति निर्धारण के नियम बनाए रखें
DiagramArtwork, Shape के अनुरूप है, और इसका path(in:) मेथड दिए गए आयत के भीतर व्यक्त Path लौटाता है। यह कोई व्यू नहीं लौटाता और लेआउट में बदलाव नहीं करता। यह Apple के दस्तावेज़ में बताए गए path(in:) के नियम के अनुरूप है।
हर मार्कर का .position(x:y:) उसके केंद्र को गणना से मिली जगह पर रखता है। यह वहाँ उसका ऊपरी-बायाँ कोना नहीं रखता। उदाहरण में, प्रत्येक क्रमांकित वृत्त का केंद्र जान-बूझकर किसी फ़ीचर पर रखा गया है, जो Apple की position(x:y:) परिभाषा के अनुरूप है।
विंडो का आकार बदलने पर वृत्त 28 पॉइंट चौड़े बने रहते हैं, जबकि उनके केंद्र आर्टवर्क के साथ चलते हैं। पढ़ने में आसान एनोटेशन के लिए यह जान-बूझकर चुना गया प्रस्तुतीकरण है। यदि मार्कर के आयाम ड्रॉइंग के भीतर की दूरियाँ दर्शाते हैं, तो उन आयामों की गणना भी फ़िट किए गए आकार से करें।
गणना के उदाहरण से संरेखण जाँचें
संख्यात्मक जाँच से निर्देशांक की गलती और शैली की समस्या में अंतर करना आसान होता है। केंद्रित आस्पेक्ट फ़िट के लिए, स्रोत के धनात्मक आयाम sourceWidth और sourceHeight, तथा उपलब्ध सीमित आयाम containerWidth और containerHeight मानें। नीचे दिया गया कोड गणितीय छद्म-कोड है, अलग Swift प्रोग्राम नहीं:
scale = min(containerWidth / sourceWidth,
containerHeight / sourceHeight)
fittedWidth = sourceWidth * scale
fittedHeight = sourceHeight * scale
originX = (containerWidth - fittedWidth) / 2
originY = (containerHeight - fittedHeight) / 2
containerX = originX + normalizedX * fittedWidth
containerY = originY + normalizedY * fittedHeight
ये समीकरण दस्तावेज़ में बताए गए फ़िट व्यवहार से एकसमान फ़िट और केंद्रित स्थिति निकालते हैं। उदाहरण के लिए, 800 × 500 स्रोत और (0.25, 0.40) पर स्थित मार्कर 1 से ये परिणाम मिलते हैं। यहाँ कंटेनर के आयाम आसपास की पैडिंग के बाद उपलब्ध क्षेत्र को दर्शाते हैं।
| उपलब्ध कंटेनर | फ़िट किया गया डायग्राम | कंटेनर में डायग्राम का मूल बिंदु | कंटेनर में मार्कर का केंद्र |
|---|---|---|---|
| 1000 × 700 | 1000 × 625 | (0, 37.5) | (250, 287.5) |
| 600 × 700 | 600 × 375 | (0, 162.5) | (150, 312.5) |
| 1000 × 400 | 640 × 400 | (180, 0) | (340, 160) |
बीच वाली पंक्ति में, कंटेनर की पूरी ऊँचाई को 0.40 से गुणा करने पर 280 मिलता है, जो 312.5 की सही ऊर्ध्वाधर स्थिति से मेल नहीं खाता। उदाहरण के स्थानीय ओवरले में, ऊर्ध्वाधर निर्देशांक सीधे 0.40 × 375 = 150 होता है; घिरा हुआ लेआउट 162.5 पॉइंट का हाशिया जोड़ता है।
तालिका की गणना एक छोटे, नियतात्मक Python हिसाब से जाँची गई। SwiftUI प्रोग्राम की समीक्षा की गई, लेकिन इस परिवेश में उसे कंपाइल नहीं किया गया; लक्ष्य Mac पर Xcode में उसका रेंडरिंग जाँचें।
इस तरीके को मौजूदा डायग्राम पर लागू करें
इमेज पर आधारित डायग्राम के लिए, आस्पेक्ट-फ़िट और ओवरले मॉडिफ़ायर लगाने से पहले Image को रिसाइज़ करने योग्य बनाएँ। Apple की इमेज-फ़िटिंग गाइड बताती है कि अन्यथा इमेज अपने मूल आकार में रेंडर होती है, और केवल .resizable() उसके अक्षों को स्वतंत्र रूप से स्केल कर सकता है। इमेज का वास्तविक आस्पेक्ट अनुपात बनाए रखें, फिर बाहरी फ़्रेम से पहले मार्कर ओवरले जोड़ें। उपलब्ध जगह में इमेज फ़िट करना देखें।
यदि एनोटेशन स्रोत निर्देशांकों से शुरू होते हैं, तो उन्हें उसी पूरे आर्टवर्क बाउंड के आधार पर सामान्यीकृत करें: स्रोत के क्षैतिज निर्देशांक को स्रोत की चौड़ाई से और ऊर्ध्वाधर निर्देशांक को स्रोत की ऊँचाई से भाग दें। इमेज के भीतर पारदर्शी हाशिये भी इन बाउंड का हिस्सा होते हैं। यदि निर्देशांक क्रॉप किए गए क्षेत्र के आधार पर मापे गए हों, तो इस तरीके से इस्तेमाल करने से पहले उन्हें पूरी इमेज के निर्देशांक तंत्र में बदलें।
इमेज-विशिष्ट मॉडिफ़ायर सामान्य व्यू मॉडिफ़ायर द्वारा उसे लपेटे जाने से पहले Image पर रखें। इसी तरह, संयोजित व्यू के लिए var body: some View और शेप के ड्रॉइंग मेथड के लिए Path रिटर्न टाइप बनाए रखें। Apple View में कस्टम व्यू संयोजन और मॉडिफ़ायर द्वारा व्यू को लपेटने का दस्तावेज़ देता है।
यह गणना एकसमान फ़िट मानती है, जिसमें अतिरिक्त क्रॉप, रोटेशन या स्वतंत्र पैन और ज़ूम ट्रांसफ़ॉर्म नहीं होता। यदि आप ये क्रियाएँ जोड़ते हैं, तो आर्टवर्क और एनोटेशन पर एक ही साझा ट्रांसफ़ॉर्म लागू करें या निर्देशांक मैपिंग को स्पष्ट रूप से बढ़ाएँ। .fill पर स्विच करने से फ़िट की शर्तें बदलती हैं और कंटेंट फ़्रेम के बाहर रेंडर हो सकता है; Apple उपलब्ध जगह में इमेज फ़िट करना में क्लिपिंग को अलग ऑपरेशन के रूप में दर्ज करता है।
बची हुई संरेखण समस्याओं का निदान करें
इन शर्तों को क्रम से जाँचें:
- मापन सीमा: मार्कर ओवरले को बाहरी फैलाए गए फ़्रेम से पहले रखें। इसकी सीमा आसपास के खाली क्षेत्र के बजाय आर्टवर्क के साथ होनी चाहिए।
- निर्देशांक बाउंड: पुष्टि करें कि सामान्यीकृत निर्देशांक पूरे स्रोत आयत और निर्धारित ऊपरी-बाएँ मूल बिंदु का उपयोग करते हैं। इस उदाहरण में, इच्छित आंतरिक मान शून्य से एक तक होते हैं; अप्रत्याशित मानों को चुपचाप क्लैंप करने के बजाय अस्वीकार करें।
- एंकर का चुनाव: ध्यान रखें कि
.position(x:y:)मार्कर के केंद्र को एंकर करता है। यदि लेबल का कोई कोना फ़ीचर को छूना चाहिए, तो स्पष्ट अतिरिक्त ऑफ़सेट या अलग एंकरिंग रणनीति चाहिए। - छोटे लेआउट: मार्कर सही केंद्र पर रह सकता है, फिर भी दूसरे मार्कर से ओवरलैप कर सकता है। छोटे आकारों में लेबल छिपाने या उनकी जगह बदलने के लिए अलग नियम चुनें।
- किनारों पर व्यवहार: आर्टवर्क के किनारे पर केंद्रित मार्कर उस किनारे से बाहर निकलेगा। क्लिपिंग जोड़ने से पहले तय करें कि यह बाहर निकलना स्वीकार्य है या नहीं।
पहली बार मापे गए आकार को .onAppear में सहेजने के बजाय, स्थितियाँ मौजूदा ज्यामिति से निकालें। Apple इस कॉलबैक को व्यू के दिखाई देने से जोड़ता है, जिसका समय व्यू के प्रकार पर निर्भर करता है; यह विंडो का आकार बदलने वाला कॉलबैक नहीं है। ज्यामिति को सीधे पढ़ने से इस उदाहरण की स्थिति गणना मौजूदा लेआउट से जुड़ी रहती है। onAppear(perform:) देखें।
रीसाइज़ की जा सकने वाली Mac विंडो में फ़िट किए गए डायग्राम के लिए, एक साझा आयत से शुरुआत करना भरोसेमंद तरीका है: आर्टवर्क को अपनी फ़िट की गई सीमाएँ तय करने दें, उन सीमाओं को मापें और हर एनोटेशन का केंद्र उन्हीं से निकालें। यह संबंध तय होने के बाद ही आसपास का लेआउट जोड़ें।
स्रोत
- aspectRatio(_:contentMode:) — Apple डेवलपर दस्तावेज़।
- overlay(alignment:content:) — Apple डेवलपर दस्तावेज़।
- GeometryReader — Apple डेवलपर दस्तावेज़।
- size — Apple डेवलपर दस्तावेज़।
- position(x:y:) — Apple डेवलपर दस्तावेज़।
- उपलब्ध जगह में इमेज फ़िट करना — Apple डेवलपर दस्तावेज़।
- frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:) — Apple डेवलपर दस्तावेज़।
- path(in:) — Apple डेवलपर दस्तावेज़।
- View — Apple डेवलपर दस्तावेज़।
- onAppear(perform:) — Apple डेवलपर दस्तावेज़।