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 레이아웃 시스템에 익숙해지는 것이 중요합니다. 이제 우리는 여러 개의 뷰를 원하는 위치에 배치하는 방법을 알게 되었습니다!
'iOS 프로그래밍' 카테고리의 다른 글
SwiftUI 예제 튜토리얼: '좋아요' 버튼 만들기 (0) | 2025.05.02 |
---|---|
SwiftUI 상태 관리: @State, @ObservedObject, @StateObject, @EnvironmentObject (0) | 2025.05.01 |
SwiftUI로 커스텀 뷰 생성하기: 나만의 UI 컴포넌트 만들기 (0) | 2025.04.30 |
기본 SwiftUI 프로젝트 분석: 시작점 이해하기 (0) | 2025.04.30 |
SwiftUI 모드로 Xcode 이용하기: 개발 환경 탐색 (0) | 2025.04.29 |