अपने Mac ऐप में नेस्टेड विस्तार को एनिमेट करने के लिए, DisclosureGroup और एनिमेटेड विस्तार बाइंडिंग से शुरुआत करें। यदि आपको विस्तार की ऊँचाई पर सटीक नियंत्रण चाहिए, तो स्क्रॉल व्यू में कस्टम लेआउट का उपयोग करें; केवल कंटेंट ट्रांज़िशन उस ज्यामिति को परिभाषित नहीं करता।
यह ट्यूटोरियल एक खास काम पर केंद्रित है: आइटम के एक छोटे पदानुक्रम का विस्तार करना, ताकि उसकी सामग्री दिखाई दे और बदलती ऊँचाई के साथ नीचे के आइटम भी खिसकें। उदाहरणों में कृत्रिम लेबल हैं और वे पूरी तरह ऐप की अपनी विंडो के भीतर काम करते हैं।
सामग्री बिना स्पष्ट रूप से विस्तार हुए फीकी क्यों पड़ती है?
नतीजे की जाँच करते समय तीन चीज़ों को अलग रखें: विस्तार की स्थिति, जोड़ी गई सामग्री का दिखना, और कंटेनर द्वारा आवंटित जगह। Apple, withAnimation, वैल्यू-आधारित एनिमेशन मॉडिफ़ायर और एनिमेटेड बाइंडिंग को स्थिति में बदलाव एनिमेट करने के तरीकों के रूप में दस्तावेज़ित करता है। ट्रांज़िशन को अलग से उन प्रभावों के रूप में दस्तावेज़ित किया गया है जिनका उपयोग व्यू के पदानुक्रम में आने या उससे जाने पर होता है। Animations
ओपैसिटी ट्रांज़िशन पारदर्शिता बदलता है। मूवमेंट ट्रांज़िशन आने या जाने वाले व्यू की स्थिति को किसी किनारे के सापेक्ष बदलता है। इनमें से कोई भी API अपने आप यह नहीं बताता कि आसपास की सूची को पंक्तियों की ऊँचाई किस तरह बदलनी चाहिए। इसलिए मूवमेंट ट्रांज़िशन जोड़ने से दिखाई देने वाला प्रवेश बदल सकता है, पर आपके इच्छित खास लेआउट व्यवहार की समस्या हल नहीं होती। move(edge:)
इसे किसी खास आंतरिक कार्यान्वयन का प्रमाण नहीं, बल्कि जाँच के दौरान एक अंतर के रूप में देखें: यदि विवरण फीके पड़ते हैं और अगली पंक्ति उछलकर अपनी जगह पर आती है, तो विवरणों के ट्रांज़िशन से अलग कंटेनर की ज्यामिति की जाँच करें। दस्तावेज़ित List API वैकल्पिक चयन सुविधा वाले पंक्ति कंटेनर का वर्णन करता है; यह हर नेस्टेड संरचना और सूची शैली के लिए किसी खास विस्तार वक्र की गारंटी नहीं देता। List
नेटिव नेस्टेड डिस्क्लोज़र ग्रुप से शुरुआत करें
Mac पर सामान्य पदानुक्रम के लिए, अपना डिस्क्लोज़र कंट्रोल बनाने से पहले DisclosureGroup का उपयोग करें। यह बूलियन बाइंडिंग के ज़रिए विस्तार को नियंत्रित करता है, और Apple का दस्तावेज़ एक डिस्क्लोज़र ग्रुप के भीतर दूसरे को नेस्ट करने का स्पष्ट उदाहरण देता है। यह API macOS 11 और उसके बाद के संस्करणों में उपलब्ध है। DisclosureGroup
SwiftUI का उपयोग करके Xcode में macOS ऐप प्रोजेक्ट बनाएँ। जनरेट किए गए ऐप डिक्लेरेशन को नीचे दिए पूरे प्रोग्राम से बदलें और टार्गेट में ठीक एक @main डिक्लेरेशन रखें। इसमें सभी नमूना सामग्री और दोनों विस्तार स्थितियाँ शामिल हैं।
import SwiftUI
@main
struct NestedDisclosureDemoApp: App {
var body: some Scene {
WindowGroup {
NativeHierarchyView()
.frame(minWidth: 360, minHeight: 280)
}
}
}
struct NativeHierarchyView: View {
@Environment(\.accessibilityReduceMotion) private var reduceMotion
@State private var collectionExpanded = false
@State private var shapesExpanded = false
private var expansionAnimation: Animation? {
reduceMotion ? nil : .easeInOut(duration: 0.25)
}
var body: some View {
List {
DisclosureGroup(
"Collection",
isExpanded: $collectionExpanded.animation(expansionAnimation)
) {
DisclosureGroup(
"Shapes",
isExpanded: $shapesExpanded.animation(expansionAnimation)
) {
Text("Circle")
Text("Square")
}
Text("Palette")
}
Text("Following item")
}
}
}
बाइंडिंग का animation मेथड वैल्यू बदलने पर एनिमेशन का अनुरोध करता है। Collection पर क्लिक करें, Shapes को फैलाएँ, फिर Collection को समेटकर दोबारा खोलें। अलग-अलग संग्रहित स्थिति के कारण, पैरेंट बंद होने पर भी नमूना याद रख सकता है कि Shapes फैला हुआ था या नहीं। यह मौजूदा व्यू की इन-मेमरी स्थिति है, ऐप लॉन्च के बीच बनी रहने वाली स्थिति नहीं। एनिमेटेड बाइंडिंग, Apple द्वारा दस्तावेज़ित एनिमेशन के तरीकों में से एक है। Animations
उदाहरण में डिज़ाइन के एक नमूने के रूप में चौथाई सेकंड का ईज़िंग कर्व इस्तेमाल किया गया है। सिस्टम की Reduce Motion प्राथमिकता चालू होने पर यह nil पास करता है। Apple यह प्राथमिकता accessibilityReduceMotion के ज़रिए उपलब्ध कराता है और इसके चालू होने पर बड़े एनिमेशन से बचने की सलाह देता है। accessibilityReduceMotion
अपने ऐप में समर्थित हर macOS रिलीज़ और सूची शैली पर नेटिव संस्करण को परखें। यदि उसका व्यवहार काम के अनुरूप है, तो उसे बनाए रखें। यदि केवल डिस्क्लोज़र का रूप बदलना है, तो DisclosureGroupStyle macOS 13 और उसके बाद के संस्करणों में समर्थित अनुकूलन बिंदु देता है। कस्टम शैली भी व्यू को संयोजित करती है; इसके दस्तावेज़ आसपास की सूची की पंक्तियों के एनिमेशन पर मनमाना नियंत्रण देने का वादा नहीं करते। DisclosureGroupStyle
ज़रूरी नतीजे के मुताबिक कंटेनर चुनें
और मॉडिफ़ायर जोड़ने से पहले इस निर्णय मार्गदर्शिका का उपयोग करें। यह नेटिव पंक्ति इंटरैक्शन और स्पष्ट रूप से नियंत्रित रिवील के बीच अंतर बताती है।
| ज़रूरी नतीजा | शुरुआत यहाँ से करें | क्या जाँचें |
|---|---|---|
| चयन सुविधा वाली सामान्य विस्तार योग्य पंक्तियाँ | List और DisclosureGroup |
वास्तविक macOS संस्करण और सूची शैली में विस्तार का व्यवहार |
| अलग डिस्क्लोज़र लेबल या कंट्रोल का रूप | DisclosureGroupStyle |
क्या आसपास का कंटेनर अब भी इच्छित ज्यामिति देता है |
| छोटे पदानुक्रम के लिए सटीक रूप से तय रिवील ऊँचाई | ScrollView, VStack और एनिमेटेबल Layout |
नेस्टेड आकार परिवर्तन, क्लिपिंग और कंट्रोल का व्यवहार |
| कॉलम और डेलिगेट वाला मौजूदा AppKit आउटलाइन | NSOutlineView |
इसके अपने विस्तार API और डेटा-सोर्स का व्यवहार |
नीचे दिया कस्टम तरीका जान-बूझकर स्क्रॉल व्यू के भीतर स्टैक का उपयोग करता है। यह अपने डिस्क्लोज़र बटन और ज्यामिति देता है, इसलिए आपके इंटरफ़ेस को जिस भी सूची चयन या पंक्ति कमांड की ज़रूरत हो, उसे भी आपको लागू करना होगा। Apple चयन को List की सुविधा के रूप में दस्तावेज़ित करता है; कंटेनर बदलने से शुरुआती आधार बदल जाता है। List
कस्टम लेआउट से दिखाई गई ऊँचाई को एनिमेट करें
macOS 13 और उसके बाद के संस्करणों में, Layout प्रोटोकॉल कंटेनर को अपना आकार निकालने और अपने बच्चों को रखने देता है। यह Animatable से भी विरासत में मिलता है। ये ऐसे दस्तावेज़ित आधार हैं जिनसे विस्तार की मात्रा को केवल प्रवेश प्रभावों पर निर्भर रहने के बजाय इंटरपोलेट होती संख्या बनाया जा सकता है। Layout
नीचे दिया गया मौलिक कार्यान्वयन हर रिवील के लिए एक चाइल्ड स्टैक का उपयोग करता है। यह स्टैक को उपलब्ध चौड़ाई और अनिर्दिष्ट ऊँचाई पर मापता है, फिर मापी गई ऊँचाई का एक अंश बताता है। दिखाई देने वाला क्षेत्र बदलने पर भी बच्चा सबसे ऊपर स्थित रहता है। Apple प्रस्ताव के साथ सबव्यू को मापने और मेल खाते प्रस्ताव के साथ उसे रखने, दोनों को दस्तावेज़ित करता है। sizeThatFits(_:), place(at:anchor:proposal:)
पिछले उदाहरण की जगह इस पूरे प्रोग्राम का विकल्प के रूप में उपयोग करें। डिप्लॉयमेंट टार्गेट macOS 13 या उसके बाद का रखें। कोड को दस्तावेज़ित API के आधार पर जाँचा गया है; यहाँ इसे कंपाइल या दृश्य रूप से परखा नहीं गया, क्योंकि इस वातावरण में Swift एक्ज़ीक्यूटेबल या macOS रनटाइम उपलब्ध नहीं है।
import SwiftUI
@main
struct RevealDemoApp: App {
var body: some Scene {
WindowGroup {
ScrollView {
VStack(alignment: .leading, spacing: 0) {
RevealGroup("Collection") {
RevealGroup("Shapes") {
Text("Circle")
Text("Square")
}
Text("Palette")
}
Text("Following item")
.padding(.vertical, 8)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
}
.frame(minWidth: 360, minHeight: 280)
}
}
}
struct RevealGroup<Content: View>: View {
let title: String
let content: Content
@State private var expanded = false
@Environment(\.accessibilityReduceMotion) private var reduceMotion
init(_ title: String, @ViewBuilder content: () -> Content) {
self.title = title
self.content = content()
}
var body: some View {
VStack(alignment: .leading, spacing: 0) {
Button {
withAnimation(
reduceMotion ? nil : .easeInOut(duration: 0.25)
) {
expanded.toggle()
}
} label: {
HStack {
Image(systemName: "chevron.right")
.rotationEffect(.degrees(expanded ? 90 : 0))
.accessibilityHidden(true)
Text(title)
Spacer(minLength: 0)
}
.padding(.vertical, 6)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.accessibilityValue(expanded ? "Expanded" : "Collapsed")
RevealLayout(progress: expanded ? 1 : 0) {
VStack(alignment: .leading, spacing: 8) {
content
}
.padding(.leading, 18)
.padding(.bottom, 8)
.frame(maxWidth: .infinity, alignment: .leading)
}
.clipped()
.allowsHitTesting(expanded)
.disabled(!expanded)
.accessibilityHidden(!expanded)
}
}
}
struct RevealLayout: Layout {
var progress: CGFloat
var animatableData: CGFloat {
get { progress }
set { progress = newValue }
}
func sizeThatFits(
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout ()
) -> CGSize {
guard let child = subviews.first else { return .zero }
let width = proposal.width.flatMap { $0.isFinite ? $0 : nil }
let size = child.sizeThatFits(
ProposedViewSize(width: width, height: nil)
)
return CGSize(
width: width ?? size.width,
height: size.height * min(max(progress, 0), 1)
)
}
func placeSubviews(
in bounds: CGRect,
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout ()
) {
guard let child = subviews.first else { return }
child.place(
at: CGPoint(x: bounds.minX, y: bounds.minY),
anchor: .topLeading,
proposal: ProposedViewSize(width: bounds.width, height: nil)
)
}
}
मुख्य वैल्यू progress है। एनिमेशन के दौरान SwiftUI पुराने और नए animatableData मानों के बीच इंटरपोलेट करता है। यहाँ इससे बताई गई ऊँचाई लगातार शून्य से बच्चे की मापी गई ऊँचाई तक बदलती है। उदाहरण के लिए, 160 पॉइंट की मापी गई ऊँचाई पर प्रगति 0.5 होने से 80 पॉइंट का रिवील बनता है; ये अंक गणितीय उदाहरण हैं, ऐप के मापे गए नतीजे नहीं। Animatable
अनिर्दिष्ट ऊँचाई बच्चे से प्रस्तावित चौड़ाई पर उसकी आदर्श ऊँचाई माँगती है, न कि उसे मौजूदा रिवील ऊँचाई में समेटने के लिए कहती है। रैप होने वाले लेबल के लिए यह अहम है: रिवील को उनके लेआउट को दिखाना चाहिए, धीरे-धीरे दबाना नहीं। Apple अनिर्दिष्ट आयामों को आदर्श आकार पाने के तरीके के रूप में वर्णित करता है। ProposedViewSize
इसके बाद क्लिपिंग बताई गई सीमा के बाहर के हिस्से को छिपाती है। इसके बिना सामग्री लेआउट द्वारा आरक्षित क्षेत्र के बाहर भी दिखाई दे सकती है। नीचे का पैडिंग रिवील के भीतर ही रखें, जैसा दिखाया गया है, ताकि उसे समेटते समय वह ऊँचाई में शामिल हो। clipped(antialiased:)
यह कार्यान्वयन जान-बूझकर समेटे जाने पर भी चाइल्ड पदानुक्रम को बनाए रखता है। यह इंटरैक्शन बंद करता है और समेटे गए क्षेत्र को एक्सेसिबिलिटी प्रतिनिधित्व से हटाता है; दिखाई देने वाला बटन अपनी विस्तार स्थिति बताता है। हिट टेस्टिंग और एक्सेसिबिलिटी दृश्यता अलग-अलग नियंत्रण हैं, इसलिए केवल क्लिपिंग पर्याप्त नहीं है। allowsHitTesting(_:), accessibilityHidden(_:)
कस्टम संस्करण अपनाने से पहले नेस्टेड व्यवहार जाँचें
काम कर रहे रिवील और केवल सबसे सरल स्थिति में सही दिखने वाले एनिमेशन के बीच अंतर जानने के लिए नीचे दिया क्रम अपनाएँ।
Collectionफैलाएँ, फिरShapesफैलाएँ। दोनों स्तर बदलते समयFollowing itemको देखें।Shapesखुला रहते हुएCollectionको समेटें, फिर उसे दोबारा खोलें। पुष्टि करें कि इच्छित चाइल्ड विस्तार स्थिति बनी रहती है।- एनिमेशन पूरा होने से पहले किसी डिस्क्लोज़र बटन पर फिर क्लिक करें। जाँचें कि रिवील बिना खाली जगह छोड़े उलट जाता है।
- विंडो संकरी करें और किसी नमूना लेबल को लंबे कृत्रिम लेबल से बदलें। रैपिंग और रिवील की अंतिम ऊँचाई जाँचें।
Reduce Motionचालू करें और पुष्टि करें कि एनिमेटेड मूवमेंट के बिना भी विस्तार उपयोग योग्य है।- कीबोर्ड से सक्रिय करने और फैले तथा समेटे गए, दोनों समूहों के एक्सेसिबिलिटी प्रतिनिधित्व की जाँच करें।
कस्टम लेआउट को इन लेबल और नेस्टेड स्टैक जैसी सीमित ऊँचाई वाली सामग्री के लिए बनाया गया है। रिवील के भीतर लंबवत रूप से असीमित स्क्रॉल क्षेत्र न रखें और उससे पूरी सामग्री की उपयोगी ऊँचाई मिलने की अपेक्षा न करें। कार्यान्वयन स्पष्ट रूप से आदर्श ऊँचाई माँगता है, इसलिए बच्चे को उस प्रस्ताव का अर्थपूर्ण उत्तर देना आना चाहिए। यह चुने गए आकार निर्धारण के डिज़ाइन का परिणाम है। ProposedViewSize
समेटी गई सामग्री को बनाए रखने का अर्थ है उसकी निर्माण और लेआउट लागत भी बनाए रखना। छोटे पदानुक्रम के लिए साधारण VStack से शुरुआत करें; बड़े ट्री पर इस eager उदाहरण को लागू करने से पहले किसी अन्य आर्किटेक्चर का मूल्यांकन करें। डेटा-आधारित पंक्तियों के लिए विस्तार और पुनःक्रमण के दौरान मॉडल आइडेंटिफ़ायर स्थिर रखें। Swift का Identifiable प्रोटोकॉल स्थिर इकाई पहचान दर्शाता है, और ForEach व्यू बनाने के लिए पहचाने गए डेटा का उपयोग करता है। Identifiable, ForEach
AppKit और TextKit का उपयोग कहाँ होता है?
यदि इंटरफ़ेस पहले से AppKit का उपयोग करता है, तो NSOutlineView पदानुक्रमित पंक्ति और कॉलम कंट्रोल है, जैसा दस्तावेज़ित है। इसका expandItem(_:) मेथड किसी आइटम को फैलाता है और तब कुछ नहीं करता जब वह आइटम पहले से फैला हुआ हो या फैल नहीं सकता हो। यह मेथड SwiftUI के एनिमेशन कर्व का पैरामीटर नहीं देता, इसलिए यह न मानें कि इससे ऊपर दिखाया गया कस्टम समय नियंत्रण मिलता है। NSOutlineView, expandItem(_:)
TextKit टेक्स्ट स्टोरेज, टेक्स्ट लेआउट और रेंडरिंग संभालता है, जिसमें NSTextView द्वारा इस्तेमाल होने वाली मशीनरी भी शामिल है। सामान्य लेबल वाले पदानुक्रम के लिए यह विस्तार करने वाले कंटेनर से अलग विषय है। इसका उपयोग तब करें जब सामग्री को टेक्स्ट संपादन या कस्टम टेक्स्ट लेआउट की ज़रूरत हो; यह SwiftUI सूची विस्तार को एनिमेट करने का दस्तावेज़ित स्विच नहीं है। TextKit
नेटिव डिस्क्लोज़र ग्रुप से शुरुआत करें और अपने लक्षित Mac इंटरफ़ेस में उनके व्यवहार की जाँच करें। जब काम के लिए नियंत्रित ऊँचाई वाला रिवील चाहिए, तो कस्टम लेआउट उस ऊँचाई को स्पष्ट एनिमेटेबल वैल्यू देता है। चुनाव को ज़रूरी इंटरैक्शन के मुताबिक रखें: नेटिव सूची व्यवहार, कस्टम रिवील ज्यामिति या मौजूदा AppKit आउटलाइन।
स्रोत
- DisclosureGroup — Apple
- Animations — Apple
- move(edge:) — Apple
- List — Apple
- accessibilityReduceMotion — Apple
- DisclosureGroupStyle — Apple
- Layout — Apple
- sizeThatFits(_:) — Apple
- place(at:anchor:proposal:) — Apple
- Animatable — Apple
- ProposedViewSize — Apple
- clipped(antialiased:) — Apple
- allowsHitTesting(_:) — Apple
- accessibilityHidden(_:) — Apple
- Identifiable — Apple
- ForEach — Apple
- NSOutlineView — Apple
- expandItem(_:) — Apple
- TextKit — Apple