기본적인 구현 방식은 다음과 같습니다.
고민을 많이 했던 부분은
스티키 헤더뷰가 올라갈때 스크롤 뷰도 같이 올라가는 것을 구현하는 부분이었습니다. 둘의 올라가는 속도가 달랐을때 이를 스케일링하여 구현하였습니다.
UIScrollViewDelegate 상속 받아서 scrollView의 스크롤 이벤트 처리를 위임받을 수 있도록 합니다.
사용된 UIView는 postStickyHeaderView, postHeaderView, scrollView, postTableView 입니다.
class PostViewController: UIViewController, UIScrollViewDelegate {
...
var postStickyHeaderView = PostStickyHeaderView()
var postHeaderView = PostHeaderView()
var scrollView = UIScrollView()
var postTableView = UITableView()
.then {
$0.register(PostTableViewCell.self, forCellReuseIdentifier: PostTableViewCell.identifier)
$0.separatorStyle = .none
$0.isScrollEnabled = false
$0.showsVerticalScrollIndicator = false
}
var postStickyHeaderViewTopConstraint = NSLayoutConstraint()
var scrollViewTopConstraint = NSLayoutConstraint()
var postTableViewHeightConstraint = NSLayoutConstraint()
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
}
...
}
UITableView의 스크롤 방지는 #1 에서 구현 했습니다.
UITableView의 높이를 컨텐츠 사이즈로 설정하여 높이가 화면밖으로 튀어나갈 수 있도록 설정해 주어야 합니다.
func setView() {
// ... 생략
scrollView.addSubview(postTableView)
postTableView.translatesAutoresizingMaskIntoConstraints = false
postTableViewHeightConstraint = postTableView.heightAnchor.constraint(equalToConstant: 0)
NSLayoutConstraint.activate([
scrollViewTopConstraint,
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
postTableView.topAnchor.constraint(equalTo: scrollView.topAnchor),
postTableView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
postTableView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
postTableViewHeightConstraint,
])
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
postTableViewHeightConstraint.constant = postTableView.contentSize.height
}
viewWillLayoutSubviews를 오버라이딩 하여 TableView의 높이를 직접 설정해주었습니다. 이렇게 하면 ScrollView안에 TableView가 들어갑니다.
[?] UITableView는 내부적으로 UIScrollView를 상속 받고, 위 과정은 UITableView의 Scroll 기능을 해제하는 과정입니다.
스티키 헤더 뷰의 TopAnchor을 잡아주고 스크롤 뷰의 TopAnchor를 잡아줍니다.
2개의 뷰의 변화만 있을 예정입니다.
func setView() {
// ... 생략
view.addSubview(postStickyHeaderView)
view.addSubview(postHeaderView)
view.addSubview(scrollView)
postStickyHeaderView.translatesAutoresizingMaskIntoConstraints = false
postHeaderView.translatesAutoresizingMaskIntoConstraints = false
postStickyHeaderViewTopConstraint = postStickyHeaderView.topAnchor.constraint(equalTo: view.topAnchor)
scrollViewTopConstraint = scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: Const.Size.postHeaderMaxHeight)
NSLayoutConstraint.activate([
postStickyHeaderViewTopConstraint,
postStickyHeaderView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
postStickyHeaderView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
postStickyHeaderView.heightAnchor.constraint(equalToConstant: Const.Size.postHeaderMaxHeight),
postHeaderView.topAnchor.constraint(equalTo: view.topAnchor),
postHeaderView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
postHeaderView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
postHeaderView.heightAnchor.constraint(equalToConstant: Const.Size.postHeaderMinHeight),
scrollViewTopConstraint,
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
delegate로 상속받은 후 scrollViewDidScroll를 구현해 줍니다.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
var headerConstant = scrollView.contentOffset.y
var tableConstant = CGFloat()
headerConstant = headerConstant < 0 ? 0 : headerConstant
headerConstant = headerConstant > Const.Size.postHeaderMinHeight ? Const.Size.postHeaderMinHeight : headerConstant
tableConstant = Const.Size.postHeaderMaxHeight - ((headerConstant - 0) / (Const.Size.postHeaderMinHeight)) * (Const.Size.postHeaderMaxHeight - Const.Size.postHeaderMinHeight)
postStickyHeaderViewTopConstraint.constant = -headerConstant
scrollViewTopConstraint.constant = tableConstant
postStickyHeaderView.alpha = 1 - headerConstant / Const.Size.postHeaderMinHeight
postHeaderView.titleView.alpha = headerConstant / Const.Size.postHeaderMinHeight
postHeaderView.rightLabel.alpha = 1
}
[?] 스티키 헤더 뷰가 올라가면 스크롤 뷰도 따라서 올라가게 해야합니다. 하지만, 완전 끝까지 올라가면 안되고 postHeaderMinHeight 만큼 상단과의 여유를 두어야 합니다. 스티키 헤더뷰는 끝까지 다 올라가지만, 스크롤 뷰는 최소 여유가 존재하므로 둘의 속도가 다르고 이를 스케일링 하는 과정을 해주었습니다. 검색키워드는 정규화 Scaling 입니다.
스티키 헤더 뷰가 10 올라가면 스크롤 뷰는 7정도 올라간다고 생각하면 됩니다.
GitHub link for the finished project?