paint-brush
यूआई थ्रोबैक: CALayers का उपयोग करके iOS के लिए एक रेट्रो प्रोग्रेस बार बनानाद्वारा@maxkalik
680 रीडिंग
680 रीडिंग

यूआई थ्रोबैक: CALayers का उपयोग करके iOS के लिए एक रेट्रो प्रोग्रेस बार बनाना

द्वारा Max Kalik22m2023/12/17
Read on Terminal Reader

बहुत लंबा; पढ़ने के लिए

यह मार्गदर्शिका पुरानी यादों को ताज़ा करने के लिए CALayers का उपयोग करके iOS में एक रेट्रो-शैली प्रगति पट्टी के निर्माण की पड़ताल करती है। यह चरण-दर-चरण प्रक्रिया को कवर करता है, प्रत्येक परत को डिजाइन करने से लेकर एनिमेशन लागू करने तक, आधुनिक आईओएस विकास के साथ पुराने स्कूल के सौंदर्यशास्त्र के संयोजन में अंतर्दृष्टि प्रदान करता है। डेवलपर्स और यूआई डिजाइनरों के लिए आदर्श, लेख अद्वितीय यूआई घटकों के लिए तकनीकी गहराई और रचनात्मक प्रेरणा दोनों प्रदान करता है। स्रोत कोड: https://github.com/maxkalik/RetroProgressBar
featured image - यूआई थ्रोबैक: CALayers का उपयोग करके iOS के लिए एक रेट्रो प्रोग्रेस बार बनाना
Max Kalik HackerNoon profile picture
0-item


मुझे यूआई घटक बनाना पसंद है। निर्माण के बाद उन्हें देखना और उनका उपयोग करना मुझे एक प्रकार का सौंदर्यात्मक आनंद देता है। यह समग्र रूप से MacOS/iOS पारिस्थितिकी तंत्र के प्रति मेरे प्रेम के साथ भी मेल खाता है। हाल ही में, मुझे UIKit उपयोग करके एक कस्टम प्रगति पट्टी बनाने का काम सौंपा गया था। यह कोई बड़ी बात नहीं है, लेकिन संभवतः 99% iOS डेवलपर्स ने अपने करियर में किसी न किसी बिंदु पर इसे बनाया है। मेरे मामले में, मैं 0 से 1 तक की मान सीमा पर आधारित एनिमेशन के साथ कुछ CALayer बनाने के अलावा कुछ और करना चाहता था। मैं एक ऐसे समाधान का लक्ष्य बना रहा था जो कई परिदृश्यों के लिए उपयुक्त होगा जहां प्रगति पट्टी की आवश्यकता होती है .


मैं खुद को उन दिनों में वापस ले जाना चाहता हूं जब Apple दुनिया में UI घटक इस तरह दिखते थे:


स्रोत: https://www.smashingmagazine.com/2010/08/free-wireframing-kits-ui-design-kits-pdfs-and-resources/


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


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

UIView से लेकर CALayers API तक।

प्रगति पट्टी सीधी लग सकती है, लेकिन यदि हम विवरण में गहराई से उतरें, तो आप समझेंगे कि इसके लिए बहुत सारी उप-परतों की आवश्यकता होती है।


रेट्रो प्रोग्रेस बार


मैं यहाँ CALayers के बारे में विस्तार से नहीं बताने जा रहा हूँ। आप इस लेख को देख सकते हैं जहां मैंने संक्षेप में बताया है कि CALayers क्या हैं और वे कैसे काम करते हैं: https://hackernoon.com/rolling-numbers-animation-using-only-calayers


तो, आइए इसे छोटी परतों में तोड़ें:

  1. सामान्य पृष्ठभूमि परत
  2. मुखौटा परत (एनिमेटेबल भाग)
  3. प्रगति पृष्ठभूमि परत
  4. चकाचौंध परत
  5. झिलमिलाती परत


पृष्ठभूमि परत में दो अतिरिक्त चाइल्ड परतें हैं:

  1. भीतरी छाया परत
  2. सीमा ढाल परत


आइए इसे ठीक से संरचित करें:


 RetroProgressBar [UIView] |--General background [CALayer] |--Inner Shadow [CALayer] |--Border Gradient [CALayer] |--Progress Background Layer [CALayer] |--Animated Mask Layer [CALayer] |--Glare [CALayer] |--Shimering [CALayer]


