Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- clean architecture
- uitableview
- 클린 코드
- uiscrollview
- combine
- Human interface guide
- map
- Xcode
- UITextView
- 리펙터링
- collectionview
- ribs
- Refactoring
- HIG
- Protocol
- 애니메이션
- 스위프트
- swiftUI
- 리펙토링
- SWIFT
- rxswift
- tableView
- 리팩토링
- RxCocoa
- UICollectionView
- swift documentation
- ios
- Clean Code
- MVVM
- Observable
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - Swift] tableView, collectionView 스크롤 시 상단 뷰 흐리게 하는 방법 (네이버 웹툰 상단 뷰, Sticky Header) 본문
UI 컴포넌트 (swift)
[iOS - Swift] tableView, collectionView 스크롤 시 상단 뷰 흐리게 하는 방법 (네이버 웹툰 상단 뷰, Sticky Header)
jake-kim 2022. 11. 27. 22:33
구현 아이디어
- 상단에는 UIImageView, 하단에는 스크롤되는 UITableView나 UICollectionView 준비 (예제에서는 UICollectionView 사용)
- UIImageView와 UICollectionView 레이아웃
- UICollectionView의 topAnchor를 화면의 최상단으로 제약
- UICollectionView의 top contentInset값을 UIImageView의 크기만큼 설정 - UIImageView가 마치 collectionView의 하나의 셀처럼 보이도록 하기 위함
- 상단의 UIImageView도 마치 스크롤 되는 동작처럼 보여야하므로, scrollViewDidScroll(_:) 델리게이트에서, scrollView.contentOffset.y값을 이용하여 UIImageView의 height constraint 업데이트
구현 방법
- 두 가지 뷰 준비
- collectionView에서는 indicator를 숨김 (indicator의 시작점이 UIImageView에 있는 곳에 있을것이므로)
- collectionView의 top contentInset을 상단 높이 headerHeight 만큼 설정 (상단의 UIImageView도 마치 하나의 셀처럼 동작하게끔 하기 위함)
- UIImageView의 contentMode를 .scaleAspectFill로 설정 (높이를 변경해줄건데, 높이에 따라 너비도 비율에 맞게 달라지도록 하기 위함)
// ViewController.swift
// MARK: Constants
private enum Metric {
static let headerHeight = 250.0
}
// MARK: UI
private let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.minimumLineSpacing = 4.0
layout.minimumInteritemSpacing = 0
layout.itemSize = .init(width: UIScreen.main.bounds.width, height: 150)
let view = UICollectionView(frame: .zero, collectionViewLayout: layout)
view.isScrollEnabled = true
view.showsHorizontalScrollIndicator = false
// indicator 숨기기
view.showsVerticalScrollIndicator = false
// top의 간격
view.contentInset = .init(top: Metric.headerHeight, left: 0, bottom: 0, right: 0)
view.backgroundColor = .clear
view.clipsToBounds = true
view.register(CollectionViewCell.self, forCellWithReuseIdentifier: CollectionViewCell.id)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let headerImageView: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "image")
// contentMode를 .scaleAspectFill로
view.clipsToBounds = true
view.contentMode = .scaleAspectFill
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
- 레이아웃
- headerHeightConstraint 준비 - 스크롤 될떄마다 UIImageView의 height값을 변경해주어야 하므로 따로 선언
// MARK: Properties
private var headerHeightConstraint: NSLayoutConstraint?
// MARK: View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
[collectionView, headerImageView]
.forEach(view.addSubview)
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.topAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
headerHeightConstraint = headerImageView.heightAnchor.constraint(equalToConstant: Metric.headerHeight)
headerHeightConstraint?.isActive = true
NSLayoutConstraint.activate([
headerImageView.topAnchor.constraint(equalTo: view.topAnchor),
headerImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
headerImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
])
collectionView.dataSource = self
collectionView.delegate = self
}
- 스크롤했을때 상단의 UIImageView가 사라지거나, 다시 보이게끔 구현 방법
- scrollView.contentOffset.y 값을 사용하여 구현
- scrollView.contentOffset.y 값의 의미
- scrollView가 최상단에 닿으면 0
- scrollView의 시작점이 최상단보다 밑에 있으면 -
- scrollView의 시작점이 최상단보다 위에 있으면 +
- 3가지 상태가 존재
- 1) 초기 상태: UIImageView가 지정한 크기만큼 커졌고, 스크롤뷰의 시작점이 최상단보다 아래 존재
- 2) 스크롤 뷰의 시작점이 최상단보다 위에 존재
- 3) 스크롤 뷰의 시작점이 최상단보다 밑에 있고, 스크롤뷰 상단 contentInset이 미리 지정한 UIImageView 높이인, Metric.headerHeight보다 큰 경우
extension ViewController: UICollectionViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let constraint = headerHeightConstraint else { return }
// print(scrollView.contentOffset.y)
let remainingTopSpacing = abs(scrollView.contentOffset.y)
let lowerThanTop = scrollView.contentOffset.y < 0
let stopExpandHeaderHeight = scrollView.contentOffset.y > -Metric.headerHeight
if stopExpandHeaderHeight, lowerThanTop {
// 1) 초기 상태: UIImageView가 지정한 크기만큼 커졌고, 스크롤뷰의 시작점이 최상단보다 아래 존재
collectionView.contentInset = .init(top: remainingTopSpacing, left: 0, bottom: 0, right: 0)
constraint.constant = remainingTopSpacing
headerImageView.alpha = remainingTopSpacing / Metric.headerHeight
view.layoutIfNeeded()
} else if !lowerThanTop {
// 2) 스크롤 뷰의 시작점이 최상단보다 위에 존재
collectionView.contentInset = .zero
constraint.constant = 0
headerImageView.alpha = 0
} else {
// 3) 스크롤 뷰의 시작점이 최상단보다 밑에 있고, 스크롤뷰 상단 contentInset이 미리 지정한 UIImageView 높이인, Metric.headerHeight보다 큰 경우
constraint.constant = remainingTopSpacing
headerImageView.alpha = 1
}
}
}
'UI 컴포넌트 (swift)' 카테고리의 다른 글
[iOS - swift] Custom Button 만드는 방법 (커스텀 버튼) (1) | 2023.01.04 |
---|---|
[iOS - swift] extendable tableView 구현 방법 (동적으로 늘어나는 셀 구현), reloadRows(at:with:) (0) | 2022.12.25 |
[iOS - Swift] TimerView 구현 방법 (썸네일 테두리 회전 뷰, progress, CAShapeLayer, UIBezierPath) (0) | 2022.09.08 |
[iOS - swift] Wave Animation (웨이브 애니메이션) (2) | 2022.06.30 |
[iOS - swift] BottomSheet 구현 방법 (bottom sheet, floating panel) (0) | 2022.06.29 |
Comments