#18 swift animate 進階動畫(一)

Stephen Huang
9 min readJul 13, 2021

--

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

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

--

--

Stephen Huang
Stephen Huang

Written by Stephen Huang

Wish me luck on the way become iOS app developer!

No responses yet