Skip to content

Instantly share code, notes, and snippets.

@audrl1010
Created April 1, 2023 04:58
Show Gist options
  • Save audrl1010/1d5779df49b056aa03c93d79977f198d to your computer and use it in GitHub Desktop.
Save audrl1010/1d5779df49b056aa03c93d79977f198d to your computer and use it in GitHub Desktop.
parallax scrolling.md
final class ParallaxContentView: UIView {
  
  lazy var collectionView = UICollectionView(
    frame: .zero,
    collectionViewLayout: ParallaxFlowLayout()
  ).then {
    $0.contentInsetAdjustmentBehavior = .never
    $0.dataSource = self
    $0.delegate = self
    $0.register(ParallaxCell.self, forCellWithReuseIdentifier: "cell")
    $0.register(ParallaxHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "header")
  }
  
  override init(frame: CGRect) {
    super.init(frame: frame)
    self.setup()
  }
  
  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
  
  private func setup() {
    self.addSubview(self.collectionView)
    
    self.collectionView.snp.makeConstraints {
      $0.edges.equalToSuperview()
    }
  }
}

extension ParallaxContentView: UICollectionViewDelegateFlowLayout {
  
  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
    let width: CGFloat = collectionView.frame.width
    let height: CGFloat = 480
    return CGSize(width: width, height: height)
  }
}

extension ParallaxContentView: UICollectionViewDataSource {
  
  func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return 100
  }
  
  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return CGSize(width: collectionView.bounds.width, height: 300)
  }
  
  func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
    return cell
  }
  
  func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
    let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "header", for: indexPath)
    
    return header
  }
}

final class ParallaxFlowLayout: UICollectionViewFlowLayout {
  
  override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
    return true
  }
  
  override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    let layoutAttributes = super.layoutAttributesForElements(in: rect)
    
    guard let offset = collectionView?.contentOffset, let stLayoutAttributes = layoutAttributes else {
      return layoutAttributes
    }
    
    if offset.y < 0 { // stretchy header 늘어나는 효과....!!!
      for attributes in stLayoutAttributes {
        if let elmKind = attributes.representedElementKind, elmKind == UICollectionView.elementKindSectionHeader {
          let diffValue = abs(offset.y)
          var frame = attributes.frame
          frame.size.height = max(0, 480 + diffValue)
          frame.origin.y = frame.minY - diffValue
          attributes.frame = frame
        }
      }
    } else { // 헤더가 점점 화면에서 사라져야 할 때
      for attributes in stLayoutAttributes {
        if let elmKind = attributes.representedElementKind, elmKind == UICollectionView.elementKindSectionHeader {
          let diffValue = abs(offset.y)
          var frame = attributes.frame
          frame.size.height = max(0, 480 - diffValue)
          frame.origin.y = frame.minY + diffValue
          attributes.frame = frame
          attributes.alpha =  1 - diffValue/480
        }
      }
    }
    
    return layoutAttributes
  }
}


class ParallaxCell: UICollectionViewCell {
  
}

class ParallaxHeaderView: UICollectionReusableView {
  
  let imageView = UIImageView().then {
    $0.contentMode = .scaleAspectFill
    $0.isUserInteractionEnabled = true
    $0.clipsToBounds = true
    $0.image = UIImage(named: "product")
  }
  
  override init(frame: CGRect) {
    super.init(frame: frame)
    self.setup()
  }
  
  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
  
  private func setup() {
    self.addSubview(self.imageView)
    
    self.imageView.snp.makeConstraints {
      $0.edges.equalToSuperview()
    }
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment