[SwiftUI] HStack, VStack, ZStack으로 헤더 컴포넌트 만들기

Asher(애셔/오효준)·2026년 3월 11일
post-thumbnail

SwiftUI 입문 — F1 앱 헤더 만들기

Apple Developer Academy @ POSTECH 입과 후 첫 번째 SwiftUI 학습 기록입니다.
제가 스스로 연습과 학습을 위한 학습입니다. 애플아카데미에서 제공하는 학습자료가 아닌 점 알려드립니다. 전혀 무관함을 알려드립니다. 개인프로젝트입니다.


들어가며

SwiftUI를 처음 접하면서 가장 먼저 만들어본 것은 F1 레이싱 앱의 헤더 컴포넌트입니다. 단순해 보이지만 레이아웃의 핵심 개념인 HStack, VStack, ZStack, Spacer를 모두 활용할 수 있는 좋은 출발점이었습니다.

완성된 헤더는 아래와 같습니다.

☰  MONACO GRAND PRIX                    ⚙
   🔴 Live • Lap 42/78

프로젝트 구조

파일을 역할별로 분리하는 것부터 시작했습니다.

practiceApp/
├── Assets.xcassets
├── Components/
│   └── Header.swift       # 재사용 가능한 UI 부품
├── practiceApp.swift      # @main 앱 진입점
└── Screens/
    └── HomeView.swift     # 실제 화면
  • Components/ — 여러 화면에서 재사용할 수 있는 작은 UI 단위
  • Screens/ — 하나의 완성된 화면 단위

ContentView.swift는 필수가 아닙니다. @main 파일에서 원하는 View를 바로 진입점으로 지정할 수 있습니다.

@main
struct practiceApp: App {
    var body: some Scene {
        WindowGroup {
            HomeView() // ContentView 없이 바로 연결
        }
    }
}

레이아웃 기초 — HStack / VStack / ZStack

SwiftUI의 레이아웃은 세 가지 Stack으로 구성됩니다.

Stack방향주요 용도
HStack가로 (←→)아이콘, 버튼 나열
VStack세로 (↑↓)제목 + 부제목 배치
ZStack앞뒤 겹치기배경 위에 컨텐츠 올리기

각 Stack은 alignment 파라미터로 정렬 방향을 지정할 수 있습니다.

VStack(alignment: .leading) { }  // 왼쪽 정렬
ZStack(alignment: .top) { }      // 위쪽 정렬

Spacer() — 공간을 차지하는 투명한 뷰

Spacer()남은 공간을 전부 차지하는 투명한 뷰입니다. 직접 무언가를 밀어내는 것이 아니라, 공간을 먼저 차지함으로써 다른 뷰들이 상대적으로 밀려나는 효과를 만듭니다.

// HStack — 양쪽 끝으로 분리
HStack {
    Text("왼쪽")
    Spacer()
    Text("오른쪽")
}

// VStack — 헤더를 상단에 고정
VStack {
    Header()
    Spacer()
}

전체 화면 검은 배경 적용

ZStack으로 Color.black을 배경으로 먼저 깔고, 그 위에 컨텐츠를 올립니다. .ignoresSafeArea()를 적용하면 노치/홈바 영역까지 색상이 채워집니다.

struct HomeView: View {
    var body: some View {
        ZStack(alignment: .top) {
            Color.black
                .ignoresSafeArea()  // Safe Area 영역까지 검은색

            VStack(alignment: .leading) {
                Header()
                Spacer()
            }
        }
    }
}

SF Symbols

Apple이 제공하는 6,000개 이상의 무료 아이콘 라이브러리입니다. 별도 설치 없이 Image(systemName:)으로 바로 사용할 수 있습니다.

Image(systemName: "line.3.horizontal")  // 햄버거 메뉴
Image(systemName: "gearshape")          // 설정
Image(systemName: "magnifyingglass")    // 검색

Mac App Store에서 SF Symbols 앱을 설치하면 아이콘을 카테고리별로 검색하고 이름을 바로 복사할 수 있어 개발 속도가 훨씬 빨라집니다.


커스텀 컬러 — Color Extension

extension으로 Color를 확장하면 프로젝트 전반에서 .f1Red처럼 기본 색상처럼 사용할 수 있습니다.

extension Color {
    static let f1Red = Color(red: 225/255, green: 6/255, blue: 0/255)
}

// 사용
.foregroundColor(.f1Red)
.foregroundColor(.f1Red.opacity(0.3))  // 투명도 조절

배경 있는 버튼 — ZStack 활용

ZStack으로 도형과 버튼을 겹쳐서 배경이 있는 버튼을 만들 수 있습니다.

ZStack {
    RoundedRectangle(cornerRadius: 10)
        .frame(width: 50, height: 50)
        .foregroundColor(.f1Red.opacity(0.3))
    Button(action: {}) {
        Image(systemName: "gearshape")
            .foregroundColor(.red)
            .font(.system(size: 30))
    }
}

최종 코드

// Header.swift
import SwiftUI

extension Color {
    static let f1Red = Color(red: 225/255, green: 6/255, blue: 0/255)
}

struct Header: View {
    var body: some View {
        HStack {
            Button(action: {}) {
                Image(systemName: "line.3.horizontal")
                    .foregroundColor(.red)
                    .font(.system(size: 30))
            }

            VStack(alignment: .leading) {
                Text("Monaco Grand Prix")
                    .font(.title3)
                    .bold()
                    .foregroundColor(.white)
                HStack {
                    Circle()
                        .frame(width: 8, height: 8)
                        .foregroundColor(.red)
                    Text("Live • Lap 42/78")
                        .font(.title3)
                        .bold()
                        .foregroundColor(.red)
                }
            }
            .padding()

            Spacer()

            ZStack {
                RoundedRectangle(cornerRadius: 10)
                    .frame(width: 50, height: 50)
                    .foregroundColor(.f1Red.opacity(0.3))
                Button(action: {}) {
                    Image(systemName: "gearshape")
                        .foregroundColor(.red)
                        .font(.system(size: 30))
                }
            }
        }
        .padding(.horizontal)
    }
}

마치며

첫 SwiftUI 컴포넌트를 만들면서 느낀 것은, UIKit과 달리 레이아웃을 선언적으로 구성한다는 점이 매우 직관적이라는 것입니다. Stack을 조합하고 Spacer로 공간을 배분하는 방식에 익숙해지면 복잡한 레이아웃도 명확하게 구조화할 수 있을 것 같습니다.

다음에는 @State@Binding을 활용해 버튼에 실제 동작을 연결하고, 바텀 네비게이션바를 통한 화면 전환을 구현할 예정입니다.


학습 기록은 GitHub AppleDeveloperAcademy_TIL에서 확인할 수 있습니다.

profile
Asher입니다. 하지만 Joon이라고도 불리는

0개의 댓글