ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [UIKit] - 스크롤에 따라 이미지뷰를 줄이고 사라지게 만드는 방법 (UIKit + SnapKit)
    iOS/UIkit 2025. 5. 17. 13:52
    728x90
    반응형

    많은 앱에서 상단 이미지(썸네일 또는 배너)가 스크롤에 따라 자연스럽게 축소되고 사라지는 UI를 볼 수 있다.

    대표적으로는 Apple Music, App Store의 상세화면 등이 있다.

     

    이번 글에서는 SnapKit + UIScrollViewDelegate를 활용해

    이미지뷰의 높이와 투명도를 스크롤 위치에 따라 동적으로 조절하는 인터랙션을 구현해봤다.

     

    🎯 구현 목표

    • 스크롤하면 이미지뷰가 자연스럽게 축소된다.
    • 충분히 스크롤하면 완전히 사라진다.
    • 스크롤이 하단에 도달하면 원래 높이로 돌아가지 않도록 제어한다.
    • SnapKit 사용 시 발생할 수 있는 제약 충돌을 방지한다.

     

    🧱 View 구성 구조

    DetailView
    ├── artworkImageView (썸네일)
    ├── scrollView
    │   └── contentStackView (스크롤 가능한 상세 콘텐츠)

     

    🧩 구현 포인트 요약

    포인트설명

    SnapKit .constraint 저장 안전하게 높이 업데이트
    layoutSubviews()에서 초기 높이 저장 AutoLayout 반영 이후 시점 보장
    offset / 200 → progress 비율 계산 부드러운 height 조절
    alpha 조절 자연스러운 페이드 효과
    스크롤 하단 도달 시 처리 차단 layout 복원 방지

     

     

    1️⃣ 제약을  .constraint로 저장해 업데이트할 수 있게 만들기

     

    SnapKit은 제약을 명시적으로 .constraint로 저장하지 않으면

    update() 호출 시 “could not find existing matching constraint” 에러를 발생시킬 수 있다.

    artworkImageView.snp.makeConstraints {
        $0.top.equalToSuperview()
        $0.width.equalTo(safeAreaLayoutGuide)
        artworkHeightConstraint = $0.height.equalTo(artworkImageView.snp.width).constraint
    }

    이렇게 하면 artworkHeightConstraint?.update(offset:)를 안전하게 호출할 수 있다.

     

     

     

    2️⃣ 이미지뷰의 초기 높이는 layoutSubviews()에서 저장

     

    뷰의 크기가 정확히 정해지는 시점은 layoutSubviews() 이후다.

    따라서 이 시점에서 원본 height를 저장한다.

    override func layoutSubviews() {
        super.layoutSubviews()
        self.originalArtworkHeight = artworkImageView.bounds.width
    }

    스크롤 비율 계산의 기준이 되는 값이므로 반드시 필요하다.

     

    3️⃣ 스크롤 위치에 따라 높이와 투명도 조절하기

     

    스크롤이 발생할 때마다 offsetY 값을 읽고, 이를 0~1 사이의 progress로 정규화한다.

    이 값을 이용해 높이와 alpha를 함께 조절한다.

    func updateArtworkImageHeight(by offsetY: CGFloat) {
        guard originalArtworkHeight > 0 else { return }
    
        let progress = min(max(offsetY / 200, 0), 1) // 0~1 범위 제한
        let adjustedHeight = originalArtworkHeight * -progress
    
        artworkHeightConstraint?.update(offset: adjustedHeight)
        artworkImageView.alpha = 1 - progress
    }
    • offset이 0일 땐 progress = 0, 원래 크기 유지
    • offset이 200 이상일 땐 progress = 1, 높이 0, alpha 0

     

     

    4️⃣ 스크롤 하단에 도달했을 땐 이미지뷰를 조절하지 않도록 막기

     

    스크롤이 완전히 아래로 내려가면, scrollView의 레이아웃 특성상

    스크롤이 조금만 바뀌어도 다시 높이가 복원되는 문제가 생길 수 있다.

     

    이를 막기 위해 아래 조건을 추가했다.

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let offset = scrollView.contentOffset.y
        let contentHeight = scrollView.contentSize.height
        let scrollViewHeight = scrollView.frame.size.height
    
        let isAtBottom = offset + scrollViewHeight >= contentHeight
        if !isAtBottom {
            detailView.updateArtworkImageHeight(by: offset)
        }
    }

     

     

     

    🧪 최종 코드 스니펫 요약

    // 제약 저장
    artworkHeightConstraint = $0.height.equalTo(artworkImageView.snp.width).constraint
    
    // 높이 + 투명도 동적 업데이트
    func updateArtworkImageHeight(by offsetY: CGFloat) {
        let progress = min(max(offsetY / 200, 0), 1)
        artworkHeightConstraint?.update(offset: originalArtworkHeight * -progress)
        artworkImageView.alpha = 1 - progress
    }
    
    // 스크롤 조건 처리
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let offset = scrollView.contentOffset.y
        let isAtBottom = offset + scrollView.frame.height >= scrollView.contentSize.height
        if !isAtBottom {
            updateArtworkImageHeight(by: offset)
        }
    }

     

     

     

    ✅ 마무리

     

    이 기능은 단순해 보이지만 실제로 구현할 땐 여러 함정이 존재한다.

    • SnapKit 제약 충돌
    • 뷰 사이클 타이밍
    • 스크롤 하단 처리
    • 부자연스러운 alpha 변경

    이러한 부분을 하나하나 정리하면서 구현했기 때문에,

    UI가 부드럽고 안정적으로 작동하는 결과를 만들 수 있었다.

     

    비슷한 인터랙션을 구현하고 싶다면 위 구조를 그대로 가져가도 좋다.

    필요에 따라 blur 효과, 고정 타이틀 등으로 확장해보는 것도 추천한다.

    'iOS > UIkit' 카테고리의 다른 글

    [UIKit] - Tabbar 커스텀하기  (1) 2025.03.12
    [UIKit] - AutoLayout  (0) 2024.01.17
    [UIKit] - XIB와 NIB  (0) 2024.01.17
    [UIkit] - 코드베이스 VS 스토리보드  (0) 2024.01.10
Designed by Tistory.