paint-brush
UI-Rückblick: Erstellen einer Retro-Fortschrittsleiste für iOS mit CALayersvon@maxkalik
684 Lesungen
684 Lesungen

UI-Rückblick: Erstellen einer Retro-Fortschrittsleiste für iOS mit CALayers

von Max Kalik22m2023/12/17
Read on Terminal Reader

Zu lang; Lesen

In diesem Leitfaden wird die Erstellung eines Fortschrittsbalkens im Retro-Stil in iOS erläutert, wobei CALayers für einen nostalgischen Touch verwendet wird. Es deckt den schrittweisen Prozess ab, vom Entwurf jeder Ebene bis zur Implementierung von Animationen, und bietet Einblicke in die Kombination von Old-School-Ästhetik mit moderner iOS-Entwicklung. Der Artikel ist ideal für Entwickler und UI-Designer und bietet sowohl technische Tiefe als auch kreative Inspiration für einzigartige UI-Komponenten. Quellcode: https://github.com/maxkalik/RetroProgressBar
featured image - UI-Rückblick: Erstellen einer Retro-Fortschrittsleiste für iOS mit CALayers
Max Kalik HackerNoon profile picture
0-item


Ich liebe es, UI-Komponenten zu erstellen. Es bereitet mir eine Art ästhetisches Vergnügen, sie nach der Erstellung eines Bauwerks zu sehen und zu verwenden. Dazu passt auch meine Liebe zum MacOS/iOS-Ökosystem insgesamt. Kürzlich wurde ich damit beauftragt, mit UIKit einen benutzerdefinierten Fortschrittsbalken zu erstellen. Es ist keine große Sache, aber wahrscheinlich haben 99 % der iOS-Entwickler irgendwann in ihrer Karriere eines von Grund auf erstellt. In meinem Fall wollte ich etwas mehr tun, als nur ein paar CALayer s mit Animationen basierend auf einem Wertebereich von 0 bis 1 zu erstellen. Ich strebte eine Lösung an, die für viele Szenarien geeignet ist, in denen ein Fortschrittsbalken benötigt wird .


Ich möchte mich in die Zeit zurückversetzen, als UI-Komponenten in der Apple-Welt so aussahen:


Quelle: https://www.smashingmagazine.com/2010/08/free-wireframing-kits-ui-design-kits-pdfs-and-resources/


Wenn wir über die Entwicklung des UI-Designs nachdenken, ist es fast ein Jahrzehnt her, seit UI-Elemente in der Apple-Welt mit der Einführung von iOS 7 im Jahr 2014 ein flaches Design angenommen haben. Lassen Sie uns also in der Nostalgie für die früheren Versionen von Mac OS und iOS schwelgen und kreativ sein ein schöner Fortschrittsbalken zusammen.


Wenn Sie gerne UIKit verwenden, sich aber noch nicht sicher sind, wie Sie CALayers manipulieren, ist dieser Artikel hilfreich für Sie. Hier gehen wir typische Layout-, Animations- und Zeichenherausforderungen durch und bieten Einblicke und Lösungen, die Sie dabei unterstützen.

Von UIView bis tief in die CALayers-API.

Der Fortschrittsbalken mag einfach erscheinen, aber wenn wir uns eingehend mit den Details befassen, werden Sie verstehen, dass dafür viele Unterebenen erforderlich sind.


Retro-Fortschrittsbalken


Ich werde hier nicht näher auf CALayers eingehen. Sie können diesen Artikel lesen, in dem ich kurz beschrieben habe, was CALayers sind und wie sie funktionieren: https://hackernoon.com/rolling-numbers-animation-using-only-calayers .


Teilen wir es also in kleine Schichten auf:

  1. Allgemeine Hintergrundebene
  2. Maskenebene (animierbarer Teil)
  3. Fortschrittshintergrundebene
  4. Blendschicht
  5. Schimmernde Schicht


Die Hintergrundebene verfügt über zwei zusätzliche untergeordnete Ebenen:

  1. Innere Schattenebene
  2. Randverlaufsebene


Lassen Sie es uns richtig strukturieren:


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


Kurz gesagt, so sollte es funktionieren: Der Fortschrittsbalken sollte auf einen Wert zwischen 0,0 und 1,0 reagieren. Wenn sich der Wert ändert, müssen wir die Breite einer Ebene basierend auf diesem Wert anpassen:


 bounds.width * value


