Notice
Recent Posts
Recent Comments
Link
관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] 1. Sticky Header 구현 - 스크롤 시 상단 해더 숨기는 방법 본문

UI 컴포넌트 (swift)

[iOS - swift] 1. Sticky Header 구현 - 스크롤 시 상단 해더 숨기는 방법

jake-kim 2023. 1. 7. 22:19

1. Sticky Header 구현 - 스크롤 시 상단 해더 숨기는 방법

2. Sticky Header 구현 - 스크롤 시 상단에 뷰 붙이는 방법

구현한 Sticky Header

Sticky Header

  • 개념: 스크롤 뷰 상단에 마치 붙어있다가 아래로 스크롤하면 뷰가 떼어진다고 하여 Sticky Header라고 명칭

구현 아이디어

  • UIScrollView가 가장 하위에 있고, 그 위에 StickyHeaderView가 존재
  • UIScrollView의 델리게이트 메소드 중 scrollViewDidScroll(_ scrollView: UIScrollView)를 사용하여 스크롤 offset에 따라 StickyHeader뷰의 alpha값만 조정하면 구현 완료

Sticky Header 구현

* 코드로 오토레이아웃 정의에 편리한 SnapKit 사용 

  • 레이아웃 정의에 필요한 상수 정의
    • statusBarHeight 같은 경우 +6을 따로 해주어야 높이가 맞으므로 주의
    • stickyHeaderHeightMin, max같은 경우는 StickyHeader를 두 개 둘것인데, Max는 처음에 보이는 Sticy Header뷰의 높이를 의마하고 min은 두 번째 보이는 Sticy header 뷰를 의미
    • stickyHeaderHeightMaxWithoutStatusBar는 scrollview의 오토레이아웃에 사용
import UIKit
import SnapKit

class ViewController: UIViewController {
    // MARK: Constants
    private enum Metric {
        // 1. statusBarHeight에 6을 더해야 진정한 statusBarheight가 구해지므로 주의
        static let statusBarHeight = UIApplication.shared.statusBarFrame.height + 6
        static let stickyHeaderHeightMin = 80.0
        static let stickyHeaderHeightMax = 180.0
        static var stickyHeaderHeightMaxWithoutStatusBar: Double {
            stickyHeaderHeightMax - statusBarHeight
        }
    }
}
  • 필요한 뷰 선언
// MARK: UIs
private let scrollView: UIScrollView = {
    let view = UIScrollView()
    view.backgroundColor = .lightGray
    print(Metric.statusBarHeight)
    // 2. contentInset의 top은 위 헤더뷰만큼 떨어져 있어야 스크롤 맨 위가 헤더뷰에 안가려짐
    // contentInset은 superview기준이 아니고 safearea기준이므로 statusBar의 높이값 제외해야함
    view.contentInset = .init(top: Metric.stickyHeaderHeightMaxWithoutStatusBar, left: 0, bottom: 0, right: 0)
    return view
}()
private let beforeHeaderView: UIView = {
    let view = UIView()
    view.backgroundColor = .systemBlue
    return view
}()
private let afterHeaderView: UIView = {
    let view = UIView()
    view.backgroundColor = .blue
    view.alpha = 0
    return view
}()
private let stackView: UIStackView = {
    let view = UIStackView()
    view.axis = .vertical
    return view
}()
private let label: UILabel = {
    let label = UILabel()
    label.text = "1long text\n\n\n2long text\n\n\n\n\n3long textlong text\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n4textlongtextlong"
    label.textColor = .white
    label.numberOfLines = 0
    label.font = .systemFont(ofSize: 36)
    return label
}()
  • viewDidLoad에서 레이아웃 선언
override func viewDidLoad() {
    super.viewDidLoad()
    scrollView.delegate = self
    
    [scrollView, beforeHeaderView, afterHeaderView]
        .forEach(view.addSubview(_:))
    scrollView.addSubview(stackView)
    stackView.addArrangedSubview(label)
    
    beforeHeaderView.snp.makeConstraints {
        $0.top.left.right.equalToSuperview()
        $0.height.equalTo(Metric.stickyHeaderHeightMax)
    }
    afterHeaderView.snp.makeConstraints {
        $0.top.left.right.equalToSuperview()
        $0.height.equalTo(Metric.stickyHeaderHeightMin)
    }
    scrollView.snp.makeConstraints {
        // 3. scorllView는 superview로 top 고정시켜놓으면 처음 offset이 safearea의 top으로 설정해야 offset 조정에 오차가 없음
        $0.top.equalTo(view.safeAreaLayoutGuide)
        $0.left.right.bottom.equalToSuperview()
    }
    stackView.snp.makeConstraints {
        $0.edges.width.equalToSuperview()
    }
}
  • 델리게이트 구현
    • 위에서 scrollView의 contrentInset.top값을 설정했으므로, scrollView.contentOffset의 초기값도 contentInset.top과 동일하며, contentOffset.y는 뷰의 (0,0) 좌표에서 멀어질수록 - 값을 띄우므로 (0,0)까지 남은 topSapcing은 -값을 부여하여 선언
    • 남은 top sacping의 비율값은 처음에 top spacing으로 정의했던 Metric.stickyHeaderHeightMaxWithoutStatusBar으로 나누어주어서 계산
    • alpha값을 이용하여 앞으로 숨길 뷰와 숨겨질 뷰를 표현
extension ViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let remainingTopSpacing = -scrollView.contentOffset.y
        let reaminingTopSpacingRatio = remainingTopSpacing / Metric.stickyHeaderHeightMaxWithoutStatusBar
        
        beforeHeaderView.alpha = reaminingTopSpacingRatio
        afterHeaderView.alpha = 1 - reaminingTopSpacingRatio
    }
}

* 전체 코드: https://github.com/JK0369/ExStickyHeader

Comments