SwiftUI 스택

개요

SwiftUI에서 가장 기초적인 개념중 하나. 스택에 대해서 알아보고 사용법에 대해 익혀보겠다.

애플 공식문서




스택


스택은 뷰를 배치하는 데 사용하는 컨테이너 뷰로, 컨텐츠로 전달된 자식 뷰들을 어떤 형태로 배치할 건지 결정한다.

스택에는 가로, 세로 방향으로 뷰를 배열하는 Horizontal Stack(HStack), Vertical Stack(VStack), Depth Stack(ZStack)`의 3가지가 있다.

스택은 뷰 프로토콜 형태인 Content를 제네릭 매개 변수로 받아 자식 뷰로 표현하는 제네릭 구조체로 선언되어있다.

프로퍼티에는 반드시 하나의 값만 반환해야한다. 그러므로 여러가지 텍스트들을 묶어서 하나의 컨테이너로 반환해준다.

1
2
3
4
5
6
7
8
9
struct HStack<Content> : View where Content: View
struct VStack<Content> : View where Content: View
struct ZStack<Content> : View where Content: View

container {
  content
  content
  ...
}



HStack


Horizontal Stack. 즉 뷰를 가로로 배열하는 스택이다.

HStack은 화면이 온스크린이든 오프스크린이든 관계없이 한번에 렌더링한다.

자식 뷰의 숫자가 적을 때나 lazy Hstack의 렌더링에서의 지연을 원하지 않을 때는 일반 HStack을 사용해라.

스택도 하나의 뷰이므로 뷰 프로토콜을 가진 수식어를 활용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//초기값 설정
init(alignment: VerticalAlignment = .center, // 컨텐츠 정렬 방식
     spacing: CGFloat? = nil, //공백
     content: () -> Content) //컨텐츠


//예시
var body: some View {
    HStack(
        alignment: .top,
        spacing: 10
    ) {
        ForEach(
            1...5,
            id: \.self
        ) {
            Text("Item \($0)")
        }
    }
    .padding()
}
//result : item1 item2 item3 item4 item5



VStack


Vertical Stack. 즉 뷰를 세로로 배열하는 스택이다.

VStack 화면이 온스크린이든 오프스크린이든 관계없이 한번에 렌더링한다.

자식 뷰의 숫자가 적을 때나 lazy VStack 렌더링에서의 지연을 원하지 않을 때는 일반 VStack 사용해라.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var body: some View {
    VStack(
        alignment: .leading,
        spacing: 10
    ) {
        ForEach(
            1...5,
            id: \.self
        ) {
            Text("Item \($0)")
        }
    }
}
//result : item1 
           item2 
           item3 
           item4 
           item5



ZStack


Depth Stack이다. ZStack은 연속적인 자식 뷰를 이전의 z-축보다 높은 곳에 할당한다. 즉 나중의 자식 뷰들이 앞에 보인다.

ZStack은 각각의 자식들의 x축, y축에 따라서 좌표를 세팅한다. 기본은 중앙 할당이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let colors: [Color] =
    [.red, .orange, .yellow, .green, .blue, .purple]

var body: some View {
    ZStack {
        ForEach(0..<colors.count) {
            Rectangle()
                .fill(colors[$0])
                .frame(width: 100, height: 100)
                .offset(x: CGFloat($0) * 10.0,
                        y: CGFloat($0) * 10.0)
        }
    }
}




Spacer


Spacer은 뷰 사이의 간격을 설정하거나 뷰의 크기를 확장할 용도로 사용된다.

1
2
3
4
5
6
struct Spacer {
  var minLength: CGFloat?   // 최소 간격
  init(minLength: CGFloat? = nil)  //nil이면 기본 간격
  typealias Body = Never
}
extension Spacer: View {}

스택 외부와 내부에서의 사용 결과는 다르다.



스택 외부


부모 뷰가 제공하는 공간내에서 최대 크기로 확장되며, 시각적 요소를 적용할 수 있는 하나의 뷰로 사용된다.



스택 내부


시각적 요소가 제외되고 공간을 차지하기 위한 역할로만 사용

1
2
3
4
5
6
7
8
9
10
11
// 파란 색으로 안바뀜
HStack {
  spacer().background(Color.blue)
  Text("Spacer").background(Color.yellow)
}

// 글자의 가로로 최대만큼 파란색으로 바뀜
HStack {
  spacer()
  Text("Spacer").background(Color.yellow)
}.background(Color.blue)




ZStack 안쓰는 중첩된 뷰


overlaybackground 수식어를 이용하면 중첩된 뷰를 표현하는 것이 가능하다.

1
2
3
4
ZStack {
  Rectangle().fill(Color.green).frame(width: 150, height: 150)
  Rectangle().fill(Color.yellow).frame(width: 150, height: 150)
}

위의 코드와 같은 코드를 만들어보겠다.



overlay


위로 새로운 뷰를 중첩하여 쌓는다.

1
2
3
4
Rectangle().fill(Color.green).frame(width: 150, height: 150)
  .overlay(
    Rectangle().fill(Color.yellow)
  )



background


아래로 새로운 뷰를 중첩하여 쌓는다.

1
2
3
4
Rectangle().fill(Color.yellow).frame(width: 150, height: 150)
  .background(
    Rectangle().fill(Color.green)
  )



선택




overlay/background


  • 수식 대상이 되는 뷰와 직접적인 연관성이 있는 뷰를 추가할 때
  • 전체 화면의 레이아웃을 구성할 때보다는 UI의 각 부분을 구성하는 개별 객체를 꾸밀 때



ZStack


  • 직접적인 연관성이 없는 뷰를 구성할 때




컨테이너의 자식 뷰 제한


컨테이너 뷰는 직접적인 하위 뷰를 10개로 제한한다.

만약 스택 컨테이너가 10개 이상의 자식뷰를 담으면 다음과 같은 오류를 발생한다.

Argument passed to call that takes no argument

만약 10개를 넘기고 싶다면 Group 컨테이너를 이용해서 그룹화를 하면 된다.




텍스트 줄 제한과 우선순위


Stack 뒤에 lineLimit() 수정자를 사용하면 된다.

괄호 안에 제한 할 숫자를 적으면 된다. 공간이 충분하다면 정상적으로 출력되고 아닐 경우는 …으로 표시된다.

모든 경우가 적당히 나눠져서 출력되는데, 우선순위를 정해서 어떤 것을 …이 아닌 우선적으로 출력할 지 지정할 수 있다.