-
[SwiftUI] Background, Grid, ScrollView 등(+View)SwiftUI 2024. 9. 8. 23:28
안녕하세요. 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 같은 상태 관리 로직도 살펴보겠습니다.
감사합니다!
'SwiftUI' 카테고리의 다른 글
[SwiftUI] Gradient, Icon, Image, Frame (0) 2024.08.28 [SwiftUI] Preview, Text, Shape (0) 2024.08.26 [SwiftUI] SwiftUI란? (0) 2024.08.25