본문 바로가기
iOS 프로그래밍

SwiftUI 스택과 프레임: 뷰 배치와 크기 조절의 기본

by iOS 2025. 5. 1.

SwiftUI 스택(Stack)과 프레임(Frame) 이해하기: 여러 뷰를 화면에 효율적으로 배치하는 기본 방법을 배워보세요. VStack, HStack, ZStack과 .frame() 수정자를 활용하는 법을 상세히 설명합니다.

 

SwiftUI에서는 Text, Image, Button 등 개별적인 뷰들을 조합하여 사용자 인터페이스를 구성합니다. 하지만 이러한 여러 뷰를 화면에 원하는 위치에 배치하고 크기를 조절하려면 특별한 도구들이 필요합니다. SwiftUI는 레이아웃을 위해 다양한 컨테이너 뷰와 뷰 수정자(View Modifiers)를 제공하며, 그중 가장 기본적이고 핵심적인 것이 바로 스택(Stacks)프레임(Frame)입니다.

 

스택은 여러 뷰를 특정 방향으로 나란히 정렬하는 컨테이너 뷰이고, 프레임은 개별 뷰의 크기와 정렬을 제어하는 수정자입니다. 이 두 가지를 조합하여 복잡한 레이아웃을 효율적으로 구축할 수 있습니다. 이번 글에서는 SwiftUI의 스택(VStack, HStack, ZStack)과 .frame() 수정자의 사용법을 자세히 살펴보겠습니다.

SwiftUI 레이아웃 기본

SwiftUI의 레이아웃 시스템은 기본적으로 컨테이너 기반이며 선언형입니다. 각 뷰는 자신이 원하는 크기를 부모 뷰(컨테이너)에게 제안(propose)하고, 부모 뷰는 자식 뷰들의 제안을 바탕으로 자신에게 주어진 공간 내에서 자식 뷰들을 배치(arrange)하고 최종 크기를 결정(determine)합니다. 뷰 수정자는 이러한 제안이나 결정 과정에 영향을 미쳐 뷰의 최종 크기와 위치를 조절합니다.

스택 (Stacks): 뷰 쌓아 올리기

스택은 여러 개의 뷰를 그룹화하고 특정 축을 따라 정렬하는 데 사용되는 컨테이너 뷰입니다.

1. VStack (Vertical Stack - 세로 스택)

하위 뷰들을 수직 방향으로 차례대로 나열합니다.

VStack(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil) {
    // 하위 뷰들
}
  • alignment: 하위 뷰들을 수평 방향으로 어떻게 정렬할지 지정합니다 (.leading, .center, .trailing). 기본값은 .center입니다.
  • spacing: 하위 뷰들 사이의 간격을 지정합니다. nil이면 시스템 기본 간격이 사용됩니다.
VStack(alignment: .leading, spacing: 10) {
    Text("제목")
        .font(.largeTitle)
    Text("부제목")
        .font(.title2)
    Divider() // 구분선 뷰
    Text("내용 내용...")
}
.padding()

2. HStack (Horizontal Stack - 가로 스택)

하위 뷰들을 수평 방향으로 차례대로 나열합니다.

HStack(alignment: VerticalAlignment = .center, spacing: CGFloat? = nil) {
    // 하위 뷰들
}
  • alignment: 하위 뷰들을 수직 방향으로 어떻게 정렬할지 지정합니다 (.top, .center, .bottom, .firstTextBaseline, .lastTextBaseline). 기본값은 .center입니다.
  • spacing: 하위 뷰들 사이의 간격을 지정합니다.
HStack(alignment: .bottom, spacing: 20) {
    Image(systemName: "star.fill")
        .foregroundColor(.yellow)
        .imageScale(.large)
    Text("별점")
        .font(.headline)
    Text("5.0")
        .font(.title) // 베이스라인이 다르므로 alignment 중요
}
.padding()

3. ZStack (Z-axis Stack - 깊이 스택)

하위 뷰들을 화면에 겹쳐서 배치합니다. 나중에 오는 뷰일수록 화면의 앞쪽(사용자에게 더 가깝게)에 표시됩니다. 깊이 방향으로 뷰를 쌓아 올리는 효과를 줍니다.

ZStack(alignment: Alignment = .center) {
    // 하위 뷰들 (먼저 오는 뷰가 아래에, 나중에 오는 뷰가 위에 쌓입니다)
}
  • alignment: 하위 뷰들이 ZStack 내에서 어떻게 정렬될지 지정합니다 (.center, .topLeading, .bottomTrailing 등 다양한 조합 가능). 기본값은 .center입니다.
ZStack(alignment: .bottomTrailing) {
    // 배경 이미지 (먼저 와서 아래에 깔림)
    Image("background_image") // Assets에 추가된 이미지 이름
        .resizable() // 크기 조절 가능하게
        .scaledToFill() // 비율 유지하며 채우기
        .frame(width: 200, height: 150) // 크기 지정
        .clipped() // 프레임을 넘어가는 부분 잘라내기

    // 이미지 위에 표시될 텍스트 (나중에 와서 위에 겹쳐짐)
    Text("워터마크")
        .font(.caption)
        .foregroundColor(.white)
        .padding(5)
        .background(Color.black.opacity(0.5)) // 반투명 배경
        .cornerRadius(5)
        .offset(x: -5, y: -5) // 오른쪽 하단에서 살짝 안쪽으로 이동
}

스택 조합하기 (Nesting Stacks)

