iOS/UIKIT

오토레이아웃과 UIScrollView의 동작 방식 이해

밤새는 탐험가89 2024. 8. 15. 23:38

1. 왜 detailHeaderView.bottomAnchor.constraint(equalTo: basicScrollView.bottomAnchor, constant: -10)을 설정할까?

UIScrollView는 내부 콘텐츠의 크기를 contentSize로 정의합니다.

UIScrollView의 contentSize는 내부에 있는 모든 서브뷰의 위치와 크기를 기반으로 자동으로 결정됩니다.

만약 UIScrollView 내부의 마지막 서브뷰의 bottomAnchor가 scrollView의 bottomAnchor와 연결되지 않으면, UIScrollView는 contentSize.height를 자동으로 계산할 수 없습니다.

detailHeaderView.bottomAnchor.constraint(equalTo: basicScrollView.bottomAnchor, constant: -10) 제약 조건을 추가하면 UIScrollView는 contentSize.height를 정확히 계산할 수 있게 됩니다.

이 제약 조건은 UIScrollView에게 detailHeaderView가 스크롤뷰의 가장 아래 부분까지 닿아야 한다는 정보를 제공합니다. 그래서 스크롤뷰가 그 높이를 고려하여 스크롤 가능한 영역을 생성합니다.

 

2. 이미 detailHeaderView의 높이를 설정했는데, 왜 필요할까?

detailHeaderView의 높이는 고정되어 있지만, UIScrollView가 contentSize를 올바르게 계산하려면 내부 모든 뷰의 최하단 위치를 알아야 합니다. 예를 들어, detailHeaderView의 높이가 350으로 설정되어 있다 해도, 스크롤뷰는 contentSize.height를 설정할 때 이를 자동으로 고려하지 않습니다. 대신, 내부에서 가장 아래에 위치한 뷰의 bottomAnchor를 참조하여 그 높이를 계산합니다.

이 제약 조건이 없다면 UIScrollView는 contentSize.height를 올바르게 설정하지 못하고, 따라서 스크롤이 제대로 작동하지 않거나 예상과 다른 결과를 얻게 될 수 있습니다.

 

 

3. detailHeaderView의 높이가 350이 유지되는 이유

detailHeaderView.bottomAnchor와 basicScrollView.bottomAnchor 간의 제약 조건은 스크롤뷰가 detailHeaderView의 위치와 크기를 기반으로 contentSize를 계산하게 만듭니다. detailHeaderView의 높이가 350으로 고정되어 있기 때문에, scrollView는 detailHeaderView의 바닥이 스크롤뷰의 가장 아래로 배치되어 있다고 계산하여 contentSize를 결정하게 됩니다.

이 제약 조건은 스크롤뷰가 내부의 뷰들이 어떻게 배치되어 있는지 명확하게 파악하고, 그에 따라 적절한 스크롤 영역을 만들 수 있도록 돕습니다. 결과적으로 detailHeaderView의 높이는 여전히 350으로 유지되며, 스크롤뷰의 스크롤 가능 영역이 정확하게 설정됩니다.

 

 

4. view.layoutIfNeeded()의 역할

view.layoutIfNeeded()는 현재 뷰 계층에 있는 뷰들의 레이아웃이 즉시 업데이트되도록 강제로 요청하는 메서드입니다.

오토레이아웃 제약 조건에 따라 뷰의 프레임이 계산된 후, 실제 뷰의 위치와 크기를 반영하기 위해 사용됩니다.

이 메서드를 호출하는 이유는 basicScrollView.contentSize를 설정하기 전에 detailHeaderView의 프레임이 정확히 계산되도록 하기 위함입니다. 그렇지 않으면, detailHeaderView.frame 값을 정확하게 얻을 수 없게 되어, contentSize를 잘못 계산하게 될 수 있습니다.

 

 

 

5. basicScrollView.contentSize 설정

basicScrollView.contentSize는 스크롤 뷰의 스크롤 가능한 영역을 결정하는 속성입니다.

스크롤 뷰가 내부에 얼마나 많은 콘텐츠를 표시할지 알려주는 것이죠. 이 코드에서 contentSize는 스크롤뷰가 detailHeaderView의 전체를 포함하도록 설정됩니다.

basicScrollView.contentSize = CGSize(width: view.frame.size.width, 
                              height: detailHeaderView.frame.maxY + 20)

 

⭐️ 굳이? 할 필요없다.

🟥 detailHeaderView.frame.maxY + 20의 의미

 

  • detailHeaderView.frame.maxY: 이 값은 detailHeaderView의 프레임에서 가장 하단의 Y 좌표를 의미합니다. 즉, detailHeaderView의 하단 끝이 스크롤 뷰의 최상단에서부터 얼마나 떨어져 있는지를 나타냅니다.
  • + 20: 이 부분은 detailHeaderView 하단에 추가적인 여백을 주기 위해 설정된 것입니다. 이 여백은 시각적인 공간을 제공하거나, 스크롤이 끝났을 때 사용자에게 자연스러운 끝점을 제공하는 데 도움이 됩니다.

 

⭐️ 결론적으로, maxY + 20은 사용자의 경험을 더 개선하기 위해 시각적 여백을 추가한 것입니다. 이 값을 통해 스크롤뷰의 내용이 끝난 후에도 약간의 추가 공간을 제공해 스크롤 끝부분이 더 부드럽게 보이게 됩니다. ⭐️

 

import UIKit

class DetailViewController: UIViewController {
    
    private let basicScrollView: UIScrollView = {
        let scrollView = UIScrollView()
        scrollView.showsVerticalScrollIndicator = false
        scrollView.backgroundColor = .systemPink
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        return scrollView
    }()
    
    private let detailHeaderView: DetailHeaderView = {
        let view = DetailHeaderView()
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(basicScrollView)
        basicScrollView.addSubview(detailHeaderView)
        
        configureConstraints()
    }
    
    private func configureConstraints() {
        
        let basicScrollViewConstraints = [
            basicScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            basicScrollView.topAnchor.constraint(equalTo: view.topAnchor),
            basicScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            basicScrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ]
        
        let detailHeaderViewConstraints = [
            detailHeaderView.leadingAnchor.constraint(equalTo: basicScrollView.leadingAnchor, constant: 10),
            detailHeaderView.topAnchor.constraint(equalTo: basicScrollView.topAnchor, constant: 10),
            detailHeaderView.trailingAnchor.constraint(equalTo: basicScrollView.trailingAnchor, constant: -10),
            detailHeaderView.widthAnchor.constraint(equalTo: basicScrollView.widthAnchor, constant: -20),
            detailHeaderView.heightAnchor.constraint(equalToConstant: 350),
            detailHeaderView.bottomAnchor.constraint(equalTo: basicScrollView.bottomAnchor, constant: -10) // 중요!
        ]
        
        NSLayoutConstraint.activate(basicScrollViewConstraints)
        NSLayoutConstraint.activate(detailHeaderViewConstraints)
        
        // contentSize 설정
        view.layoutIfNeeded() // 이 메서드는 레이아웃이 확정된 후 contentSize를 설정하기 위해 호출됩니다.
        basicScrollView.contentSize = CGSize(width: view.frame.size.width, height: detailHeaderView.frame.maxY + 20)
    }
}

 

 

'