संक्षेप में, यहां बताया गया है कि इसे कैसे काम करना चाहिए: प्रोग्रेस बार को 0.0 से 1.0 तक के मान पर प्रतिक्रिया करनी चाहिए। जब मान बदलता है, तो हमें इस मान के आधार पर परत की चौड़ाई को समायोजित करने की आवश्यकता होती है:


 bounds.width * value


इस सरल गणना का उपयोग करके, हम CATransaction का उपयोग करके चौड़ाई को संशोधित कर सकते हैं। लेकिन ऐसा करने से पहले, हमें यह समझने की ज़रूरत है कि मूल्य बदलने पर कौन सी परतें बदलनी चाहिए। आइए संरचना पर दोबारा गौर करें और उन परतों की पहचान करें जिन्हें मूल्य परिवर्तनों के प्रति उत्तरदायी होने की आवश्यकता है।


 RetroProgressBar [UIView] |--General background [CALayer] |--Inner Shadow [CALayer] |--Border Gradient [CALayer] |--Progress Background Layer [CALayer] |--Animated Mask Layer [CALayer] : [Animated Width] |--Glare [CALayer] : [Animated Width] |--Shimering [CALayer] : [Animated Width]


ऐसा प्रतीत होता है कि हमें एक प्रोटोकॉल विकसित करने की आवश्यकता है जिसे उन परतों द्वारा अपनाया जा सके जिन्हें मूल्य के आधार पर एनिमेटेड चौड़ाई की आवश्यकता होती है।

प्रोग्रेसएनिमेटेबल प्रोटोकॉल

