반응형

iOS 개발자가 많이 하는 실수 - UIScrollView + Auto Layout에서 contentSize / 제약을 잘못 설정해 스크롤이 안 되는 실수

 

UIScrollView와 Auto Layout은 조합이 까다로운 편이라
초보자뿐 아니라 경력자도 자주 다음과 같은 문제를 겪습니다.

  • 스크롤이 전혀 안 됨
  • 스크롤이 되긴 하는데 컨텐츠가 잘림
  • 하단 여백이 이상하게 남음
  • 특정 기기/회전에서만 스크롤 범위가 꼬임

 

1. 문제 패턴: Auto Layout + 수동 contentSize 혼용

대표적인 안티 패턴:

scrollView.addSubview(contentView)

// Auto Layout 제약 설정
NSLayoutConstraint.activate([
    contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
    contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
    contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
    contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
])

// ❌ 수동으로 contentSize를 설정
scrollView.contentSize = CGSize(width: view.bounds.width, height: 2000)

문제:

  • Auto Layout이 이미 스크롤 콘텐츠의 크기를 계산하려고 하는데
  • 개발자가 강제로 contentSize를 덮어씀
  • 기기/회전/컨텐츠 크기가 변할 때 계속 불일치 발생

Auto Layout을 쓸 거면 contentSize를 직접 만지지 않는 것이 원칙입니다.


2. ScrollView + Auto Layout의 기본 개념

iOS 11 이후, UIScrollView에는 두 가지 중요한 레이아웃 가이드가 생겼습니다.

  • contentLayoutGuide
    • 스크롤 가능한 콘텐츠 영역의 크기/위치
    • 내부 콘텐츠(view들)에 이 가이드를 기준으로 제약을 건다.
  • frameLayoutGuide
    • ScrollView 자체의 프레임(보이는 영역)
    • 상위 뷰와의 제약에 사용한다.

이 두 가지를 올바르게 사용하면

contentSize를 코드로 건드릴 필요 없이

Auto Layout만으로 자연스럽게 스크롤 영역이 계산됩니다.


3. 올바른 ScrollView + Auto Layout 구성 패턴


3-1. 기본 구조

일반적인 패턴은:

  • ScrollView 안에 contentView 하나를 추가
  • contentView 안에 나머지 UI 요소들을 배치

코드 예시:

let scrollView = UIScrollView()
let contentView = UIView()

view.addSubview(scrollView)
scrollView.addSubview(contentView)

scrollView.translatesAutoresizingMaskIntoConstraints = false
contentView.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([
    // 1) ScrollView를 상위 뷰에 고정 (frameLayoutGuide 사용)
    scrollView.topAnchor.constraint(equalTo: view.topAnchor),
    scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
    scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
    scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),

    // 2) contentView를 contentLayoutGuide에 고정
    contentView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
    contentView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
    contentView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
    contentView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),

    // 3) contentView의 width를 scrollView의 frameLayoutGuide에 맞춤
    contentView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor)
])

포인트:

  • contentView가 가로 방향으로는 scrollView와 같은 폭을 가지게 하고
  • 세로 방향으로는 내부 콘텐츠의 높이에 따라 늘어나도록 구성
  • Auto Layout이 contentLayoutGuide를 통해 contentSize를 자동 계산

3-2. 내부 콘텐츠 배치

이제 contentView 안에 StackView 등을 활용해 콘텐츠를 배치:

let stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 16

contentView.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([
    stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16),
    stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
    stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
    stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -16)
])

이렇게 하면:

  • stackView의 내용에 따라 contentView의 height가 결정
  • 그 높이가 다시 contentLayoutGuide → contentSize로 반영
  • 결과적으로 Auto Layout에 의해 스크롤 영역이 자연스럽게 정해짐

4. 흔한 실수 패턴과 해결책


4-1. contentView의 width 제약 누락

다음 코드에서:

NSLayoutConstraint.activate([
    contentView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
    contentView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
    contentView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
    contentView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
    // ❌ width 제약이 없음
])

이 경우:

  • Auto Layout은 contentView의 가로 크기를 확정하지 못함
  • “ambiguous width” 혹은 제대로 된 스크롤 동작을 하지 않을 수 있음

