[SwiftUI] Background, Grid, ScrollView 등(+View)
안녕하세요. 1000JI입니다 :)
저번 글에 이어서 계속 SwiftUI View 관련 코드를 이어가려고 합니다.

Background & Overlay
첫 번째 예제
Text 또는 Circle 등 View를 생성 한 뒤 배경 설정 또는 맨 앞에 배치하는 방법에 대한 내용 입니다.
Text("Hellow, World!")
.frame(width: 100, height: 100)
.background(
Circle()
.fill(
LinearGradient(
gradient: Gradient(
colors: [
Color.red,
Color.blue
]
),
startPoint: .leading,
endPoint: .trailing
)
)
)
.frame(width: 120, height: 120)
.background(
Circle()
.fill(Color.red)
)
위 코드를 살펴보게 되면, Text("Hellow, World!")를 생성 한 뒤 frame, background 설정을 하고 있습니다.
코드 그대로 텍스트의 사이즈를 100으로 설정한 후 background에 Circle을 생성하고 있습니다.
frame의 크기를 그대로 받아 생성하기 때문에 Circle의 사이즈도 100으로 설정 됩니다.
두 번째 예제
Circle()
.fill(Color.pink)
.frame(width: 100, height: 100)
.overlay(
Text("1")
.font(.title)
.foregroundColor(Color.white)
)
.background( // Background 뒤쪽에 깔리는 도형/색상을 지정 할 수 있음.
Circle()
.fill(Color.purple)
.frame(width: 120, height: 120)
)
overlay를 통해 Circle 위에 텍스트를 삽입 하였습니다.
이후 background를 사용하여 뒤쪽에 도형을 배치하였습니다.
Stack
Stack 뷰는 정말 다양하게 활용 할 수 있으며, 필수적인 뷰 중에 하나 입니다 :)
첫 번째 예제(ZStack, VStack, HStack)
1. ZStack
- 개요: ZStack은 자식 뷰를 Z축(깊이 축)으로 쌓는 스택입니다. 즉, 뷰를 겹쳐 쌓아올려서 배치합니다.
- 사용 예시: 배경 이미지 위에 텍스트를 겹쳐 놓거나, 여러 개의 뷰를 한 위치에 겹쳐서 놓아야 할 때 사용합니다.
2. HStack
- 개요: HStack은 자식 뷰를 가로축(X축)으로 나란히 배치하는 스택입니다.
- 사용 예시: 여러 개의 뷰를 수평으로 정렬해야 할 때 사용합니다. 예를 들어, 여러 개의 버튼이나 이미지를 가로로 나란히 배치하고자 할 때 적합합니다.
3. VStack
- 개요: VStack은 자식 뷰를 세로축(Y축)으로 나란히 배치하는 스택입니다.
- 사용 예시: 여러 개의 뷰를 수직으로 정렬해야 할 때 사용합니다. 예를 들어, 리스트 형태로 텍스트나 이미지 등을 세로로 배치하고자 할 때 적합합니다.
ZStack (alignment: .top) {
Rectangle()
.fill(Color.yellow)
.frame(width: 350, height: 500)
VStack(alignment: .leading, spacing: 30) {
Rectangle()
.fill(Color.red)
.frame(width: 150, height: 150)
Rectangle()
.fill(Color.green)
.frame(width: 100, height: 100)
HStack(alignment: .bottom, spacing: 10) {
Rectangle()
.fill(Color.purple)
.frame(width: 50, height: 50)
Rectangle()
.fill(Color.pink)
.frame(width: 75, height: 75)
Rectangle()
.fill(Color.blue)
.frame(width: 25, height: 25)
}
.background(Color.white)
}
.background(Color.black)
}
두 번째 예제(ZStack vs Background)
ZStack과 Background를 활용해서 같은 뷰를 다른 방법으로 구현 할 수 있습니다.
VStack(spacing: 50) {
// ZStack 을 사용해서 원에 1을 표현 - layer 가 복잡할때 ZStack 사용하면 좋음
ZStack(alignment: .center) {
Circle()
.frame(width: 100, height: 100)
Text("1")
.font(.title)
.foregroundColor(.white)
}
// Background 를 사용해서 원에 1을 동일하게 표현 - layer 가 단순할 경우 추천
Text("1")
.font(.title)
.foregroundColor(.white)
.background(
Circle()
.frame(width: 100, height: 100)
)
}
Spacer
Spacer는 SwiftUI에서 레이아웃을 구성할 때 빈 공간을 추가하는 데 사용하는 뷰입니다. Spacer는 가변적인 크기의 빈 공간을 만들어서 다른 뷰들 사이에 일정한 간격을 유지하거나, 원하는 위치로 뷰를 밀어낼 수 있도록 해줍니다.
Spacer의 특징
- 유연한 공간 제공: Spacer는 레이아웃에서 가능한 모든 여유 공간을 차지하려고 시도합니다. 여러 Spacer가 있을 경우, 해당 공간을 균등하게 나누어 가집니다.
- 수평 및 수직 정렬에 사용: Spacer는 HStack(수평 스택)과 VStack(수직 스택)에서 모두 사용되어 빈 공간을 추가할 수 있습니다.
- 간격 조절: Spacer를 사용하면 뷰 간의 간격을 자유롭게 조절할 수 있습니다. 예를 들어, Spacer를 HStack이나 VStack 내에서 사용하여 뷰들을 원하는 위치로 밀어낼 수 있습니다.
VStack {
HStack(spacing: 0) {
Image(systemName: "xmark")
Spacer() // Spacer는 최대한 공간을 차지하려는 속성이 있기 때문에 좌우로 밀어냄
Image(systemName: "gear")
}
.font(.title)
.padding(.horizontal)
Spacer()
}
ForEach
ForEach는 SwiftUI에서 반복적으로 뷰를 생성하고 배열하거나 목록을 만들 때 사용하는 구조입니다. 주어진 데이터 컬렉션을 순회하며 각 요소에 대해 뷰를 생성하여 화면에 표시할 수 있도록 도와줍니다.
ForEach의 특징
- 반복적으로 뷰 생성: 주어진 데이터 컬렉션의 각 요소에 대해 반복적으로 뷰를 생성합니다.
- 식별 가능한 데이터 사용: SwiftUI는 각 뷰를 고유하게 식별해야 하기 때문에, ForEach에서 사용하는 데이터는 고유한 식별자를 가져야 합니다. 식별자는 데이터가 변경되었을 때 SwiftUI가 뷰를 효율적으로 업데이트할 수 있도록 도와줍니다.
- 동적 목록 생성: ForEach를 사용하면 고정된 개수가 아닌 데이터 기반의 동적인 개수의 뷰를 생성할 수 있습니다.
var data: [String] = ["Hi", "Hello", "Hey everyone"]
VStack {
// View를 반복 사용하기 위해서 ForEach 구분을 사용해야 함.
// 컬렉션 데이터 값을 기반으로 뷰를 계산하는 구조임.
ForEach(0..<10) { index in
HStack {
Circle()
.frame(width: 20, height: 20)
Text("인덱스 번호 : \(index)번")
}
}
Divider()
// 2번
/*
* ForEach는 랜덤 액세스 컬렉션 타입임. 따라서 데이터 값에 하나씩 아이디 값을 넣어줘야함.
* id 파라미터는 ForEach에서 각 항목을 고유하게 식별하기 위한 값입니다.
* SwiftUI는 이 값을 사용하여 각 항목의 상태를 추적하고, 컬렉션의 데이터가 변경될 때 뷰를 최소한으로 업데이트할 수 있도록 합니다.
* id: \.self를 사용하는 것은 데이터가 고유한 값을 가지고 있을 때 적합합니다.
예를 들어, String이나 Int와 같은 기본 데이터 유형은 이미 고유성을 가지므로 id: \.self를 사용할 수 있습니다.
하지만 커스텀 데이터 유형을 사용하는 경우, 그 데이터 타입이 Hashable을 준수하거나, 특정 속성(id)을 사용하여 고유 식별자를 제공해야 합니다.
*/
ForEach(data, id: \.self) { item in
Text(item)
}
}
ScrollView
ScrollView는 SwiftUI에서 콘텐츠가 현재 화면에 다 담기지 않을 때 스크롤 가능한 영역을 만들어주는 뷰입니다. ScrollView는 사용자가 손가락을 움직여서 화면을 위아래 또는 양옆으로 이동할 수 있게 해주며, 긴 텍스트, 목록, 이미지, 또는 다양한 뷰들을 스크롤하여 볼 수 있도록 합니다.
ScrollView의 특징
- 스크롤 가능한 콘텐츠 영역: ScrollView를 사용하면 콘텐츠가 화면 밖으로 넘칠 때 해당 콘텐츠를 스크롤할 수 있도록 만들어줍니다.
- 수직 및 수평 스크롤: ScrollView는 수직(vertical) 또는 수평(horizontal)으로 스크롤할 수 있으며, 필요에 따라 스크롤 방향을 지정할 수 있습니다.
- 동적 콘텐츠: 긴 리스트, 다량의 데이터 또는 동적 콘텐츠를 스크롤을 통해 쉽게 표시할 수 있습니다.
- 내부 레이아웃 구성 지원: ScrollView 안에 다양한 레이아웃(VStack, HStack, ZStack 등)을 사용할 수 있습니다.
HStack과 LazyHStack은 SwiftUI에서 뷰를 수평으로 나란히 배치하는 데 사용하는 레이아웃 컨테이너입니다. 두 가지 모두 수평 방향으로 뷰를 배치하지만, 주요 차이점은 뷰의 로딩 방식에 있습니다.
주요 차이점
- 뷰 로딩 방식
- HStack: 모든 자식 뷰를 한 번에 메모리에 로드합니다. 즉, HStack에 포함된 모든 뷰가 초기화될 때 즉시 메모리에 로드되고, 레이아웃이 설정됩니다.
- LazyHStack: 필요할 때만 자식 뷰를 로드합니다. 스크롤 가능한 컨테이너 안에 있을 때, 화면에 나타나는 뷰들만 로드하고 나머지는 스크롤할 때 필요할 때 로드합니다. 따라서 많은 수의 뷰가 있을 때 성능이 더 효율적입니다.
- 사용 목적
- HStack: 소수의 뷰가 있을 때 적합합니다. 모든 뷰를 한 번에 메모리에 로드하기 때문에, 소규모의 정적인 뷰를 나열할 때 사용합니다.
- LazyHStack: 많은 수의 뷰가 있을 때 적합합니다. 특히 스크롤 가능한 컨테이너(예: ScrollView) 내에서 사용하여 성능을 최적화하고, 불필요한 메모리 사용을 줄입니다.
// 기본 값은 .vertical && showIndicators = true
ScrollView(.vertical, showsIndicators: true) {
// 데이터 양이 많아지는 경우 LazyVStack, LazyHStack을 사용하게 되면 화면에 보여지는 부분만 로딩이 되고,
// 스크롤을 하게 되면 나중에 정보가 업데이트 하는 방식임.
LazyVStack {
ForEach(0..<10) { _ in
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack {
ForEach(0..<20) { _ in
RoundedRectangle(cornerRadius: 25.0)
.fill(Color.white)
.frame(width: 200, height: 150)
.shadow(radius: 10)
.padding()
}
}
}
}
}
}
Grid
Grid는 SwiftUI에서 iOS 17, macOS 14, watchOS 10 및 tvOS 17 이후 사용할 수 있는 새로운 레이아웃 시스템으로, 행(row)과 열(column)로 구성된 격자 형태의 레이아웃을 만들 수 있게 해줍니다. Grid는 HStack과 VStack보다 유연하게 뷰를 배치할 수 있으며, 복잡한 레이아웃을 보다 직관적이고 간단하게 만들 수 있도록 도와줍니다.
Grid의 특징
- 격자 형태의 배치: Grid는 행과 열을 사용하여 뷰를 배치합니다. 이를 통해 가로와 세로로 복잡한 레이아웃을 쉽게 구성할 수 있습니다.
- 유연한 레이아웃 구성: 각 열과 행의 크기와 위치를 자유롭게 설정할 수 있어, 고정된 크기나 비율, 자동 크기 조절 등의 다양한 요구에 맞게 레이아웃을 구성할 수 있습니다.
- 자동화된 셀 크기 조정: Grid는 자식 뷰의 크기에 따라 셀 크기를 자동으로 조정할 수 있습니다.
- 컬럼 설정: 열의 개수와 크기를 정의하고, 각각의 열이 고정 너비 또는 비율에 따라 크기를 조정하도록 설정할 수 있습니다.
Grid의 주요 구성 요소
- Grid: 행과 열로 구성된 격자 형태의 기본 레이아웃 컨테이너입니다.
- GridRow: Grid 내에서 하나의 행을 정의하며, 행 안에 여러 개의 열을 포함할 수 있습니다.
- 열 크기 및 정렬: 열의 크기를 고정된 값으로 지정하거나, 콘텐츠에 따라 자동으로 조정되도록 설정할 수 있습니다.
/*
* List: 일반적 목록 배열 방식(일반적인 수직 방향으로 목록 표출)
* Grid: box 형태로 나타내는 배열 방식(가로 세로 방향으로 사진 배열 같은 형태 표출)
*/
// LazyVGrid
// columns 의 갯수를 3개로 설정
// .flexible() -> Single Flexible Item 그 크기를 화면 프로엠이 맞춰줌
// .flexible(minimum: 10, maximum: .infinity)
let columns: [GridItem] = [
GridItem(.flexible(), spacing: 6, alignment: nil),
GridItem(.flexible(), spacing: 6, alignment: nil),
GridItem(.flexible(), spacing: 6, alignment: nil),
]
var body: some View {
// 1 - LazyVGrid
ScrollView {
// Hero 부분 (위에 사진 부분)
Rectangle()
.fill(Color.orange)
.frame(height: 400)
LazyVGrid(
columns: columns,
alignment: .center,
spacing: 6,
pinnedViews: [.sectionHeaders]) {
// Section 1
Section(
header: Text("Section1")
.foregroundColor(.white)
.font(.title)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.blue)
.padding()
) {
ForEach(0..<20) { index in
Rectangle()
.fill(Color.gray)
.frame(height: 150)
.overlay(
Text("\(index) 번")
.frame(maxWidth: .infinity)
.background(Color.red)
)
}
} //: Section 1
// Section 2
Section(header: Text("Section2")
.foregroundColor(.white)
.font(.title)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.red)
.padding()
) {
ForEach(0..<20) { index in
Rectangle()
.fill(Color.green)
.frame(height: 150)
.overlay(
Text("\(index) 번")
)
}
} //: Section 2
}
} // :1번
}
Button
가장 많이 사용하게 될 View 중 하나인 Button이다.
다양한 예제로 살펴보자!
// property
// @State: UI 화면에 변경이 되면 자동으로 알아차려서 UI 화면에 업데이트를 해줌.
@State var mainTitle: String = "아직 버튼 안눌림"
var body: some View {
VStack (spacing: 20) {
// 리셋 버튼
Button {
// Action: 사용자가 버튼을 클릭할 경우 작업 수행 메서드
mainTitle = "리셋"
} label: {
// Label: 텍스트, 아이콘 등 버튼을 라벨링하기
Text("리셋버튼")
}
// 라벨
Text(mainTitle)
.font(.title)
Divider()
// 1번 버튼
// action 은 button을 눌렀을때 실행할 event 넣기
// label 은 button 모양을 디자인 하기
Button {
// action
mainTitle = "기본 버튼 눌림"
} label: {
// label
Text("기본 버튼")
}
.accentColor(.red)
Divider()
// 2번 버튼
Button {
mainTitle = "저장 버튼 눌림"
} label: {
Text("저장")
.font(.headline)
.fontWeight(.semibold)
.foregroundColor(.white)
.padding() // 왜 padding을 2개를 쓰는거지?
.padding(.horizontal, 20)
.background(
Color.blue
.cornerRadius(10)
.shadow(radius: 10)
)
}
Divider()
// 3번 버튼
Button {
mainTitle = "하트 버튼 눌림"
} label: {
Circle()
.fill(Color.white)
.frame(width: 75, height: 75)
.shadow(radius: 10)
.overlay(
Image(systemName: "heart.fill")
.font(.largeTitle)
.foregroundColor(Color.red)
)
}
// 4번 버튼
Button {
mainTitle = "완료 버튼 눌림"
} label: {
Text("완료")
.font(.caption)
.bold()
.foregroundColor(.gray)
.padding() // 상하좌우에 균등한 기본 패딩(여백)을 추가 함.
.padding(.horizontal, 10) // 가로 방향으로 10포인트 추가 패딩을 적용
.background(
Capsule()
.stroke(Color.gray, lineWidth: 2.5)
)
}
}
}
이상으로 SwiftUI의 주요한 뷰들을 예제와 함께 살펴보았습니다.
다음에는 @state, @binding 같은 상태 관리 로직도 살펴보겠습니다.
감사합니다!