製作簡易柱狀圖動畫

Steven_唐
彼得潘的 Swift iOS App 開發教室
7 min readSep 30, 2022

--

bar chart icon 4627986 Vector Art at Vecteezy

這次主題本來是只有了解layoutIfNeeded的運用,後來加了個Label顯示View目前的高度後,突然靈光一閃,心想可以試著讓這個Label在調整數字時有類似滾動的效果。

成果圖:

製作過程

StoryBoard排版

主要有UIButton、UIView、UILabel各一個。

IBOutlet&IBAction

由NSLayoutConstraint控制View高度。valueLabel改為NumberRollingLabel,等等會介紹到。

animate這個function用來控制constraint的值,當值經過變更後執行layoutIfNeeded就會更新畫面。

if viewHeight.constant == 50.0 {   viewHeight.constant = violetView.frame.height + 200   valueLabel.text = String(format: "%.0f", viewHeight.constant)   valueLabel.countFromCurrent(to: Float(viewHeight.constant), duration: 1)} else {   viewHeight.constant = 50.0   valueLabel.text = String(format: "%.0f", viewHeight.constant)   valueLabel.countFromCurrent(to: Float(viewHeight.constant), duration: 1)}UIView.animate(withDuration: 1.0) {   self.view.layoutIfNeeded()}

viewDidLoad

這裡為專案預設的ViewController.swift file。設定view的預設高度以及倒圓角。

viewHeight.constant = 50.0violetView.setTopCornerRadius(radius: 20)

設定View只倒上面的兩個圓角。

extension UIView {   func setTopCornerRadius(radius: CGFloat) {      self.layer.cornerRadius = radius      self.clipsToBounds = true      self.layer.maskedCorners = [.layerMinXMinYCorner,    .layerMaxXMinYCorner]   } }

數字變化

建立新的Swift.file,接著寫一個class繼承UILabel並設定下列變數。

var animationDuration = 5.0 //給多少都可以var startingValue: Float = 0var destinationValue: Float = 0var progress: TimeInterval = 0var lastUpdateTime: TimeInterval = 0var totalTime: TimeInterval = 0var currentValue: Float { 
if progress >= totalTime { return destinationValue }
return startingValue + Float(progress / totalTime) * (destinationValue - startingValue)}

設定CADisplayLink為optional,接著透過function將CADisplayLink加入Runloop。

var timer: CADisplayLink?func addDisplayLink() {
timer = CADisplayLink(target: self, selector: #selector(self.updateValue(timer:)))
timer?.add(to: .main, forMode: .default)}

當progress的時間與totalTime相等或大於時(會超出那麼一點點,因DisplayLink是根據裝置幀數更新一次,也就是60左右。每秒now與lastUpdateTime約相減60次左右),將DisplayLink退出runloop,並使progress 的值與 totalTime相等。

@objc fileprivate func updateValue(timer: Timer) {   let now: TimeInterval = Date.timeIntervalSinceReferenceDate   progress += now - lastUpdateTime   lastUpdateTime = now   if progress >= totalTime {      self.timer?.invalidate()      self.timer = nil      progress = totalTime  }}

如果沒有將DisplayLink退出會發生上面這段程式碼一直不斷地被重複執行。

執行invalidate()

接著編寫count()並將它包在countFromCurrent(),完成後就能在Label呼叫countFromCurrent。

剩下最後一個步驟!將Storyboard上的UILabel Custom Class欄位填上剛完成的UILabel class。(這步驟沒完成在按下按鈕後將會Crash)

Github連結:

Steven-Tang-rong/NeedsLayout_demo: 製作簡易柱狀圖動畫 (github.com)

參考資料:

CADisplayLink · KKBOX iOS/Mac OS X 基本開發教材 (gitbooks.io)

tips/计时器CADisplayLink.md at master · pro648/tips (github.com)

--

--