Tôi thích xây dựng các thành phần giao diện người dùng. Nó mang lại cho tôi một loại niềm vui thẩm mỹ khi xem và sử dụng chúng sau khi xây dựng. Điều này cũng kết hợp với tình yêu của tôi dành cho hệ sinh thái MacOS/iOS nói chung. Gần đây, tôi được giao nhiệm vụ tạo thanh tiến trình tùy chỉnh bằng UIKit
. Đó không phải là vấn đề lớn, nhưng có lẽ 99% nhà phát triển iOS đã tạo ra một iOS từ đầu vào một thời điểm nào đó trong sự nghiệp của họ. Trong trường hợp của tôi, tôi muốn làm nhiều hơn là chỉ tạo một vài CALayer
với hình ảnh động dựa trên phạm vi giá trị từ 0 đến 1. Tôi đang hướng tới một giải pháp phù hợp với nhiều tình huống cần có thanh tiến trình .
Tôi muốn quay lại thời kỳ mà các thành phần giao diện người dùng trong thế giới Apple trông như thế này:
Phản ánh về sự phát triển của thiết kế giao diện người dùng, đã gần một thập kỷ kể từ khi các thành phần giao diện người dùng trong thế giới Apple áp dụng thiết kế phẳng với sự ra đời của iOS 7 vào năm 2014. Vì vậy, hãy cùng đắm chìm trong hoài niệm về các phiên bản Mac OS và iOS trước đó và tạo ra một thanh tiến trình đẹp cùng nhau.
Nếu bạn thích sử dụng UIKit
nhưng vẫn chưa chắc chắn về cách thao tác với CALayers thì bài viết này sẽ hữu ích cho bạn. Tại đây, chúng ta sẽ xem xét các thử thách về bố cục, hoạt ảnh và vẽ điển hình, đồng thời cung cấp thông tin chi tiết và giải pháp để trợ giúp bạn trong quá trình thực hiện.
Thanh tiến trình có vẻ đơn giản, nhưng nếu chúng ta đi sâu vào chi tiết, bạn sẽ hiểu rằng nó yêu cầu rất nhiều lớp con.
Tôi sẽ không đi sâu vào chi tiết về CALayers
ở đây. Bạn có thể xem bài viết này trong đó tôi mô tả ngắn gọn CALayers
là gì và cách chúng hoạt động: https://hackernoon.com/rolling-numbers-animation-USE-only-calayers .
Vì vậy, hãy chia nó thành các lớp nhỏ:
Lớp nền có thêm hai lớp con:
Hãy cấu trúc nó đúng cách:
RetroProgressBar [UIView] |--General background [CALayer] |--Inner Shadow [CALayer] |--Border Gradient [CALayer] |--Progress Background Layer [CALayer] |--Animated Mask Layer [CALayer] |--Glare [CALayer] |--Shimering [CALayer]
Tóm lại, đây là cách nó hoạt động: Thanh tiến trình sẽ phản ứng với giá trị nằm trong khoảng từ 0,0 đến 1,0. Khi giá trị thay đổi, chúng ta cần điều chỉnh độ rộng của lớp dựa trên giá trị này:
bounds.width * value
Sử dụng phép tính đơn giản này, chúng ta có thể sửa đổi độ rộng bằng CATransaction
. Nhưng trước khi làm điều này, chúng ta cần hiểu lớp nào sẽ thay đổi khi giá trị thay đổi. Hãy xem lại cấu trúc và xác định các lớp cần đáp ứng với những thay đổi về giá trị.
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]
Có vẻ như chúng ta cần phát triển một giao thức có thể được áp dụng bởi các lớp cần độ rộng hoạt ảnh dựa trên giá trị.
Trong giao thức, chúng ta chỉ cần hai phương pháp: thay đổi độ rộng của lớp có hoạt ảnh và không có hoạt ảnh.
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)?) }
Thực hiện:
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() } }
Cả hai phương pháp đều sử dụng CATransaction
. Phương pháp đầu tiên có vẻ kỳ quặc vì chúng ta chỉ cần thay đổi một giá trị mà không cần hoạt ảnh. Vậy tại sao CATransaction
lại cần thiết?
Hãy thử tạo CALayer
cơ bản và thay đổi chiều rộng hoặc chiều cao của nó mà không có bất kỳ hoạt ảnh nào. Khi chạy một bản dựng, bạn sẽ nhận thấy CALayer
thay đổi kèm theo hình ảnh động. Sao có thể như thế được? Trong Core Animation, các thay đổi đối với thuộc tính lớp (chẳng hạn như giới hạn, vị trí, độ mờ, v.v.) thường được tạo hoạt ảnh dựa trên hành động mặc định được liên kết với thuộc tính đó. Hành vi mặc định này nhằm mục đích cung cấp các chuyển tiếp trực quan mượt mà mà không cần hoạt ảnh rõ ràng cho mỗi thay đổi thuộc tính.
Điều này có nghĩa là chúng ta cần vô hiệu hóa rõ ràng các hành động trong CALayer
. Bằng cách đặt CATransaction.setDisableActions(true)
, bạn đảm bảo rằng chiều rộng của lớp được cập nhật ngay lập tức thành giá trị mới mà không cần bất kỳ chuyển đổi hoạt ảnh trung gian nào.
Phương pháp thứ hai cung cấp một cách linh hoạt và có thể tái sử dụng để tạo hiệu ứng cho chiều rộng của bất kỳ lớp nào tuân theo giao thức ProgressAnimatable
. Nó cung cấp khả năng kiểm soát thời lượng và nhịp độ của hoạt ảnh, đồng thời bao gồm tùy chọn cho hành động hoàn thành. Điều này làm cho nó rất phù hợp với các nhu cầu hoạt hình khác nhau trong khung UIKit.
Các ứng cử viên của chúng tôi tuân thủ giao thức này sẽ có ba lớp: Lớp mặt nạ hoạt hình, Lớp chói và Lớp lung linh. Hãy tiếp tục và thực hiện nó.
Đã đến lúc tuân thủ giao thức của chúng tôi và lớp quan trọng nhất trong thiết lập này sẽ là CAShapeLayer
chung của chúng tôi. Tại sao chúng ta cần mặt nạ này? Tại sao nó không thể chỉ là một CALayer
có chiều rộng hoạt ảnh? Với tư cách là Kỹ sư iOS , đặc biệt là người tập trung vào phát triển giao diện người dùng, bạn thường cần dự đoán các trường hợp sử dụng tiềm năng cho các thành phần của mình. Trong trường hợp của tôi, có Lớp nền tiến trình. Đây không phải là CALayer
hoạt hình, nhưng nếu nó được tạo hoạt hình bằng thứ gì đó như CAReplicatorLayer
thì sao? Nếu bạn lo ngại về hiệu suất, bạn có thể cân nhắc rằng một lớp như vậy sẽ được hiển thị một lần và sau đó chỉ cần thực hiện hoạt ảnh của nó. Đó là lý do tại sao mặt nạ này có thể cực kỳ hữu ích. Nó cho phép hiển thị hiệu quả trong khi vẫn mang lại sự linh hoạt cần thiết cho các tình huống hoạt hình khác nhau.
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") } }
Chúng ta hãy nhìn vào dòng này:
anchorPoint = CGPoint(x: 0, y: 0.5)
Dòng này rất quan trọng đối với Thanh tiến trình của chúng tôi. Theo mặc định, CALayers
được căn giữa, với anchorPoint
nằm ở giữa lớp chứ không phải ở giữa cạnh trái như thường được giả định. anchorPoint
là điểm xung quanh đó tất cả các biến đổi của lớp xảy ra. Đối với lớp được sử dụng làm thanh tiến trình, cài đặt này thường có nghĩa là khi chiều rộng của lớp thay đổi, nó sẽ mở rộng bằng nhau theo cả hai hướng từ anchorPoint
. Tuy nhiên, nếu chúng ta điều chỉnh anchorPoint
ở giữa cạnh trái, lớp sẽ chỉ mở rộng sang bên phải trong khi cạnh trái vẫn cố định tại chỗ. Hành vi này rất cần thiết cho thanh tiến trình, đảm bảo rằng sự phát triển của thanh xuất hiện từ một phía chứ không phải cả hai.
private let progressMaskLayer = ProgressMaskLayer() private let progressBackgroundLayer = ProgressBackgroundLayer() public init() { super.init(frame: .zero) layer.addSublayer(progressMaskLayer) layer.addSublayer(progressBackgroundLayer) progressBackgroundLayer.mask = progressMaskLayer }
Trong đoạn mã trên, chúng tôi khởi tạo hai phần chính của Thanh tiến trình: ProgressMaskLayer
và ProgressBackgroundLayer
. Chúng ta hãy nhìn vào cái thứ hai.
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 }) } }
Như bạn có thể thấy, đó là CAGradientLayer
có ba màu. Việc triển khai này biến đổi màu được chèn thành dải màu dọc, nâng cao khả năng hiển thị của thanh tiến trình và mang lại cho nó vẻ ngoài đồ sộ hơn.
Để thực hiện được điều này, tôi đã chuẩn bị hai phương pháp trong tiện ích mở rộng CGColor
. Để giải trí, tôi đã chọn không chuyển đổi CGColor
trở lại UIColor
mà chỉ chơi trong lĩnh vực 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 } }
Như bạn có thể thấy, nó không quá phức tạp. Chúng ta chỉ cần lấy colorSpace
, components
( red
, green
, blue
và alpha
) và model
. Điều này có nghĩa là dựa trên giá trị, chúng ta có thể tính toán màu mới: đậm hơn hoặc nhạt hơn.
Hãy hướng tới việc làm cho Thanh tiến trình của chúng ta trở nên 3D hơn. Bây giờ, chúng ta cần thêm một lớp khác, chúng ta sẽ gọi là Glare.
Đây là cách triển khai đơn giản nhưng phải tuân theo giao thức 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") } }
Để đơn giản, chúng ta chỉ cần màu nền ở đây là màu trắng với alpha là 0,3.
Tôi biết một số người có thể nói rằng MacOS X hoặc iOS trước phiên bản 6 không có các thành phần UI với hiệu ứng lung linh như thế này. Nhưng như tôi đã nói, đây giống như một bộ phim lấy cảm hứng từ một câu chuyện có thật. Sự lung linh này làm nổi bật hiệu ứng 3D và làm cho nó rõ ràng hơn.
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") } } }
Ở đây, chúng ta cần kết hợp hai ảnh động (Độ mờ và Vị trí) thành một CAAnimationGroup
. Ngoài ra, việc sử dụng CAAnimationGroup
cho phép chúng tôi sắp xếp tạm dừng giữa các phiên hoạt ảnh. Tính năng này khá hữu ích vì khách hàng cũng có thể muốn kiểm soát thuộc tính này.
Như bạn có thể thấy trong hình, điều này bổ sung thêm nhiều hiệu ứng 3D hơn, nhưng vẫn chưa đủ. Hãy tiến về phía trước.
Chúng ta cần làm việc với lớp nền tĩnh nằm bên dưới Đường tiến độ hoạt hình. Nếu bạn nhớ lại, chúng ta có hai lớp con bổ sung ở đó: Inner Shadow và Border gradient.
|--General background [CALayer] |--Inner Shadow [CALayer] |--Border Gradient [CALayer]
Tôi có thể nói rằng hai lớp này khá phổ biến trong các truy vấn tìm kiếm cách đây vài năm, có thể là do xu hướng giao diện người dùng. Hãy tạo ra những phiên bản của riêng mình và lưu giữ chúng trong bài viết này cho thế hệ tương lai :)
Lớp này được thiết kế để tạo một lớp có hiệu ứng bóng bên trong, đây có thể là một cách hấp dẫn về mặt hình ảnh để thêm chiều sâu cho các thành phần giao diện người dùng.
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 } }
Chúng tôi cần CAShapeLayer
vì chúng tôi phải đặt shadowPath
. Để làm được điều đó, chúng ta cần tạo một đường dẫn cho bóng hơi mở rộng ra ngoài giới hạn của lớp bằng cách sử dụng insetBy
từ giới hạn. Và sau đó là một UIBezierPath
khác - một đường dẫn bị cắt ngược với giới hạn của lớp.
Trong hình minh họa, nó gần như đã ở đó. Nhưng chúng ta cần lớp cuối cùng để đưa chúng ta trở lại MacOS X Leopard.
Đó là một lớp con của CAGradientLayer
được thiết kế để tạo đường viền chuyển màu xung quanh một lớp. Nó sử dụng CAShapeLayer
làm mặt nạ để đạt được hiệu ứng đường viền. shapeLayer
được định cấu hình với màu nét vẽ (ban đầu là màu đen) và không có màu tô và được sử dụng làm mặt nạ cho BorderGradientLayer
. Thiết lập này cho phép độ dốc chỉ hiển thị ở nơi shapeLayer
có nét, tạo đường viền chuyển màu một cách hiệu quả.
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 } }
Trình khởi tạo của BorderGradientLayer
chấp nhận borderWidth
và một mảng UIColor
cho gradient. Nó thiết lập các màu gradient, áp dụng độ rộng đường viền cho shapeLayer
và sử dụng shapeLayer
làm mặt nạ để hạn chế độ chuyển màu cho vùng viền.
Phương thức layoutSublayers
đảm bảo rằng đường dẫn của shapeLayer
được cập nhật để khớp với giới hạn và bán kính góc của lớp, đảm bảo đường viền khớp với lớp một cách chính xác. Phương thức setBorderWidth
cho phép điều chỉnh động độ rộng của đường viền sau khi khởi tạo.
Tốt hơn nhiều. Được rồi. Hãy ngừng tạo ra những thứ trực quan và hãy làm việc dựa trên logic.
Bây giờ là lúc kết hợp tất cả các lớp này và đưa chúng vào cuộc sống.
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 }
Chúng ta hãy xem cách chúng ta xử lý tất cả các lớp sẽ thay đổi độ rộng của chúng bằng hoạt ảnh:
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?() } }
Đầu tiên, chúng ta cần chuẩn bị chiều rộng từ một giá trị. Giá trị phải nhỏ hơn 0 và lớn hơn 1. Ngoài ra, chúng ta cần xem xét độ rộng đường viền có thể có.
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 }
Để có hiệu ứng 3D chính xác, các lớp chói và lung linh phải có một chút đệm từ bên phải và bên trái:
newWidth - WIDTH_ANIMATABLE_INSET
Và cuối cùng, phương thức này sử dụng một nhóm hoạt ảnh ( DispatchGroup
) để đồng bộ hóa hoạt ảnh của ba lớp: shimmeringLayer
, progressMaskLayer
và glareLayer
. Chiều rộng của mỗi lớp được tạo hiệu ứng động theo chiều rộng đã tính toán (được điều chỉnh bởi WIDTH_ANIMATABLE_INSET
để shimmeringLayer
và glareLayer
để phù hợp với thiết kế) trong khoảng thời gian được chỉ định và với đường cong hoạt ảnh được chỉ định.
Các hoạt ảnh được điều phối bằng cách sử dụng DispatchGroup
, đảm bảo rằng tất cả các hoạt ảnh bắt đầu đồng thời và việc đóng hoàn thành chỉ được gọi sau khi tất cả các hoạt ảnh đã kết thúc. Phương pháp này cung cấp một cách trực quan hấp dẫn để cập nhật giá trị của thanh tiến trình bằng hoạt ảnh mượt mà, đồng bộ trên các lớp trang trí khác nhau của nó.
Phiên bản mới nhất tồn tại dưới dạng Gói Swift và Pod. Vì vậy, bạn có thể lấy nó từ đây Kho lưu trữ Retro Progress Bar . Btw, đóng góp đều được chào đón!
Tạo một phiên bản của RetroProgressBar. Bạn có thể thêm nó vào hệ thống phân cấp chế độ xem của mình giống như với bất kỳ UIView nào.
let progressBar = RetroProgressBar()
Tùy chỉnh:
progressBar.progressColor = UIColor.systemBlue progressBar.cornerRadius = 5.0 progressBar.borderWidth = 2.0 progressBar.borderColors = [UIColor.white, UIColor.gray]
Để thay đổi giá trị bằng hình ảnh động:
progressBar.setValueWithAnimation(0.75, duration: 1.0, animationType: .easeInEaseOut) { print("Animation Completed") }
Không có hình ảnh động:
progressBar.setValue(0.5)
Việc tạo lại thanh tiến trình kiểu cũ này là một sự hoài niệm về tính thẩm mỹ mà tôi vô cùng nhớ. Làm việc với UIKit và CALayer
nhắc nhở tôi về tính linh hoạt và sức mạnh của các công cụ của chúng tôi với tư cách là nhà phát triển. Dự án này không chỉ là triển khai kỹ thuật; đó là cuộc hành trình quay trở lại kỷ nguyên thiết kế được yêu thích, chứng minh rằng chúng ta vẫn có thể tạo ra bất cứ thứ gì, bao gồm cả sự quyến rũ vượt thời gian của các thiết kế cũ.
Trải nghiệm này là một lời nhắc nhở sâu sắc rằng trong thiết kế giao diện người dùng , sự đơn giản và hoài cổ có thể cùng tồn tại một cách đẹp đẽ, bắc cầu giữa quá khứ và hiện tại.
Chúc mừng mã hóa!