Stretchy TableView Header in App (Swift 5, Xcode 12, iOS 2020) - iOS Development
stretchy
테이블 뷰 헤더 구현private func applyConstraints() {
let viewConstraints = [
widthAnchor.constraint(equalTo: containerView.widthAnchor),
centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
heightAnchor.constraint(equalTo: containerView.heightAnchor)
]
NSLayoutConstraint.activate(viewConstraints)
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.widthAnchor.constraint(equalTo: imageView.widthAnchor).isActive = true
containerViewHeightConstraint = containerView.heightAnchor.constraint(equalTo: heightAnchor)
containerViewHeightConstraint?.isActive = true
imageView.translatesAutoresizingMaskIntoConstraints = false
imageViewBottomConstraint = imageView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
imageViewBottomConstraint?.isActive = true
imageViewHeightConstraint = imageView.heightAnchor.constraint(equalTo: containerView.heightAnchor)
imageViewHeightConstraint?.isActive = true
}
containverView
를 서브 뷰로 삽입한 뒤 해당 containerView
에 imageView
를 삽입한 구조bottomAnchor
, heightAnchor
동일func scrollViewDidScroll(scrollView: UIScrollView) {
containerViewHeightConstraint?.constant = scrollView.contentInset.top
let offsetY = -(scrollView.contentOffset.y + scrollView.contentInset.top)
containerView.clipsToBounds = offsetY <= 0
imageViewBottomConstraint?.constant = offsetY >= 0 ? 0 : -offsetY / 2
imageViewHeightConstraint?.constant = max(offsetY + scrollView.contentInset.top, scrollView.contentInset.top)
}
scrollView.contentOffset.y
값을 통해 위/아래 스크롤을 감지 가능: 아래로 계속해서 스크롤할 경우 양수 값, 위로 올리면서 테이블 뷰를 내려갈 수록 음수 값의 offsetY
clipsToBounds
는 이러한 offsetY
가 0 이하일 때, 즉 테이블 뷰를 위로 올리는 순간 true
가 되면서 컨테이너 뷰가 주어진 바운드에 맞춰 클립import UIKit
final class StretchyTableViewHeaderView: UIView {
private let imageView: UIImageView = {
let imageView = UIImageView()
imageView.clipsToBounds = true
imageView.contentMode = .scaleAspectFill
return imageView
}()
private var imageViewHeightConstraint: NSLayoutConstraint?
private var imageViewBottomConstraint: NSLayoutConstraint?
private let containerView: UIView = {
let view = UIView()
return view
}()
private var containerViewHeightConstraint: NSLayoutConstraint?
override init(frame: CGRect) {
super.init(frame: frame)
setUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setUI() {
addSubview(containerView)
containerView.addSubview(imageView)
applyConstraints()
guard let url = URL(string: "https://cdn.vox-cdn.com/thumbor/2DRQBNSQ4tc2Sga4VL8kxew7dzE=/0x178:1199x778/fit-in/1200x600/cdn.vox-cdn.com/uploads/chorus_asset/file/13449801/DsI6yX_VYAAwXLz.jpg") else { return }
URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
guard
let data = data,
let response = response as? HTTPURLResponse,
response.statusCode >= 200 && response.statusCode < 400,
error == nil else { return }
DispatchQueue.main.async { [weak self] in
self?.imageView.image = UIImage(data: data)
}
}
.resume()
}
private func applyConstraints() {
let viewConstraints = [
widthAnchor.constraint(equalTo: containerView.widthAnchor),
centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
heightAnchor.constraint(equalTo: containerView.heightAnchor)
]
NSLayoutConstraint.activate(viewConstraints)
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.widthAnchor.constraint(equalTo: imageView.widthAnchor).isActive = true
containerViewHeightConstraint = containerView.heightAnchor.constraint(equalTo: heightAnchor)
containerViewHeightConstraint?.isActive = true
imageView.translatesAutoresizingMaskIntoConstraints = false
imageViewBottomConstraint = imageView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
imageViewBottomConstraint?.isActive = true
imageViewHeightConstraint = imageView.heightAnchor.constraint(equalTo: containerView.heightAnchor)
imageViewHeightConstraint?.isActive = true
}
func scrollViewDidScroll(scrollView: UIScrollView) {
containerViewHeightConstraint?.constant = scrollView.contentInset.top
let offsetY = -(scrollView.contentOffset.y + scrollView.contentInset.top)
containerView.clipsToBounds = offsetY <= 0
imageViewBottomConstraint?.constant = offsetY >= 0 ? 0 : -offsetY / 2
imageViewHeightConstraint?.constant = max(offsetY + scrollView.contentInset.top, scrollView.contentInset.top)
}
}
offsetY
를 구한 뒤, 해당 값의 양/음 조건에 따라 현재 컨테이너 뷰가 작아져야 할지, 커져야 할지 결정import UIKit
class StretchyHeaderTableViewController: UIViewController {
private lazy var tableView: UITableView = {
let tableView = UITableView()
tableView.delegate = self
tableView.dataSource = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "tableViewCell")
return tableView
}()
private let models = [
"pikachu",
"charizard",
"bulbasaur",
"squirtle"
]
override func viewDidLoad() {
super.viewDidLoad()
setUI()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
tableView.frame = view.bounds
tableView.tableHeaderView?.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: view.frame.size.width)
print("CurrentWidth: \(view.frame.size.width)")
}
private func setUI() {
view.backgroundColor = .systemBackground
view.addSubview(tableView)
let appearance = UINavigationBarAppearance()
appearance.largeTitleTextAttributes = [.foregroundColor : UIColor.white]
navigationItem.standardAppearance = appearance
tableView.tableHeaderView = StretchyTableViewHeaderView()
}
}
extension StretchyHeaderTableViewController: UITableViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let header = tableView.tableHeaderView as? StretchyTableViewHeaderView else { return }
header.scrollViewDidScroll(scrollView: tableView)
}
}
extension StretchyHeaderTableViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return models.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath)
cell.textLabel?.text = models[indexPath.row].uppercased()
return cell
}
}