प्रोटोकॉल में, हमें केवल दो तरीकों की आवश्यकता है: परत की चौड़ाई को एनीमेशन के साथ और बिना बदलें।


 protocol ProgressAnimatable: CALayer { /// Sets the layer's width instantly to the specified value. func setToWidth(_ width: CGFloat) /// Animates the layer's width to the specified value. func animateToWidth(_ width: CGFloat, duration: TimeInterval, animationType: CAMediaTimingFunctionName, completion: (() -> Void)?) }


कार्यान्वयन:


 extension ProgressAnimatable { func setToWidth(_ width: CGFloat) { guard width >= 0 else { return } removeAllAnimations() CATransaction.begin() CATransaction.setDisableActions(true) self.bounds.size.width = width CATransaction.commit() } func animateToWidth(_ width: CGFloat, duration: TimeInterval, animationType: CAMediaTimingFunctionName = .easeInEaseOut, completion: (() -> Void)? = nil) { guard width >= 0, width != self.bounds.width else { completion?() return } let animation = CABasicAnimation(keyPath: "bounds.size.width") animation.fromValue = bounds.width animation.toValue = width animation.duration = duration animation.fillMode = .forwards animation.isRemovedOnCompletion = false animation.timingFunction = CAMediaTimingFunction(name: animationType) CATransaction.begin() CATransaction.setCompletionBlock { self.bounds.size.width = width completion?() } self.add(animation, forKey: "widthChange") CATransaction.commit() } }


दोनों विधियाँ CATransaction उपयोग करती हैं। पहली विधि अजीब लग सकती है क्योंकि हमें एनीमेशन के बिना केवल एक मान बदलने की आवश्यकता है। तो, CATransaction क्यों आवश्यक है?


एक बुनियादी CALayer बनाने और बिना किसी एनीमेशन के उसकी चौड़ाई या ऊंचाई बदलने का प्रयास करें। जब आप कोई बिल्ड चलाते हैं, तो आप एनीमेशन के साथ CALayer में बदलाव देखेंगे। यह कैसे संभव है? कोर एनीमेशन में, परत गुणों में परिवर्तन (जैसे सीमाएं, स्थिति, अस्पष्टता इत्यादि) आम तौर पर उस संपत्ति से जुड़े डिफ़ॉल्ट कार्रवाई के आधार पर एनिमेटेड होते हैं। इस डिफ़ॉल्ट व्यवहार का उद्देश्य प्रत्येक संपत्ति परिवर्तन के लिए स्पष्ट एनिमेशन की आवश्यकता के बिना सहज दृश्य बदलाव प्रदान करना है।


इसका मतलब है कि हमें CALayer में क्रियाओं को स्पष्ट रूप से अक्षम करने की आवश्यकता है। CATransaction.setDisableActions(true) सेट करके, आप सुनिश्चित करते हैं कि परत की चौड़ाई बिना किसी मध्यवर्ती एनिमेटेड संक्रमण के तुरंत नए मान में अपडेट हो जाती है।


दूसरी विधि ProgressAnimatable प्रोटोकॉल के अनुरूप किसी भी परत की चौड़ाई को एनिमेट करने के लिए एक लचीला और पुन: प्रयोज्य तरीका प्रदान करती है। यह एनीमेशन की अवधि और गति पर नियंत्रण प्रदान करता है और इसमें पूर्ण कार्रवाई के लिए एक विकल्प शामिल होता है। यह इसे UIKit ढांचे के भीतर विभिन्न एनीमेशन आवश्यकताओं के लिए उपयुक्त बनाता है।


इस प्रोटोकॉल के अनुरूप हमारे उम्मीदवार तीन परतें होंगे: एनिमेटेड मास्क परत, चमक परत, और चमकदार परत। आइये आगे बढ़ें और इसे क्रियान्वित करें।

प्रोग्रेसमास्कलेयर

यह हमारे प्रोटोकॉल के अनुरूप होने का समय है, और इस सेटअप में सबसे महत्वपूर्ण परत हमारी सामान्य CAShapeLayer होगी। हमें इस मास्क की आवश्यकता क्यों है? यह केवल एनिमेटेड चौड़ाई वाला CALayer क्यों नहीं हो सकता? एक iOS इंजीनियर के रूप में, विशेष रूप से फ्रंट-एंड डेवलपमेंट पर ध्यान केंद्रित करने वाले इंजीनियर के रूप में, आपको अक्सर अपने घटकों के लिए संभावित उपयोग के मामलों का अनुमान लगाने की आवश्यकता होती है। मेरे उदाहरण में, एक प्रगति पृष्ठभूमि परत है। यह एक एनिमेटेड CALayer नहीं है, लेकिन क्या होगा अगर यह CAReplicatorLayer जैसी किसी चीज़ के साथ एनिमेटेड हो? यदि आप प्रदर्शन के बारे में चिंतित हैं, तो आप संभवतः इस बात पर विचार करेंगे कि ऐसी परत को एक बार प्रस्तुत किया जाना चाहिए और फिर बस उसका एनीमेशन किया जाना चाहिए। इसलिए यह मास्क बेहद उपयोगी हो सकता है। यह विभिन्न एनीमेशन परिदृश्यों के लिए आवश्यक लचीलेपन की पेशकश करते हुए कुशल प्रतिपादन की अनुमति देता है।


 final class ProgressMaskLayer: CAShapeLayer, ProgressAnimatable { override init() { super.init() backgroundColor = UIColor.black.cgColor anchorPoint = CGPoint(x: 0, y: 0.5) } override init(layer: Any) { super.init(layer: layer) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }


आइए इस पंक्ति पर एक नजर डालें:


 anchorPoint = CGPoint(x: 0, y: 0.5)


यह लाइन हमारे प्रोग्रेस बार के लिए महत्वपूर्ण है। डिफ़ॉल्ट रूप से, CALayers केंद्रित होते हैं, anchorPoint परत के केंद्र में स्थित होता है, न कि बाएं किनारे के मध्य में, जैसा कि आमतौर पर माना जाता है। anchorPoint वह बिंदु है जिसके चारों ओर परत के सभी परिवर्तन होते हैं। प्रगति पट्टी के रूप में उपयोग की जाने वाली परत के लिए, इस सेटिंग का आम तौर पर मतलब है कि जब परत की चौड़ाई बदलती है, तो यह anchorPoint से दोनों दिशाओं में समान रूप से विस्तारित होती है। हालाँकि, यदि हम anchorPoint बाएं किनारे के मध्य में समायोजित करते हैं, तो परत केवल दाईं ओर विस्तारित होगी जबकि बायां किनारा अपनी जगह पर स्थिर रहेगा। यह व्यवहार प्रगति पट्टी के लिए आवश्यक है, यह सुनिश्चित करते हुए कि पट्टी की वृद्धि दोनों तरफ के बजाय एक तरफ से दिखाई देती है।


 private let progressMaskLayer = ProgressMaskLayer() private let progressBackgroundLayer = ProgressBackgroundLayer() public init() { super.init(frame: .zero) layer.addSublayer(progressMaskLayer) layer.addSublayer(progressBackgroundLayer) progressBackgroundLayer.mask = progressMaskLayer }


उपरोक्त स्निपेट में, हम प्रोग्रेस बार के दो मुख्य भागों को आरंभ करते हैं: ProgressMaskLayer और ProgressBackgroundLayer । आइये दूसरे पर एक नजर डालते हैं.


 final class ProgressBackgroundLayer: CAGradientLayer { override init() { super.init() locations = [0, 0.5, 1] } override init(layer: Any) { super.init(layer: layer) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSublayers() { super.layoutSublayers() colors = [ backgroundColor?.lightened(by: 0.3), backgroundColor, backgroundColor?.darkened(by: 0.5) ].compactMap({ $0 }) } }


जैसा कि आप देख सकते हैं, यह एक CAGradientLayer है जिसमें तीन रंग हैं। यह कार्यान्वयन एक इंजेक्ट किए गए रंग को ऊर्ध्वाधर ढाल में बदल देता है, प्रगति पट्टी की दृश्यता को बढ़ाता है और इसे अधिक विशाल रूप देता है।


ढाल

इसे संभव बनाने के लिए, मैंने CGColor एक्सटेंशन में दो विधियाँ तैयार कीं। मनोरंजन के लिए, मैंने CGColor वापस UIColor में परिवर्तित नहीं करने का निर्णय लिया, बल्कि केवल CGColor के दायरे में खेलने का निर्णय लिया:


 extension CGColor { func darkened(by value: CGFloat) -> CGColor { guard let colorSpace = self.colorSpace, let components = self.components, colorSpace.model == .rgb, components.count >= 3 else { return self } let multiplier = 1 - min(max(value, 0), 1) let red = components[0] * multiplier let green = components[1] * multiplier let blue = components[2] * multiplier let alpha = components.count > 3 ? components[3] : 1.0 return CGColor( colorSpace: colorSpace, components: [red, green, blue, alpha] ) ?? self } func lightened(by value: CGFloat) -> CGColor { guard let colorSpace = self.colorSpace, let components = self.components, colorSpace.model == .rgb, components.count >= 3 else { return self } let red = min(components[0] + value, 1.0) let green = min(components[1] + value, 1.0) let blue = min(components[2] + value, 1.0) let alpha = components.count > 3 ? components[3] : 1.0 return CGColor( colorSpace: colorSpace, components: [red, green, blue, alpha] ) ?? self } }


जैसा कि आप देख सकते हैं, यह बहुत जटिल नहीं है। हमें बस colorSpace , components ( red , green , blue और alpha ), और model प्राप्त करने की आवश्यकता है। इसका मतलब यह है कि मूल्य के आधार पर, हम एक नए रंग की गणना कर सकते हैं: या तो गहरा या हल्का।


चकाचौंध परत

आइए अपने प्रोग्रेस बार को और अधिक 3डी-ईश बनाने की दिशा में आगे बढ़ें। अब, हमें एक और परत जोड़ने की जरूरत है, जिसे हम ग्लेयर कहेंगे।


यह एक सीधा कार्यान्वयन है, लेकिन इसे ProgressAnimatable प्रोटोकॉल के अनुरूप होना चाहिए।


 final class ProgressGlareLayer: CALayer, ProgressAnimatable { override init() { super.init() backgroundColor = UIColor.white.withAlphaComponent(0.3).cgColor anchorPoint = CGPoint(x: 0, y: 0.5) } override init(layer: Any) { super.init(layer: layer) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } 


चकाचौंध के साथ


चीजों को सरल रखने के लिए, हमें यहां पृष्ठभूमि का रंग केवल 0.3 के अल्फा के साथ सफेद होना चाहिए।


छोटी सी अच्छी झिलमिलाती परत (झिलमिलाती परत)

मैं जानता हूं कि कुछ लोग कह सकते हैं कि संस्करण 6 से पहले MacOS X या iOS में इस तरह के चमकदार प्रभाव वाले UI घटक नहीं थे। लेकिन, जैसा कि मैंने कहा, यह एक वास्तविक कहानी से प्रेरित फिल्म की तरह है। यह झिलमिलाहट 3डी प्रभाव को बढ़ाती है और इसे और अधिक दृश्यमान बनाती है।


 final class ShimmeringLayer: CAGradientLayer, ProgressAnimatable { let glowColor: UIColor = UIColor.white private func shimmerAnimation() -> CABasicAnimation { let animation = CABasicAnimation(keyPath: "locations") animation.fromValue = [-1.0, -0.5, 0.0] animation.toValue = [1.0, 1.5, 2.0] animation.duration = 1.5 animation.repeatCount = Float.infinity return animation } private func opacityAnimation() -> CABasicAnimation { let animation = CABasicAnimation(keyPath: "opacity") animation.fromValue = 1.0 animation.toValue = 0.0 animation.duration = 0.1 // Quick transition to transparent animation.beginTime = shimmerAnimation().duration animation.fillMode = .forwards animation.isRemovedOnCompletion = false return animation } private func animationGroup() -> CAAnimationGroup { let pauseDuration = 3.0 // Duration of pause between shimmering let shimmerAnimation = shimmerAnimation() let opacityAnimation = opacityAnimation() let animationGroup = CAAnimationGroup() animationGroup.animations = [shimmerAnimation, opacityAnimation] animationGroup.duration = shimmerAnimation.duration + pauseDuration animationGroup.repeatCount = Float.infinity return animationGroup } override init() { super.init() setupLayer() } override init(layer: Any) { super.init(layer: layer) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSublayers() { super.layoutSublayers() startAnimation() } private func setupLayer() { let lightColor = UIColor.white.withAlphaComponent(0.7).cgColor let darkColor = UIColor.white.withAlphaComponent(0).cgColor colors = [ darkColor, lightColor, darkColor ] anchorPoint = CGPoint(x: 0, y: 0.5) locations = [0.0, 0.5, 1.0] startPoint = CGPoint(x: 0.0, y: 0.5) endPoint = CGPoint(x: 1.0, y: 0.5) shadowColor = glowColor.cgColor shadowRadius = 5.0 shadowOpacity = 1 shadowOffset = .zero } private func startAnimation() { if animation(forKey: "shimmerEffect") == nil { let animationGroup = animationGroup() add(animationGroup, forKey: "shimmerEffect") } } }


यहां, हमें दो एनिमेशन (अपारदर्शिता और स्थान) को एक CAAnimationGroup में संयोजित करने की आवश्यकता है। इसके अलावा, CAAnimationGroup का उपयोग हमें एनीमेशन सत्रों के बीच एक विराम की व्यवस्था करने की अनुमति देता है। यह सुविधा काफी उपयोगी है क्योंकि ग्राहक इस संपत्ति को भी नियंत्रित करना चाह सकते हैं।


झिलमिलाहट के साथ


जैसा कि आप चित्र में देख सकते हैं, यह अधिक 3D प्रभाव जोड़ता है, लेकिन यह पर्याप्त नहीं है। आइए आगे बढ़ें.

पृष्ठभूमि परत

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


 |--General background [CALayer] |--Inner Shadow [CALayer] |--Border Gradient [CALayer]


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

भीतरी छाया परत

क्लास को आंतरिक छाया प्रभाव के साथ एक परत बनाने के लिए डिज़ाइन किया गया है, जो यूआई तत्वों में गहराई जोड़ने का एक आकर्षक तरीका हो सकता है।


 final class InnerShadowLayer: CAShapeLayer { override init() { super.init() masksToBounds = true shadowRadius = 3 shadowColor = UIColor.black.cgColor shadowOffset = CGSize(width: 0.0, height: 1.0) shadowOpacity = 0.5 } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSublayers() { super.layoutSublayers() // Creates a path for the shadow that extends slightly outside the bounds of the layer. let shadowPath = UIBezierPath(roundedRect: bounds.insetBy(dx: -5, dy: -5), cornerRadius: cornerRadius) // Creates a cutout path that is the inverse of the layer's bounds. let cutoutPath = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius).reversing() shadowPath.append(cutoutPath) self.shadowPath = shadowPath.cgPath } }


हमें CAShapeLayer आवश्यकता है क्योंकि हमें shadowPath सेट करना है। उसके लिए, हमें छाया के लिए एक पथ बनाने की आवश्यकता है जो सीमा से insetBy उपयोग करके परत की सीमा से थोड़ा बाहर तक फैली हुई है। और फिर एक और UIBezierPath - एक कटआउट पथ जो परत की सीमाओं का व्युत्क्रम है।


भीतरी छाया


चित्रण में, यह लगभग पहले से ही मौजूद है। लेकिन हमें आखिरी परत की जरूरत है, जो हमें MacOS X तेंदुए पर वापस लाएगी।

सीमा ढाल परत

यह CAGradientLayer का एक उपवर्ग है जिसे एक परत के चारों ओर एक ग्रेडिएंट बॉर्डर बनाने के लिए डिज़ाइन किया गया है। यह सीमा प्रभाव को प्राप्त करने के लिए मास्क के रूप में CAShapeLayer उपयोग करता है। shapeLayer स्ट्रोक रंग (शुरुआत में काला) और बिना किसी रंग के कॉन्फ़िगर किया गया है और इसे BorderGradientLayer के लिए मास्क के रूप में उपयोग किया जाता है। यह सेटअप ग्रेडिएंट को केवल वहीं दिखाई देने की अनुमति देता है जहां shapeLayer में एक स्ट्रोक होता है, जो प्रभावी रूप से एक ग्रेडिएंट बॉर्डर बनाता है।


 final class BorderGradientLayer: CAGradientLayer { let shapeLayer: CAShapeLayer = { let layer = CAShapeLayer() layer.strokeColor = UIColor.black.cgColor // Temporary color layer.fillColor = nil return layer }() init(borderWidth: CGFloat, colors: [UIColor]) { super.init() self.mask = shapeLayer self.colors = colors.map { $0.cgColor } self.setBorderWidth(borderWidth) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSublayers() { super.layoutSublayers() shapeLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius).cgPath } func setBorderWidth(_ borderWidth: CGFloat) { self.shapeLayer.lineWidth = borderWidth } }


BorderGradientLayer का इनिशियलाइज़र borderWidth और ग्रेडिएंट के लिए UIColor s की एक सरणी को स्वीकार करता है। यह ग्रेडिएंट रंगों को सेट करता है, बॉर्डर की चौड़ाई को shapeLayer पर लागू करता है, और ग्रेडिएंट को बॉर्डर क्षेत्र तक सीमित करने के लिए shapeLayer मास्क के रूप में उपयोग करता है।


layoutSublayers विधि यह सुनिश्चित करती है कि shapeLayer का पथ परत की सीमाओं और कोने की त्रिज्या से मेल खाने के लिए अद्यतन किया गया है, जिससे यह सुनिश्चित होता है कि बॉर्डर परत पर सही ढंग से फिट बैठता है। setBorderWidth विधि आरंभीकरण के बाद सीमा की चौड़ाई के गतिशील समायोजन की अनुमति देती है।


बॉर्डर के साथ


जिस तरह से बेहतर हो। ठीक है। आइए दृश्य सामग्री बनाना बंद करें, और तर्क पर काम करें।

सभी एक साथ

अब, इन सभी परतों को मिलाने और उन्हें जीवन में लाने का समय आ गया है।


 private lazy var backgroundLayer = BackgroundLayer( borderWidth: borderWidth, colors: borderColors ) private let glareLayer = ProgressGlareLayer() private let shimmeringLayer = ShimmeringLayer() private let progressMaskLayer = ProgressMaskLayer() private let progressBackgroundLayer = ProgressBackgroundLayer() public init() { super.init(frame: .zero) layer.backgroundColor = UIColor.white.cgColor layer.masksToBounds = true layer.addSublayer(progressMaskLayer) layer.addSublayer(progressBackgroundLayer) layer.insertSublayer(backgroundLayer, at: 0) progressBackgroundLayer.addSublayer(shimmeringLayer) progressBackgroundLayer.addSublayer(glareLayer) progressBackgroundLayer.mask = progressMaskLayer }


आइए देखें कि हम उन सभी परतों से कैसे निपटेंगे जिनकी चौड़ाई एनीमेशन के साथ बदलनी चाहिए:


 public func setValueWithAnimation(_ value: Double, duration: TimeInterval = 1.0, animationType: CAMediaTimingFunctionName = .easeInEaseOut, completion: (() -> Void)? = nil) { let newWidth = calculateProgressWidth(value) let animationGroup = DispatchGroup() animationGroup.enter() shimmeringLayer.animateToWidth( newWidth - WIDTH_ANIMATABLE_INSET, duration: duration, animationType: animationType, completion: animationGroup.leave ) animationGroup.enter() progressMaskLayer.animateToWidth( newWidth, duration: duration, animationType: animationType, completion: animationGroup.leave ) animationGroup.enter() glareLayer.animateToWidth( newWidth - WIDTH_ANIMATABLE_INSET, duration: duration, animationType: animationType, completion: animationGroup.leave ) animationGroup.notify(queue: .main) { completion?() } }


सबसे पहले, हमें एक मान से एक चौड़ाई तैयार करने की आवश्यकता है। मान 0 से कम और 1 से अधिक होना चाहिए। साथ ही हमें संभावित सीमा चौड़ाई को भी ध्यान में रखना होगा।


 fileprivate func normalizeValue(_ value: Double) -> Double { max(min(value, 1), 0) } private func calculateProgressWidth(_ value: Double) -> CGFloat { let normalizedValue = normalizeValue(value) let width = bounds.width * normalizedValue - borderWidth return width }


सही 3डी प्रभाव के लिए, चकाचौंध और झिलमिलाती परतें दाएं और बाएं तरफ से थोड़ी पैडिंग के साथ होनी चाहिए:


 newWidth - WIDTH_ANIMATABLE_INSET


और अंत में, विधि तीन परतों के एनिमेशन को सिंक्रनाइज़ करने के लिए एक एनीमेशन समूह ( DispatchGroup ) का उपयोग करती है: shimmeringLayer , progressMaskLayer , और glareLayer । प्रत्येक परत की चौड़ाई निर्दिष्ट अवधि के दौरान और निर्दिष्ट एनीमेशन वक्र के साथ गणना की गई चौड़ाई (डिज़ाइन को फिट करने के लिए shimmeringLayer और glareLayer के लिए WIDTH_ANIMATABLE_INSET द्वारा समायोजित) के अनुसार एनिमेटेड होती है।


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

सोर्स कोड

नवीनतम संस्करण स्विफ्ट पैकेज और पॉड के रूप में मौजूद है। तो आप इसे यहां रेट्रो प्रोग्रेस बार रिपॉजिटरी से प्राप्त कर सकते हैं। वैसे, योगदान का स्वागत है!

प्रयोग

रेट्रोप्रोग्रेसबार का एक उदाहरण बनाएं। आप इसे अपने दृश्य पदानुक्रम में जोड़ सकते हैं जैसे आप किसी भी UIView के साथ जोड़ सकते हैं।


 let progressBar = RetroProgressBar()


अनुकूलन:


 progressBar.progressColor = UIColor.systemBlue progressBar.cornerRadius = 5.0 progressBar.borderWidth = 2.0 progressBar.borderColors = [UIColor.white, UIColor.gray]


एनीमेशन के साथ मूल्य बदलने के लिए:


 progressBar.setValueWithAnimation(0.75, duration: 1.0, animationType: .easeInEaseOut) { print("Animation Completed") }


एनीमेशन के बिना:


 progressBar.setValue(0.5)

अंतिम विचार

इस पुरानी शैली की प्रगति पट्टी को फिर से बनाना सौंदर्यशास्त्र में एक उदासीन गोता लगाने जैसा है जिसे मैं गहराई से याद करता हूँ। UIKit और CALayer के साथ काम करने से मुझे डेवलपर्स के रूप में हमारे टूल की बहुमुखी प्रतिभा और शक्ति की याद आई। यह परियोजना केवल तकनीकी कार्यान्वयन के बारे में नहीं थी; यह एक प्रिय डिज़ाइन युग की ओर वापसी की यात्रा थी, जिसने साबित किया कि हम अभी भी कुछ भी तैयार कर सकते हैं, जिसमें पुराने डिज़ाइनों का कालातीत आकर्षण भी शामिल है।


यह अनुभव एक मार्मिक अनुस्मारक रहा है कि यूआई डिज़ाइन में, सादगी और पुरानी यादें अतीत और वर्तमान को जोड़ते हुए खूबसूरती से सह-अस्तित्व में रह सकती हैं।


हैप्पी कोडिंग!