🍣
スクロールに合わせて画像の高さをコードで動的に変える
実装要件
スクロールに合わせて画面最上部に設定されている画像の高さが更新される
SectionHeaderとtableHeaderViewの間の空白がない状態を維持したままスクロールできる
上にスクロールするとSectionHeaderは画面最上部で止まる
全体完成イメージ
スクロールに合わせて画面最上部に設定されている画像の高さが更新される
基本的な考え方は以下
- UITableViewを使ってtableHeaderViewにUIImageViewを持つCustomViewを設定
- スクロール時にoffsetの値に応じてCustomViewの持つUIImageViewのボトムの制約と高さの制約を都度更新する
ソースコード
ViewController.swift
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
private lazy var tableView: UITableView = {
let view = UITableView()
view.delegate = self
view.dataSource = self
view.contentInsetAdjustmentBehavior = .never
view.register(SectionHeaderView.self, forHeaderFooterViewReuseIdentifier: "SectionHeader")
view.register(ContentCell.self, forCellReuseIdentifier: "Cell")
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
makeConstraints()
}
private func makeConstraints() {
view.addAutoLayoutedSubview(tableView)
NSLayoutConstraint.activate(tableView.fillConstraintsWithTopSafeArea())
}
private func setupTableView() {
let headerView = StretchyTableHeaderView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: StretchyTableHeaderView.imageHeihgt))
self.tableView.tableHeaderView = headerView
let view = UIView()
view.backgroundColor = .orange
tableView.tableHeaderView?.addAutoLayoutedSubview(view)
NSLayoutConstraint.activate([
view.centerXAnchor.constraint(equalTo: tableView.tableHeaderView!.centerXAnchor),
view.widthAnchor.constraint(equalTo: tableView.tableHeaderView!.widthAnchor),
view.heightAnchor.constraint(equalToConstant: 30),
view.topAnchor.constraint(equalTo: tableView.tableHeaderView!.bottomAnchor)
])
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? ContentCell else {
fatalError("Failed To get a CustomCell")
return UITableViewCell() }
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
1
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let sectionHeader = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as? SectionHeaderView
return sectionHeader
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let headerView = self.tableView.tableHeaderView as! StretchyTableHeaderView
headerView.scrollViewDidScroll(scrollView: scrollView)
}
}
StretchyTableHeaderView.swift
class StretchyTableHeaderView: UIView {
static let imageHeihgt = 328.3333333333333
private var imageViewHeight = NSLayoutConstraint()
private var imageViewBottom = NSLayoutConstraint()
private lazy var imageView: UIImageView = {
let view = UIImageView(image: UIImage(named: "headerImage"))
view.backgroundColor = .yellow
view.contentMode = .scaleAspectFill
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
setConstraints()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setConstraints() {
self.addAutoLayoutedSubview(imageView)
imageViewBottom = imageView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
imageViewHeight = imageView.heightAnchor.constraint(equalTo: self.heightAnchor)
NSLayoutConstraint.activate([
imageView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
imageViewBottom,
imageViewHeight,
])
}
func scrollViewDidScroll(scrollView: UIScrollView) {
let offsetY = -scrollView.contentOffset.y
imageView.clipsToBounds = offsetY <= 0
imageViewBottom.constant = offsetY >= 0 ? 0 : -offsetY/4.7
imageViewHeight.constant = max(offsetY, 0)
}
}
詳細はgithub: https://github.com/yuk1ch1/StretchyHeaderImageViewSample
補足
swift
+ imageViewBottom.constant = offsetY >= 0 ? 0 : -offsetY/4.7
- imageViewBottom.constant = offsetY >= 0 ? 0 : -offsetY/2
ここはもともとは/2でボトムの制約を更新していたのですが、このあとに追記する機能のために調整した結果4.7になりました
追加対応: SectionHeaderとtableHeaderViewの間の空白がない状態を維持したままスクロールさせる&SectionHeaderは画面上部で止まる
追加要件は以下
- tableHeaderViewとSectionHeaderの間にはスペースがあるのでこれを埋めたい
- スクロールするとtableHeaderViewとSectionHeaderの間のスペースがない状態を維持して動く
- 画面上部でSectionHeaderは固定されそれ以上上へSectionHeaderを持っていくことができない
解決方法
自分は上記のコードの通りスペースを埋める用のUIViewを作りSectionHeaderにaddSubviewで追加して対応しました
失敗
TableViewのstyleをgroupedにすればスペースは消えます
ただ上にスクロールしていくとSectionHeaderが画面上部で止まらず消えてしまう
参考
Discussion