#18 swift animate 進階動畫(一)

UICollectionView, UIGestureRecognizer, UIPanGestureRecognizer, UIViewPropertAnimator

製造橫向的UICollectionView, 點擊cell會有展開與折疊效果
且加上拖曳功能,能夠隨手勢拖曳控制展開的動畫進度。

UICollectionView及UICollectionViewFlowLayout
由以下網站教學下載

https://www.appcoda.com.tw/interactive-animation-uiviewpropertyanimator/

collectionView的位置、layout設定都先由flowLayout頁面設定

UIViewController雖有匯入UICollectionViewDataSource 但大部分的Cell顯示內容依然由 UICollectionViewCell 進行,包括按鈕、物件、顯示與否、動作、動畫等。

-----------------------

故整篇的重點集中在 UICollectionViewCell 頁面。

共分為4個部分

  1. enum Cell 顯示狀態
  2. 基礎變數與常數設置
  3. 開啟與收合的function及動畫
  4. 手勢設定

((1

//追蹤被選擇的Cell狀態
private enum State {
case expanded
case collapsed

var change: State {
switch self {
case .expanded: return .collapsed
case .collapsed: return .expanded
}
}
}

若為展開 回傳 收合,若為收合 回傳 展開。

((2

class CityCollectionViewCell: UICollectionViewCell, UIGestureRecognizerDelegate {

//加入手勢 UIGestureRecognizerDelegate

private let cornerRadius: CGFloat = 6 //圓角
private var initialFrame: CGRect? //Cell框大小會改變,故先紀錄原先大小,方便收合
private var state: State = .collapsed //預設cell合起來的

private lazy var animator: UIViewPropertyAnimator = {
return UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut)
}() //設定動畫秒數及形式



static let cellSize = CGSize(width: 250, height: 350)
static let identifier = "CityCollectionViewCell"

@IBOutlet weak var cityTitle: UILabel!
@IBOutlet weak var cityImage: UIImageView!
@IBOutlet weak var descriptionLabel: UILabel!
@IBOutlet weak var closeButton: UIButton!

private var collectionView: UICollectionView?
private var index: Int?


func configure(with city: City, collectionView: UICollectionView, index: Int) {
//()參數對應到UIViewController的DATASource

cityTitle.text = city.name
cityImage.image = UIImage(named: city.image)
descriptionLabel.text = city.description

self.collectionView = collectionView
self.index = index
} //在viewController顯示

//cell裡面的關閉按鈕
@IBAction func close(_ sender: Any) {
toggle()
}
//ViewController動作,改變狀態
func toggle() {
switch state {
case .expanded:
collapse()
case .collapsed:
expand()
}
}

((3以展開為例

//展開
private func expand() {
guard let collectionView = self.collectionView, let index = self.index else { return }

animator.addAnimations {
self.initialFrame = self.frame //initialFrame有值 後續用來折疊

//不透明
self.descriptionLabel.alpha = 1
self.closeButton.alpha = 1

//沒圓角,與邊界貼合(全螢幕)
self.layer.cornerRadius = 0
self.frame = CGRect(x: collectionView.contentOffset.x, y: 0, width: collectionView.frame.width, height: collectionView.frame.height)
}
//contentOffset??

//左邊的cell動畫
if let leftCell = collectionView.cellForItem(at: IndexPath(row: index - 1, section: 0)) {
leftCell.center.x -= 50
}

//右邊的cell動畫
if let rightCell = collectionView.cellForItem(at: IndexPath(row: index + 1, section: 0)) {
rightCell.center.x += 50
}

self.layoutIfNeeded()

//完成後的動作
animator.addCompletion { position in
switch position {
case .end:
self.state = self.state.change
collectionView.isScrollEnabled = false
collectionView.allowsSelection = false
default:
()
}
}

animator.startAnimation()
}

((4

手勢前設置

private let popupOffset: CGFloat = (UIScreen.main.bounds.height - cellSize.height)/2.0
//螢幕高-cellSize高/2


//拖曳手勢
private lazy var panRecognizer: UIPanGestureRecognizer = {
let recognizer = UIPanGestureRecognizer()
recognizer.addTarget(self, action: #selector(popupViewPanned(recognizer:)))

recognizer.delegate = self
return recognizer
}()

//比較垂直速度與水平速度,判斷其手勢為水平移動或垂直移動
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return abs((panRecognizer.velocity(in: panRecognizer.view)).y) > abs((panRecognizer.velocity(in: panRecognizer.view)).x)
}

//加入拖曳手勢
override func awakeFromNib() {
self.addGestureRecognizer((panRecognizer))
}

private var animationProgress: CGFloat = 0 //儲存動畫進度

手勢執行

@objc func popupViewPanned(recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
toggle()
animator.pauseAnimation()
animationProgress = animator.fractionComplete


case .changed:
let translation = recognizer.translation(in: collectionView)
var fraction = -translation.y / popupOffset
if state == .expanded { fraction *= -1 }
animator.fractionComplete = fraction + animationProgress

//faractionComplata放手後繼續進行動畫
//animationProgress記住拖曳進度

case .ended:
animator.continueAnimation(withTimingParameters: nil, durationFactor: 0)

default:
()
}
}
Photo by Eilis Garvey on Unsplash

下週開始到新公司就職,希望能夠順利。

--

--

--

Wish me luck on the way become iOS app developer!

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Stephen Huang

Stephen Huang

Wish me luck on the way become iOS app developer!

More from Medium

Cisco IOS Ultimate Guide

How we started unit testing Boozt iOS app

iOS 15 Live Text: Filtering for Custom Content Types Using Regex

8 web development trends 2022