Mit dieser einfachen Berechnung können wir die Breite mit CATransaction ändern. Bevor wir dies tun, müssen wir jedoch verstehen, welche Ebenen sich ändern sollen, wenn sich der Wert ändert. Schauen wir uns die Struktur noch einmal an und identifizieren wir die Schichten, die auf Wertänderungen reagieren müssen.


 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]


Es scheint, dass wir ein Protokoll entwickeln müssen, das von den Ebenen übernommen werden kann, die eine animierte Breite basierend auf dem Wert benötigen.

ProgressAnimatable-Protokoll

Im Protokoll benötigen wir nur zwei Methoden: Ändern der Ebenenbreite mit und ohne Animation.


 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)?) }


Implementierung:


 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() } }


Beide Methoden verwenden CATransaction . Die erste Methode mag seltsam erscheinen, da wir nur einen Wert ohne Animation ändern müssen. Warum ist CATransaction notwendig?


Versuchen Sie, einen einfachen CALayer zu erstellen und seine Breite oder Höhe ohne Animation zu ändern. Wenn Sie einen Build ausführen, werden Sie die CALayer Änderungen anhand einer Animation bemerken. Wie ist das möglich? In Core Animation werden Änderungen an Ebeneneigenschaften (z. B. Grenzen, Position, Deckkraft usw.) normalerweise basierend auf der dieser Eigenschaft zugeordneten Standardaktion animiert. Dieses Standardverhalten soll reibungslose visuelle Übergänge ermöglichen, ohne dass für jede Eigenschaftsänderung explizite Animationen erforderlich sind.


Das bedeutet, dass wir Aktionen in CALayer explizit deaktivieren müssen. Durch Festlegen von CATransaction.setDisableActions(true) stellen Sie sicher, dass die Breite der Ebene sofort und ohne zwischenzeitlichen animierten Übergang auf den neuen Wert aktualisiert wird.


Die zweite Methode bietet eine flexible und wiederverwendbare Möglichkeit, die Breite jeder Ebene entsprechend dem ProgressAnimatable Protokoll zu animieren. Es bietet Kontrolle über die Dauer und das Tempo der Animation und enthält eine Option für eine Abschlussaktion. Dadurch eignet es sich gut für verschiedene Animationsanforderungen innerhalb des UIKit-Frameworks.


Unsere Kandidaten für die Einhaltung dieses Protokolls werden drei Ebenen sein: Animierte Maskenebene, Blendebene und Schimmerebene. Lassen Sie uns weitermachen und es umsetzen.

ProgressMaskLayer

Es ist Zeit, sich an unser Protokoll anzupassen, und die wichtigste Ebene in diesem Setup wird unser allgemeiner CAShapeLayer sein. Warum brauchen wir diese Maske? Warum kann es nicht einfach ein CALayer mit einer animierten Breite sein? Als iOS-Ingenieur , insbesondere als jemand, der sich auf die Front-End-Entwicklung konzentriert, müssen Sie häufig potenzielle Anwendungsfälle für Ihre Komponenten vorhersehen. In meinem Beispiel gibt es eine Fortschrittshintergrundebene. Dies ist kein animierter CALayer , aber was wäre, wenn er mit etwas wie CAReplicatorLayer animiert wäre? Wenn Sie sich Gedanken über die Leistung machen, würden Sie wahrscheinlich darüber nachdenken, dass eine solche Ebene einmal gerendert werden sollte und dann einfach ihre Animation ausgeführt werden sollte. Deshalb kann diese Maske äußerst nützlich sein. Es ermöglicht ein effizientes Rendering und bietet gleichzeitig die Flexibilität, die für verschiedene Animationsszenarien erforderlich ist.


 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") } }


Werfen wir einen Blick auf diese Zeile:


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


Diese Zeile ist entscheidend für unseren Fortschrittsbalken. Standardmäßig sind CALayers zentriert, wobei sich der anchorPoint in der Mitte der Ebene befindet und nicht wie allgemein angenommen in der Mitte des linken Randes. Der anchorPoint ist der Punkt, um den herum alle Transformationen der Ebene stattfinden. Bei einer Ebene, die als Fortschrittsbalken verwendet wird, bedeutet diese Einstellung normalerweise, dass sich die Ebene bei einer Änderung ihrer Breite vom anchorPoint aus gleichmäßig in beide Richtungen ausdehnt. Wenn wir jedoch den anchorPoint auf die Mitte des linken Rands anpassen, wird die Ebene nur nach rechts erweitert, während der linke Rand an seinem Platz bleibt. Dieses Verhalten ist für einen Fortschrittsbalken wichtig und stellt sicher, dass das Wachstum des Balkens von einer Seite und nicht von beiden Seiten angezeigt wird.


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


