반응형

SwiftUI Study – GeometryReader에 의존하지 않고 레이아웃을 안정적으로 구성하는 방법

 

1. 왜 중요한가 (문제 배경)

SwiftUI 초기에 GeometryReader는 “레이아웃을 자유롭게 만들 수 있는 도구”로 널리 소개되었지만,

실제 프로젝트에서 과도하게 사용하면 다음과 같은 문제가 발생합니다.

  • 부모 뷰의 크기가 예상치 못하게 0 또는 무한대(∞)로 측정됨
  • GeometryReader 내부 뷰가 예상과 다른 위치로 이동
  • 스크롤/안전 영역(safe area)과 결합할 때 레이아웃이 깨짐
  • 디바이스 회전, iPad 분할 화면에서 뷰 위치가 불안정해짐

문제의 핵심은 다음에 있습니다.

GeometryReader는 “자식이 부모의 크기를 결정하는 패턴”을 만들기 쉽다.

SwiftUI는 “부모가 자식의 크기를 결정하는 패턴”이 기본이다.

따라서 GeometryReader는 꼭 필요한 상황에서만 사용하고,

대부분의 경우는 다른 레이아웃 도구로 충분히 대체할 수 있습니다.

 

2. 잘못된 패턴 예시

❌ 예시 1: 단순 정렬/레이아웃을 위해 GeometryReader 남용

struct WrongView: View {
    var body: some View {
        GeometryReader { geo in
            Text("Hello")
                .position(x: geo.size.width / 2,
                          y: geo.size.height / 2)   // ❌ 중앙 정렬을 GeometryReader로 강제
        }
    }
}

문제점

  • 이 방식은 부모가 자식의 크기를 역으로 참조하는 구조
  • NavigationBar / SafeArea 영향을 받으면 중앙이 아닌 위치로 이동
  • rotation / iPad split에서 레이아웃이 쉽게 깨짐

❌ 예시 2: 배경 크기 계산하려고 GeometryReader 사용

struct WrongBackgroundView: View {
    var body: some View {
        GeometryReader { geo in
            Color.blue
                .frame(width: geo.size.width, height: geo.size.height) // ❌ 불필요한 패턴
        }
    }
}

문제점

  • Color는 레이아웃을 꽉 채우는 기본 기능이 있음
  • GeometryReader만큼 비용이 큰 view tree 확장이 불필요하게 발생

❌ 예시 3: ScrollView 안에서 GeometryReader 위치 계산

ScrollView {
    GeometryReader { geo in
        Text("상단 고정 텍스트")
            .offset(y: geo.frame(in: .global).minY) // ❌ 스크롤 위치 기반 계산
    }
}

문제점

  • ScrollView는 지연 렌더링(lazy)이 아니라 내부 계산이 반복되어 성능 저하
  • 뷰가 스크롤되는 순간 minY 값이 계속 바뀌며 레이아웃 흔들림 발생
  • 흔히 “스크롤하면 텍스트가 날아다님” 같은 문제가 생김

 

3. 올바른 패턴 예시

✅ 예시 1: 중앙 정렬은 alignment와 frame으로 해결

struct CorrectCenterView: View {
    var body: some View {
        Text("Hello")
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color.yellow)
            .multilineTextAlignment(.center)
    }
}

장점

  • GeometryReader 없이도 안정적으로 중앙 정렬 가능
  • 디바이스 회전, safe area, split-screen 상황에서도 깨지지 않음

✅ 예시 2: 배경은 safeAreaInset 또는 ZStack으로 해결

struct CorrectBackgroundView: View {
    var body: some View {
        ZStack {
            Color.blue.ignoresSafeArea()
            Text("Hello")
        }
    }
}

장점

  • GeometryReader 없이 원하는 배경 연출 가능
  • 선언적 SwiftUI 방식에 맞는 구조

✅ 예시 3: ScrollView 레이아웃에는 PreferenceKey 사용

struct OffsetPreferenceKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}

struct CorrectScrollHeaderView: View {
    @State private var offset: CGFloat = 0

    var body: some View {
        ScrollView {
            Text("상단 고정 영역")
                .offset(y: -offset)
                .background(
                    GeometryReader { geo in
                        Color.clear
                            .preference(key: OffsetPreferenceKey.self,
                                        value: geo.frame(in: .global).minY)
                    }
                )

            VStack(spacing: 20) {
                ForEach(0..<30) { _ in
                    RoundedRectangle(cornerRadius: 10)
                        .frame(height: 80)
                        .foregroundColor(.gray.opacity(0.2))
                }
            }
        }
        .onPreferenceChange(OffsetPreferenceKey.self) { value in
            offset = value
        }
    }
}

장점

  • ScrollView 내부 레이아웃 계산이 안정됨
  • GeometryReader를 직접 뷰 배치에 사용하지 않고, “측정만 수행”
  • 흔들림/깜빡임 없이 부드러운 헤더 동작 구현 가능

 

4. 실전 적용 팁

✔ 팁 1 – GeometryReader는 “정보를 읽을 때만” 사용

뷰의 위치나 크기를 계산할 때만 사용하고,

레이아웃 배치(bounding / align / offset / position) 용도로 사용하지 않는 것이 안전함.

✔ 팁 2 – 레이아웃은 가능한 한 frame / alignment / ZStack으로 해결

GeometryReader를 쓰기 전에 다음을 먼저 생각해볼 것:

  • alignment
  • maxWidth / maxHeight
  • safeAreaInset
  • overlay / background
  • ZStack + spacer

대부분 GeometryReader 없이 해결 가능함.

✔ 팁 3 – ScrollView 안의 GeometryReader는 성능에 가장 치명적임

지연 렌더링이 없는 GeometryReader는 스크롤할 때마다 크기 계산을 반복하여 성능 저하 발생.

✔ 팁 4 – Animation + GeometryReader 조합은 특히 취약

레이아웃 계산이 틀어지면 애니메이션이 튀거나 사라짐.

 

5. 정리

  • GeometryReader 남용은 SwiftUI 레이아웃이 흔들리는 가장 큰 원인 중 하나이다.
  • 레이아웃은 frame, alignment, safeAreaInset, overlay가 기본이고 GeometryReader는 보조 도구이다.
  • GeometryReader는 “측정 전용”으로만 사용하면 레이아웃 문제를 크게 줄일 수 있다.
  • ScrollView 내부의 GeometryReader는 매우 신중하게 사용해야 한다.
반응형
Posted by 까칠코더
,