製作簡易柱狀圖動畫
這次主題本來是只有了解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)