관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] 4. Hero - UIViewController간의 화면전환 애니메이션 프레임워크 (셀 이동, 뷰 이동 애니메이션 예시) 본문

iOS framework

[iOS - swift] 4. Hero - UIViewController간의 화면전환 애니메이션 프레임워크 (셀 이동, 뷰 이동 애니메이션 예시)

jake-kim 2022. 2. 26. 22:04

1. Hero - UIViewController간의 화면전환 애니메이션 프레임워크 (개념, UIPenGestureRecognizer)

2. Hero - UIViewController간의 화면전환 애니메이션 프레임워크 (modal)

3. Hero - UIViewController간의 화면전환 애니메이션 프레임워크 (편한 extension, hero.id를 이용한 애니메이션)

4. Hero - UIViewController간의 화면전환 애니메이션 프레임워크 (셀 이동, 뷰 이동 애니메이션 예시)

회색 뷰, cell 애니메이션

애니메이션 아이디어

  • 회색 뷰: 첫 번째 뷰와 두 번째 뷰의 hero.id를 같도록 설정
  • 빨간색 cell 애니메이션 설정
    • cell에는 단순히 hero.modifiers라는 프로퍼티로 애니메이션 설정이 간편하게 가능
collectionView.hero.modifiers = [.cascade]

...
// in cellForItemAt 델리게이트
cell.hero.modifiers = [.fade, .scale(0.5)]

첫 번째 뷰 준비 (아직 Hero 미적용)

ViewController.swift

import UIKit

class ViewController: UIViewController {
  private let button: UIButton = {
    let button = UIButton()
    button.setTitle("open", for: .normal)
    button.setTitleColor(.white, for: .normal)
    button.setTitleColor(.lightGray, for: .highlighted)
    button.backgroundColor = .systemGray
    button.layer.cornerRadius = 14
    button.layer.cornerCurve = .continuous
    return button
  }()
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    self.view.addSubview(self.button)
    self.button.translatesAutoresizingMaskIntoConstraints = false
    self.button.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: -40).isActive = true
    self.button.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -40).isActive = true
    self.button.widthAnchor.constraint(equalToConstant: 50).isActive = true
    self.button.heightAnchor.constraint(equalToConstant: 50).isActive = true
    
    self.button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
  }
  
  @objc private func didTapButton() {
    let vc2 = VC2()
    self.navigationController?.pushViewController(vc2, animated: true)
  }
}

두 번째 뷰 준비

VC2.swift

import UIKit

class VC2: UIViewController {
  private enum Constants {
    static let itemSize = CGSize(width: 60, height: 72)
    static let lineSpacing = 4.0
    static let minimumInteritemSpacing = 0.0
    static let collectionViewContentInset = UIEdgeInsets(top: 10, left: 6, bottom: 10, right: 6)
  }
  private let titleLabel: UILabel = {
    let label = UILabel()
    label.textColor = .white
    label.backgroundColor = .systemGray
    label.textAlignment = .center
    label.text = "예제 타이틀"
    return label
  }()
  private let collectionViewFlowLayout: UICollectionViewFlowLayout = {
    let view = UICollectionViewFlowLayout()
    view.itemSize = Constants.itemSize
    view.minimumLineSpacing = Constants.lineSpacing
    view.minimumInteritemSpacing = Constants.minimumInteritemSpacing
    return view
  }()
  private lazy var collectionView: UICollectionView = {
    let view = UICollectionView(frame: .zero, collectionViewLayout: self.collectionViewFlowLayout)
    view.contentInset = Constants.collectionViewContentInset
    view.backgroundColor = UIColor.clear
    view.register(MyCell.self, forCellWithReuseIdentifier: "MyCell")
    return view
  }()
  
  private var dataSource: [Int] { (0...50).map { $0 } }
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    self.view.backgroundColor = .white
    self.view.addSubview(self.collectionView)
    self.collectionView.translatesAutoresizingMaskIntoConstraints = false
    self.collectionView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
    self.collectionView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
    self.collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -56).isActive = true
    self.collectionView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 400).isActive = true
    self.collectionView.dataSource = self
    
    self.view.addSubview(self.titleLabel)
    self.titleLabel.translatesAutoresizingMaskIntoConstraints = false
    self.titleLabel.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 120).isActive = true
    self.titleLabel.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
    self.titleLabel.widthAnchor.constraint(equalToConstant: 100).isActive = true
    self.titleLabel.heightAnchor.constraint(equalToConstant: 60).isActive = true
  }
}

extension VC2: UICollectionViewDataSource {
  func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    self.dataSource.count
  }
  func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCell", for: indexPath) as! MyCell
    cell.prepare(text: "\(self.dataSource[indexPath.item])")
    return cell
  }
}

final class MyCell: UICollectionViewCell {
  private let label: UILabel = {
    let label = UILabel()
    label.textColor = .white
    label.textAlignment = .center
    return label
  }()
  
  override init(frame: CGRect) {
    super.init(frame: frame)
    
    self.contentView.backgroundColor = .red
    self.contentView.addSubview(self.label)
    self.label.translatesAutoresizingMaskIntoConstraints = false
    self.label.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
    self.label.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
    self.label.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
    self.label.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
    
    self.contentView.layer.cornerRadius = 14
    self.contentView.layer.cornerCurve = .continuous
  }
  
  @available(*, unavailable)
  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
  
  override func prepareForReuse() {
    super.prepareForReuse()
    self.prepare(text: nil)
  }
  
  func prepare(text: String?) {
    self.label.text = text
  }
}

회색 뷰에 애니메이션 적용

  • 첫 번째 뷰
    • 주의사항) navigationController에도 애니메이션 적용하려면 navigationController.isHeroEnabled 활성화 필요
// ViewController.swift
// in viewDidLoad()

self.navigationController?.isHeroEnabled = true
    
self.button.isHeroEnabled = true
self.button.hero.id = "myAnimation"
  • 두 번째 뷰
// VC2.swift

label.isHeroEnabled = true
label.hero.id = "myAnimation"

셀에 애니메이션 적용

// VC2.swift

// collectionView에 hero 애니메이션 적용
self.collectionView.hero.modifiers = [.cascade]

// in cellForItemAt 델리게이트
cell.hero.modifiers = [.fade, .scale(0.5)]

* 전체 코드: https://github.com/JK0369/ExHeroExample

Comments