Im obigen Snippet initialisieren wir zwei Hauptteile der Fortschrittsleiste: ProgressMaskLayer und ProgressBackgroundLayer . Werfen wir einen Blick auf den zweiten.


 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 }) } }


Wie Sie sehen, handelt es sich um einen CAGradientLayer mit drei Farben. Diese Implementierung wandelt eine injizierte Farbe in einen vertikalen Farbverlauf um, wodurch die Sichtbarkeit des Fortschrittsbalkens verbessert und ihm ein voluminöseres Aussehen verliehen wird.


Gradient

Um dies zu ermöglichen, habe ich zwei Methoden in einer CGColor Erweiterung vorbereitet. Aus Spaß habe ich beschlossen, CGColor nicht zurück in UIColor zu konvertieren, sondern ausschließlich im Bereich von CGColor zu spielen:


 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 } }


Wie Sie sehen, ist es nicht allzu kompliziert. Wir müssen nur colorSpace , components ( red , green , blue und alpha ) und model abrufen. Das bedeutet, dass wir anhand des Wertes eine neue Farbe berechnen können: entweder dunkler oder heller.


Blendschicht

Lassen Sie uns dazu übergehen, unseren Fortschrittsbalken dreidimensionaler zu gestalten. Jetzt müssen wir eine weitere Ebene hinzufügen, die wir Glare nennen.


Es handelt sich um eine unkomplizierte Implementierung, die jedoch dem ProgressAnimatable Protokoll entsprechen sollte.


 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") } } 


Mit Blendung


Der Einfachheit halber benötigen wir hier nur eine Hintergrundfarbe mit Weiß und einem Alpha von 0,3.


Little Nice Shimmering (schimmernde Schicht)

Ich weiß, dass einige vielleicht sagen würden, dass MacOS X oder iOS vor Version 6 keine UI-Komponenten mit solchen schimmernden Effekten hatten. Aber wie gesagt, das ist wie ein Film, der von einer wahren Geschichte inspiriert ist. Dieses Schimmern verstärkt den 3D-Effekt und macht ihn besser sichtbar.


 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") } } }


Hier benötigen wir zwei Animationen (Opacity und Location), die in einer CAAnimationGroup zusammengefasst sind. Außerdem können wir durch die Verwendung CAAnimationGroup eine Pause zwischen Animationssitzungen einrichten. Diese Funktion ist sehr nützlich, da Clients diese Eigenschaft möglicherweise auch steuern möchten.


Mit Schimmer


Wie Sie auf dem Bild sehen können, werden dadurch weitere 3D-Effekte hinzugefügt, aber das reicht nicht ganz aus. Lassen Sie uns weitermachen.

Hintergrundebene

Wir müssen mit unserer statischen Hintergrundebene arbeiten, die unter der animierten Fortschrittslinie liegt. Wenn Sie sich erinnern, haben wir dort zwei zusätzliche Unterebenen: den inneren Schatten und den Randverlauf.


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


Ich würde sagen, dass diese beiden Ebenen vor einigen Jahren bei Suchanfragen recht beliebt waren, möglicherweise aufgrund von UI-Trends. Lasst uns unsere eigenen Versionen erstellen und sie in diesem Artikel für zukünftige Generationen verewigen :)

Innere Schattenschicht

Die Klasse soll eine Ebene mit einem inneren Schatteneffekt erstellen, der eine optisch ansprechende Möglichkeit sein kann, UI-Elementen Tiefe zu verleihen.


 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 } }


Wir brauchen einen CAShapeLayer weil wir shadowPath festlegen müssen. Dazu müssen wir mithilfe von insetBy from Bounds“ einen Pfad für den Schatten erstellen, der leicht über die Grenzen der Ebene hinausgeht. Und dann ein weiterer UIBezierPath – ein Ausschnittpfad, der das Gegenteil der Ebenengrenzen ist.


Inneren Schatten


In der Abbildung ist es schon fast da. Aber wir brauchen die letzte Ebene, die uns zurück zu MacOS X Leopard bringt.

Randverlaufsebene