복잡한 레이아웃은 여러 종류의 스택을 서로 안에 포함시키는 방식으로 만듭니다. 예를 들어, 세로로 두 개 항목을 나열하는데, 각 항목은 가로로 이미지와 텍스트를 나열하는 형태는 VStack 안에 HStack 두 개를 넣어서 만듭니다.

VStack { // 전체를 세로로 나열
    HStack { // 첫 번째 항목 (가로로 나열)
        Image(systemName: "person.circle")
        VStack(alignment: .leading) { // 이름과 직업을 세로로 나열 (HStack 안의 VStack)
            Text("김철수").font(.headline)
            Text("iOS 개발자").font(.subheadline)
        }
    }
    HStack { // 두 번째 항목 (가로로 나열)
        Image(systemName: "envelope")
        Text("cheolsu.kim@example.com")
    }
}

프레임 (Frame): 뷰의 크기와 위치 제어

.frame() 수정자는 개별 뷰가 부모 컨테이너(스택 또는 다른 뷰)에게 제안할 자신의 크기와, 부모가 제공한 공간 내에서 자신이 어떻게 정렬될지를 지정하는 데 사용됩니다.

뷰.frame(width: CGFloat? = nil, height: CGFloat? = nil, alignment: Alignment = .center)
  • width, height: 뷰의 폭과 높이를 고정된 값(CGFloat)으로 지정합니다. nil이면 해당 축 방향으로는 뷰의 콘텐츠에 따라 크기를 제안하거나 부모가 결정합니다.
  • alignment: 부모가 제공한 공간 내에서 뷰가 어떻게 정렬될지 지정합니다. 기본값은 .center입니다.
Text("고정 크기 텍스트")
    .frame(width: 200, height: 50) // 폭 200, 높이 50으로 고정
    .border(Color.gray) // 프레임 확인을 위해 테두리 추가

Text("정렬만 지정")
    .frame(maxWidth: .infinity, alignment: .leading) // 부모가 제공한 최대 폭 사용, 좌측 정렬
    .border(Color.gray)

.frame()은 단순히 고정된 크기만 지정하는 것이 아니라, 최소/최대 크기를 지정하여 유연한 크기 조절도 가능하게 합니다.

뷰.frame(minWidth: CGFloat? = nil, idealWidth: CGFloat? = nil, maxWidth: CGFloat? = nil,
           minHeight: CGFloat? = nil, idealHeight: CGFloat? = nil, maxHeight: CGFloat? = nil,
           alignment: Alignment = .center)
  • minWidth, maxWidth, minHeight, maxHeight: 뷰가 가질 수 있는 최소/최대 크기를 지정합니다. 부모 컨테이너는 이 제약을 고려하여 공간을 할당합니다.
  • idealWidth, idealHeight: 뷰가 가장 이상적이라고 생각하는 크기를 제안합니다.
Rectangle() // 사각형 뷰
    .foregroundColor(.blue)
    .frame(minWidth: 50, maxWidth: 200, minHeight: 50, maxHeight: 100) // 폭 50~200, 높이 50~100

.frame() 수정자는 뷰의 크기를 직접 설정하는 것처럼 보이지만, 실제로는 부모 뷰에게 "나는 이 정도 크기를 원하거나, 이 범위 안에서 크기를 조절하고 싶고, 나에게 주어진 공간에서는 이렇게 정렬되고 싶어" 라고 제안하는 역할을 합니다. 최종 크기와 위치는 부모 컨테이너(스택 등)의 결정과 다른 형제 뷰들의 크기 제안에 따라 달라질 수 있습니다.

스택과 프레임 함께 사용하기

스택은 여러 뷰를 배치하는 컨테이너 역할을 하고, 프레임은 스택 안에 있는 개별 뷰의 크기와 정렬에 영향을 줍니다. 스택의 하위 뷰에 .frame()을 적용하면, 해당 뷰는 그 프레임이 정의하는 크기 제안을 가지고 스택에게 전달하고, 스택은 이 제안을 바탕으로 하위 뷰들을 배치합니다.

HStack {
    Text("왼쪽")
        .frame(width: 80) // 폭 80 고정
        .border(Color.gray)

    Text("중앙") // 크기 제안 없음 (콘텐츠 크기만큼)
        .border(Color.gray)

    Text("오른쪽")
        .frame(minWidth: 50, maxWidth: .infinity) // 최소 폭 50, 최대 사용 가능한 공간
        .border(Color.gray)
}
.frame(height: 50) // HStack 전체의 높이를 50으로 제한
.border(Color.red) // HStack 전체 프레임 확인

위 예제에서 HStack은 전체 높이를 50으로 제한받고, 내부의 세 Text 뷰는 각자 프레임 제안을 합니다. HStack은 이 제안과 자신에게 주어진 공간(높이 50)을 바탕으로 세 뷰를 가로로 배치하고 높이를 50에 맞춥니다.

마무리

SwiftUI에서 스택(VStack, HStack, ZStack)과 .frame() 수정자는 레이아웃을 구성하는 가장 기본적인 도구입니다. 스택은 여러 뷰를 특정 방향으로 그룹화하고 정렬하며, 프레임은 개별 뷰의 크기와 정렬을 제어합니다.

이 둘을 조합하고 중첩하여 사용함으로써 거의 모든 형태의 UI 레이아웃을 구현할 수 있습니다. 스택의 정렬과 간격 옵션, 프레임의 고정/유연 크기 지정 및 정렬 옵션을 다양하게 조합해보는 연습을 통해 SwiftUI 레이아웃 시스템에 익숙해지는 것이 중요합니다. 이제 우리는 여러 개의 뷰를 원하는 위치에 배치하는 방법을 알게 되었습니다!