애플사이다의 iOS 개발 일지

[CollectionView] estimatedHeight 사용 시 is stuck in its update/layout loop 에러 발생 본문

iOS

[CollectionView] estimatedHeight 사용 시 is stuck in its update/layout loop 에러 발생

Applecider 2022. 6. 13. 13:02

CollectionView의 CompositionalLayout에서 estimatedHeight를 사용해봤다.

Item / Group의 크기를 고정하지 않고,

Cell 내부 컨텐츠의 높이를 알아서 계산하여 반영해주므로 매우 유용하다.

*예제코드는 [CollectionView] Diffable DataSource 이해하기 (3/3) - 상품 배너/목록/상세 화면을 구현한 예제코드 포스트를 참고

 

그런데 약 iOS 15.0~15.3에서crash가 발생했다.

에러 문구는 아래와 같다.

is stuck in its update/layout loop. This can happen for many reasons, including self-sizing views whose preferred attributes are not returning a consistent size. To debug this issue, check the Console app for logs in the "UICollectionViewRecursion" category.'

 

확인해보니 특정 버전에서 estimatedHeight와 관련한 버그가 발생한 게 원인이었다.

https://stackoverflow.com/questions/70213395/self-sizing-collection-view-enters-a-recursive-loop-in-ios-15

검색 키워드 : is stuck in its update/layout loop


버그 해결 방법

iOS 15 미만, iOS 15.4 이상에서는 문제가 없음을 확인했다.

아래 코드를 추가하면 해결된다는 코멘트도 있었다.

override func shouldInvalidateLayout(
    forPreferredLayoutAttributes preferredAttributes: UICollectionViewLayoutAttributes,
    withOriginalAttributes originalAttributes: UICollectionViewLayoutAttributes
) -> Bool {
    return true
}

버그 대응 예제코드

이 버그에 어떻게 대응하는 게 좋을까?

사용자의 기기 버전이 iOS 15.0~15.3인 경우, 에러가 발생하므로 아래처럼 Alert를 띄우도록 구현했다.

// ViewController에 정의한 메서드
private func checkIOSVersion() {
    // ✅ 사용자 기기의 현재 iOS 버전 정보를 받아옴
    let versionNumbers = UIDevice.current.systemVersion.components(separatedBy: ".")
    let major = versionNumbers[0]
    let minor = versionNumbers[1]
    let version = major + "." + minor

    guard let systemVersion = Double(version) else { return }
    let errorVersion = 15.0..<15.4
    // ✅ 해당 버전만 버그가 발생하므로 Alert로 업데이트 권고
    if  errorVersion ~= systemVersion {
        showErrorVersionAlert()
    }
}
  • checkIOSVersion 메서드를 추가하여 사용자 기기의 현재 iOS 버전 정보를 받아오고,
    버그가 발생하는 특정 버전이면 Alert를 띄워서 업데이트 권고하도록 구현했다.
  • Alert를 띄우는 메서드를 분리했다.

 

기타 코드는 아래를 참고

private func showErrorVersionAlert() {
    let okAlertAction = UIAlertAction(title: Content.okAlertActionTitle, style: .default)
    let alert = AlertFactory().createAlert(title: Content.versionErrorTitle,
                                           message: Content.versionErrorMessage,
                                           actions: okAlertAction)
    present(alert, animated: true)
}

private enum Content {
    static let versionErrorTitle = "기기를 iOS 15.4 이상으로 업데이트 해주세요"
    static let versionErrorMessage = "애플이 잘못했어요"
    static let okAlertActionTitle = "OK"
}

// 참고 - AlertFactory
struct AlertFactory {
    func createAlert(style: UIAlertController.Style = .alert,
                     title: String? = nil,
                     message: String? = nil,
                     actions: UIAlertAction...) -> UIAlertController {
        let alert = UIAlertController(title: title, message: message, preferredStyle: style)
        actions.forEach { action in
            alert.addAction(action)
        }
        
        return alert
    }
}
  • AlertFactory를 추가해서 코드 재사용성을 개선했다.
  • 중첩 타입 (Nested Type)으로 Content 열거형을 추가하여 문자열 상수를 분리했다.

 

- Reference

 

🍎 포스트가 도움이 되었다면, 공감🤍 / 구독🍹 / 공유🔗 / 댓글✏️ 로 응원해주세요. 감사합니다.

 

Comments