해결:

contentView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor)

를 반드시 추가.


4-2. ScrollView 자체를 상위 뷰에 Auto Layout으로 안 묶음

view.addSubview(scrollView)
scrollView.frame = view.bounds   // ❌ frame 직접 설정 + auto layout 혼용

Auto Layout 환경에서는 frame을 직접 설정하기보다는

제약으로 위치/크기를 정하는 것이 권장됩니다.


4-3. contentSize를 임의로 덮어씀

Auto Layout을 쓰고 있음에도:

scrollView.contentSize = CGSize(width: 0, height: 2000)   // ❌

이렇게 임의의 값을 설정하면:

  • 기기 회전/Insets 변경/컨텐츠 변동 시
    Auto Layout이 계산한 크기와 충돌
  • 예상치 못한 스크롤 범위 문제 발생

가능하면 Auto Layout만으로 해결하고,

정말 특수 케이스에서만 contentSize를 수동으로 건드리길 권장합니다.


4-4. 수평/수직 ScrollView 혼동

가로 스크롤이 필요한 경우:

contentView.heightAnchor.constraint(equalTo: scrollView.frameLayoutGuide.heightAnchor)

처럼 축을 반대로 잡아야 합니다.

  • 세로 스크롤: contentView.width == scrollView.frame.width
  • 가로 스크롤: contentView.height == scrollView.frame.height

5. Storyboard / Interface Builder에서의 구성 팁

IB에서도 개념은 동일합니다.

  1. ScrollView 추가
  2. 그 안에 Content View 하나 추가
  3. ScrollView ↔ 상위 뷰: 4방향 constraint
  4. Content View ↔ ScrollView:
    • contentLayoutGuide에 top/leading/trailing/bottom
    • frameLayoutGuide에 width(또는 height) 맞추기

Xcode 11+에서는 IB에서도

Content Layout Guide, Frame Layout Guide를 명시적으로 선택해서

제약을 설정할 수 있습니다.


6. 실무에서 자주 겪는 증상 정리

  1. 스크롤이 전혀 안 됨
    • contentLayoutGuide에 bottom 제약 누락
    • 내부 콘텐츠의 bottom이 contentView.bottom까지 연결되지 않음
    • contentView 크기가 ScrollView와 같아서 추가 스크롤 영역이 없음
  2. 스크롤은 되는데 여백이 이상하게 큼
    • 잘못된 bottom constant
    • contentSize를 임의로 크게 설정함
  3. 일부 기기에서만 스크롤이 안 됨
    • Safe Area, notch, bottom inset 등을 고려하지 않은 제약
    • 기기별 레이아웃 차이로 content height가 달라짐

7. 실무용 체크리스트

ScrollView + Auto Layout 쓰기 전에 다음을 확인합니다.

  1. ScrollView는 상위 뷰에 Auto Layout 제약으로 고정했는가?
  2. ScrollView 안에는 Content View 하나로 감싸고 있는가?
  3. Content View는:
    • contentLayoutGuide에 상/하/좌/우 제약이 연결되어 있는가?
    • frameLayoutGuide에 width(또는 height)가 맞춰져 있는가?
  4. 내부 콘텐츠(레이블, 버튼, 스택뷰)는 Content View의 top~bottom에

    끊김 없이 연결되어 있는가?
  5. Auto Layout을 쓰고 있는데 contentSize를 수동으로 건드리고 있지 않은가?

8. 요약

  • ScrollView + Auto Layout 조합에서 스크롤이 안 되거나 깨지는 이유의 대부분은
    contentLayoutGuide, frameLayoutGuide, contentView 제약이 잘못되었거나,
    contentSize를 수동으로 건드려서
     생긴다.
  • 올바른 패턴:
    • ScrollView 안에 Content View 하나
    • Content View ↔ contentLayoutGuide (4방향 제약)
    • Content View ↔ frameLayoutGuide (width 또는 height 제약)
    • 내부 콘텐츠는 Content View 안에서 Auto Layout으로 자연스럽게 배치
  • 원칙: > Auto Layout을 사용한다면, ScrollView의 contentSize를 직접 설정하지 않는 것이 기본이다.

 

반응형
Posted by 까칠코더
,