मुझे यूआई घटक बनाना पसंद है। निर्माण के बाद उन्हें देखना और उनका उपयोग करना मुझे एक प्रकार का सौंदर्यात्मक आनंद देता है। यह समग्र रूप से MacOS/iOS पारिस्थितिकी तंत्र के प्रति मेरे प्रेम के साथ भी मेल खाता है। हाल ही में, मुझे UIKit
उपयोग करके एक कस्टम प्रगति पट्टी बनाने का काम सौंपा गया था। यह कोई बड़ी बात नहीं है, लेकिन संभवतः 99% iOS डेवलपर्स ने अपने करियर में किसी न किसी बिंदु पर इसे बनाया है। मेरे मामले में, मैं 0 से 1 तक की मान सीमा पर आधारित एनिमेशन के साथ कुछ CALayer
बनाने के अलावा कुछ और करना चाहता था। मैं एक ऐसे समाधान का लक्ष्य बना रहा था जो कई परिदृश्यों के लिए उपयुक्त होगा जहां प्रगति पट्टी की आवश्यकता होती है .
मैं खुद को उन दिनों में वापस ले जाना चाहता हूं जब Apple दुनिया में UI घटक इस तरह दिखते थे:
यूआई डिज़ाइन के विकास को दर्शाते हुए, लगभग एक दशक हो गया है जब ऐप्पल दुनिया में यूआई तत्वों ने 2014 में आईओएस 7 की शुरुआत के साथ एक फ्लैट डिज़ाइन अपनाया था। तो, आइए मैक ओएस और आईओएस के पुराने संस्करणों के लिए पुरानी यादों में शामिल हों और बनाएं एक सुंदर प्रगति पट्टी एक साथ।
यदि आप UIKit
उपयोग करना पसंद करते हैं लेकिन अभी भी इस बारे में अनिश्चित हैं कि CALayers में हेरफेर कैसे करें, तो यह लेख आपके लिए उपयोगी होगा। यहां, हम विशिष्ट लेआउट, एनीमेशन और ड्राइंग चुनौतियों से गुजरेंगे, और रास्ते में आपकी मदद करने के लिए अंतर्दृष्टि और समाधान प्रदान करेंगे।
प्रगति पट्टी सीधी लग सकती है, लेकिन यदि हम विवरण में गहराई से उतरें, तो आप समझेंगे कि इसके लिए बहुत सारी उप-परतों की आवश्यकता होती है।
मैं यहाँ CALayers
के बारे में विस्तार से नहीं बताने जा रहा हूँ। आप इस लेख को देख सकते हैं जहां मैंने संक्षेप में बताया है कि CALayers
क्या हैं और वे कैसे काम करते हैं: https://hackernoon.com/rolling-numbers-animation-using-only-calayers ।
तो, आइए इसे छोटी परतों में तोड़ें:
पृष्ठभूमि परत में दो अतिरिक्त चाइल्ड परतें हैं:
आइए इसे ठीक से संरचित करें:
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
के साथ काम करने से मुझे डेवलपर्स के रूप में हमारे टूल की बहुमुखी प्रतिभा और शक्ति की याद आई। यह परियोजना केवल तकनीकी कार्यान्वयन के बारे में नहीं थी; यह एक प्रिय डिज़ाइन युग की ओर वापसी की यात्रा थी, जिसने साबित किया कि हम अभी भी कुछ भी तैयार कर सकते हैं, जिसमें पुराने डिज़ाइनों का कालातीत आकर्षण भी शामिल है।
यह अनुभव एक मार्मिक अनुस्मारक रहा है कि यूआई डिज़ाइन में, सादगी और पुरानी यादें अतीत और वर्तमान को जोड़ते हुए खूबसूरती से सह-अस्तित्व में रह सकती हैं।
हैप्पी कोडिंग!