Es handelt sich um eine Unterklasse von CAGradientLayer , die dazu dient, einen Verlaufsrahmen um eine Ebene zu erstellen. Es verwendet einen CAShapeLayer als Maske, um den Randeffekt zu erzielen. Der shapeLayer ist mit einer Strichfarbe (ursprünglich Schwarz) und ohne Füllfarbe konfiguriert und wird als Maske für den BorderGradientLayer verwendet. Durch diese Einrichtung ist der Farbverlauf nur dort sichtbar, wo der shapeLayer einen Strich aufweist, wodurch effektiv ein Farbverlaufsrand entsteht.


 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 } }


Der Initialisierer von BorderGradientLayer akzeptiert eine borderWidth und ein Array von UIColor s für den Farbverlauf. Es richtet die Verlaufsfarben ein, wendet die Rahmenbreite auf den shapeLayer an und verwendet den shapeLayer als Maske, um den Verlauf auf den Randbereich zu beschränken.


Die Methode layoutSublayers stellt sicher, dass der Pfad von shapeLayer so aktualisiert wird, dass er mit den Grenzen und dem Eckenradius der Ebene übereinstimmt, sodass der Rand korrekt zur Ebene passt. Die setBorderWidth Methode ermöglicht die dynamische Anpassung der Rahmenbreite nach der Initialisierung.


Mit Grenze


Viel besser. OK. Hören wir auf, visuelle Dinge zu machen, und arbeiten wir an der Logik.

Alle zusammen

Jetzt ist es an der Zeit, all diese Schichten zu kombinieren und zum Leben zu erwecken.


 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 }


Werfen wir einen Blick darauf, wie wir mit allen Ebenen umgehen, deren Breite sich durch Animation ändern soll:


 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?() } }


Zuerst müssen wir eine Breite aus einem Wert vorbereiten. Der Wert sollte kleiner als 0 und größer als 1 sein. Außerdem müssen wir eine mögliche Rahmenbreite berücksichtigen.


 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 }


Für den richtigen 3D-Effekt sollten Glanz- und Schimmerschichten auf der rechten und linken Seite etwas gepolstert sein:


 newWidth - WIDTH_ANIMATABLE_INSET


Und schließlich verwendet die Methode eine Animationsgruppe ( DispatchGroup ), um die Animationen von drei Ebenen zu synchronisieren: shimmeringLayer , progressMaskLayer und glareLayer . Die Breite jeder Ebene wird über die angegebene Dauer und mit der angegebenen Animationskurve auf die berechnete Breite animiert (angepasst durch WIDTH_ANIMATABLE_INSET für shimmeringLayer und glareLayer , um sie an das Design anzupassen).


Die Animationen werden mithilfe der DispatchGroup koordiniert, um sicherzustellen, dass alle Animationen gleichzeitig starten und der Abschlussabschluss erst aufgerufen wird, nachdem alle Animationen abgeschlossen sind. Diese Methode bietet eine optisch ansprechende Möglichkeit, den Wert des Fortschrittsbalkens mit einer reibungslosen, synchronisierten Animation über die verschiedenen dekorativen Ebenen hinweg zu aktualisieren.

Quellcode

Die neueste Version liegt als Swift-Paket und Pod vor. Sie können es also hier im Retro Progress Bar-Repository herunterladen. Übrigens, Beiträge sind willkommen!

Verwendung

Erstellen Sie eine Instanz von RetroProgressBar. Sie können es wie jedes andere UIView zu Ihrer Ansichtshierarchie hinzufügen.


 let progressBar = RetroProgressBar()


Anpassung:


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


So ändern Sie den Wert mit Animation:


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


Ohne Animation:


 progressBar.setValue(0.5)

Abschließende Gedanken

Die Neuerstellung dieses Fortschrittsbalkens im alten Stil war ein nostalgischer Tauchgang in die Ästhetik, die ich zutiefst vermisse. Die Arbeit mit UIKit und CALayer hat mich als Entwickler an die Vielseitigkeit und Leistungsfähigkeit unserer Tools erinnert. Bei diesem Projekt ging es nicht nur um die technische Umsetzung; Es war eine Reise zurück in eine beliebte Design-Ära und bewies, dass wir immer noch alles herstellen können, auch den zeitlosen Charme älterer Designs.


Diese Erfahrung war eine ergreifende Erinnerung daran, dass beim UI-Design Einfachheit und Nostalgie wunderbar nebeneinander existieren können und eine Brücke zwischen Vergangenheit und Gegenwart schlagen.


Viel Spaß beim Codieren!