iOS 개발일기

[Swift] UITableView Dynamic Height 본문

iOS/Swift

[Swift] UITableView Dynamic Height

맨날 까먹으니 적어두자 2024. 12. 26. 17:26

기존에는 동적 높이를 할당할 때에 모든 UI 요소들에 제약조건을 모두 상위 뷰에 맞춰 크기가 같이 커지도록 설정된 셀만 사용했었으나, 

 

이번 프로젝트에서 각각의 셀은 동적인 높이를 가지지만 UI 요소 중 타이틀과 버튼의 크기는 고정 크기를 가지고 유지되면서 중앙의 UILabel만 높이가 동적으로 변하도록 구성해야 됐다.

 

AutoLayout 기반으로 UI를 코드로 작성하면서 높이를 지정해주는 상황이 적다보니 생소한 부분이기도 했고

단순하게 셀의 높이를 `UITableView.automaticDimension` 으로 설정한 후에 셀 내에 타이틀과 버튼에 최소 크기를 명시해주기만 하면 속성에서 자동으로 최소 크기를 따라가지 않을까? 라는 생각에 타이틀과 버튼에 최소 크기를 지정한 결과...

 

 

셀 내에 Frame을 가지는 UI 요소의 높이가 `UITableView.automaticDimension`속성의 높이가 결정되는 대에 영향을 미치지 않는구나라는 것을 알게 되었다.

(`extimatedRowHeight`도 마찬가지로 테이블 뷰가 아직 높이를 계산하지 않은 셀의 높이를 추정하기 위해 사용되는 값일 뿐, 최소 값에 직접적인 영향을 미치진 않는다.)

 

이렇게 저렇게 해결 방법이 몇 가지 존재하였으나 서칭하던 중 

`systemLayoutSizeFitting(_:widthHorizontalFittingPriority:verticalFittingPriority:)` 라는 메서드의 존재에 대해 알게 되었는데 해당 메서드는 제약조건과 지정된 우선순위에 따라 최적의 View 크기를 반환하는 메서드라고 되어있다.

따라서, 제약조건이 설정되었을 때 즉 automaticDimension을 통해 셀의 크기가 결정되는 시점에 호출되는 메서드인 것이다.

 

이 메서드를 잘 이용하면 셀의 최소 크기도 지정할 수 있겠다 생각했다.

 

뷰 컨트롤러에서는 테이블 뷰 셀의 크기를 동적으로 할당될 수 있도록 지정해주고

func tableView(
    _ tableView: UITableView,
    cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(
        withIdentifier: "DynamicCellID",
        for: indexPath
    ) as! DynamicCell
    
    cell.configure(datas[indexPath.row])
    return cell
}

func tableView(
    _ tableView: UITableView,
    heightForRowAt indexPath: IndexPath
) -> CGFloat {
    return UITableView.automaticDimension
}

 

 

셀 내에서는 동적으로 크기가 할당되는 시점에 자동으로 설정하려는 높이와 비교를 하여 더 큰 값의 높이를 리턴해주는 것이다.

class DynamicCell: UITableViewCell {

    private let minHeight: CGFloat = UIScreen.main.bounds.height * 0.1
    
    ...
    
    override func systemLayoutSizeFitting(
        _ targetSize: CGSize,
        withHorizontalFittingPriority horizontalFittingPriority: UILayoutPrioriy,
        verticalFittingPriority: UILayoutPriority
    ) -> CGSize {
        let size = super.systemLayoutSizeFitting(
            targetSize,
            withHorizontalFittingPriority: horizontalFittingPriority,
            verticalFittingPriority: verticalFittingPriority
        )
        
        return CGSize(width: size.width, height: max(size.height, minHeight))
    }
    
    ...
}

 

이렇게 코드를 작성하게 되면 셀은 제약조건이 설정되고 크기를 지정하려고 할 때 인터셉터를 하게 되어 셀의 최소 높이를 원하는 크기로 변경할 수 있다.

 

이 방법 외에도 여러 방법이 존재하였지만 7할은 StoryBoard 내에서 속성을 변경하는 방법이 대부분이 었던 것 같다.

아니면 `tableView(_:heightForRowAt:)` 메서드에서 레이아웃을 강제로 업데이트하여 크기를 계산하고 높이를 지정하거나 이와 비슷한 방식들이 많았던 것 같다. 

 

외에도 UITableViewCell 내의 ContentView의 높이를 변경해주어 최소 크기를 변경하는 방법도 존재했었는데, 

이 방법은 SnapKit을 사용하는 상황에서는 높이만 제약조건을 업데이트하게 되니 다른 제약조건들이 유지되지않고 한 방향으로 UI 요소들이 쏠리는 현상이 있어 모두 다시 지정해주어야하는 상황이 발생하기 때문에 이 방법을 선택하게 되었다.

 

이 방법은 명시적이지 않다는 의견도 있지만 현재 나의 환경에서는 가장 합리적인 선택인 것 같았고 추후 더 좋은 방법을 찾게 된다면 이 부분은 리팩토링을 거칠 예정이다.