- 컨테이너 정렬, 정렬 가이드, 커스텀 정렬, 서로 다른 스택들 간의 정렬 구현까지 스택 정렬의 고급 기술 설명
컨테이너 정렬
- 스택 사용 시 가장 기본적인 정렬 방법은 컨테이너 정렬
- 스택에 포함된 하위 뷰들이 스택 내에서 정렬되는 방식을 정의함
- ‘암묵적으로 정렬되었다’
- 스택에 포함된 각각의 뷰에 지정된 정렬이 따로 없어, 스택에 적용한 정렬이 하위 뷰에 적용되는 것
- 개별적으로 적용된 정렬이 없는 하위 뷰에 상위 뷰의 정렬 방법이 적용되는 것
- 수평 스택 (HStack) : 하위 뷰를 수직 방향 정렬
- 수직 스택 (VStack) : 하위 뷰를 수평 방향 정렬
- ZStack : 하위 뷰를 수평/수직 방향 정렬
- 세 개의 하위 뷰를 포함하는 VStack 구성
VStack{
Text("This is some text")
Text("This is some longer text")
Text("This is short")
}
→ 컨테이너 정렬 값이 없으면 하위 뷰들은 모두 중앙 정렬(.center)이 디폴트 값
- leading, trailing 정렬 사용
VStack{
Text("This is some text")
Text("This is some longer text")
Text("This is short")
}
.padding(50)
VStack(alignment: .leading){
Text("This is some text")
Text("This is some longer text")
Text("This is short")
}
.padding(50)
VStack(alignment: .trailing){
Text("This is some text")
Text("This is some longer text")
Text("This is short")
}
.padding(50)
- HStack(수평 스택) 또한, 디폴트로 중앙 정렬이 되지만, 텍스트 베이스 라인 정렬을 위한 값뿐만 아니라 상단 정렬과 하단 정렬 제공
- 정렬을 지정할 때 여백 값 포함 가능
- HStack의 디폴트로 여백이 있는 중앙 정렬 사용, 서로 다른 폰트 크기를 가진 세 개의 하위 뷰 포함
HStack(spacing: 30){
Text("This is some text")
.font(.largeTitle)
Text("This is some much longer text")
.font(.body)
Text("This is short")
.font(.headline)
}
- 텍스트 베이스라인 정렬은 텍스트 기반 뷰의 첫 줄(.firstTextBaseline) 또는 마지막 줄(.lastTextBaseline)을 기준으로 둠
HStack(alignment: .firstTextBaseline, spacing: 30){
Text("This is some text")
.font(.largeTitle)
Text("This is some much longer text")
.font(.body)
Text("This is short")
.font(.headline)
}
.padding(.bottom, 50)
HStack(alignment: .lastTextBaseline, spacing: 30){
Text("This is some text")
.font(.largeTitle)
Text("This is some much longer text")
.font(.body)
Text("This is short")
.font(.headline)
}
정렬 가이드(alignment guide)
- 뷰가 스택에 포함된 다른 뷰와 정렬해야 할 때 사용되는 커스텀 포지션을 정의하는 데 사용
- 복잡한 정렬 구현 가능
- ex) 길이의 3분의 2 위치 또는 상단에서 20포인트를 기준으로 뷰를 정렬
- 표준 정렬 타입과 클로저를 인자로 받는 alignmentGuide() 수정자를 사용하여 적용
- 클로저는 표준 정렬 기준으로 하는 뷰 내에 위치(포인트)를 가리키는 값을 계산하여 반환
- 뷰 내의 정렬 위치 계산을 돕기 위해 뷰의 폭과 넓이를 얻는데 사용하는 ViewDimensions 객체와 뷰의 표준 정렬 위치 (.top .bottom 등)가 클로저에 전달됨
- VStack은 세 개의 서로 다른 길이와 색상을 가진 사각형을 가지고, 모두 앞쪽 정렬한 코드
VStack(alignment: .leading){
Rectangle()
.foregroundColor(.green)
.frame(width:120, height: 50)
Rectangle()
.foregroundColor(.red)
.frame(width:200, height: 50)
Rectangle()
.foregroundColor(.blue)
.frame(width:180, height: 50)
}
- 두 번째 뷰만 앞쪽에서 120포인트 안쪽으로 들어가게 정렬
VStack(alignment: .leading){
Rectangle()
.foregroundColor(.green)
.frame(width:120, height: 50)
Rectangle()
.foregroundColor(.red)
.alignmentGuide(.leading, computeValue: { dimension in
120.0
})
.frame(width:200, height: 50)
Rectangle()
.foregroundColor(.blue)
.frame(width:180, height: 50)
}
→ 정렬 가이드를 사용할 때는 alignmentGuide() 수정자에 지정된 정렬 타입은 부모 스택에 적용된 정렬 타입과 일치해야 함
VStack(alignment: .leading){
...
Rectangle()
.foregroundColor(.red)
.alignmentGuide(.leading, computeValue: { dimension in
120.0
})
.frame(width:200, height: 50)
...
}
- offset을 하드코딩하는 대신, 클로저에 전달된 ViewDimensions 객체 (위 예제의 ‘dimension’)의 프로퍼티를 정렬 가이드 위치를 계산하는 데 이용 가능함
- ex) width 프로퍼티를 이용하면 뷰의 앞쪽 3분의 1 위치로 배치
VStack(alignment: .leading){
Rectangle()
.foregroundColor(.green)
.frame(width:120, height: 50)
Rectangle()
.foregroundColor(.red)
.alignmentGuide(.leading, computeValue: { dimension in
dimension.width / 3
})
.frame(width:200, height: 50)
Rectangle()
.foregroundColor(.blue)
.frame(width:180, height: 50)
}
- ViewDimension 객체는 뷰의 HorizontalAlignment와 VerticalAlignment 프로퍼티에 대한 접근 제공
- 뷰의 끝쪽에 20포인트를 추가로 더하는 예제
Rectangle()
.foregroundColor(.red)
.alignmentGuide(.leading, computeValue: { dimension in
dimension[HorizontalAlignment.trailing] + 20
})
→ 뷰의 끝 쪽은 다른 뷰의 앞쪽에서 20포인트 더해진 위치로 정렬 됨
커스텀 정렬 타입
- 커스텀 정렬 타입을 선언하여 표준 타입들을 확장
- ex) .oneThird 이름의 커스텀 정렬 타입은 뷰의 지정된 끝쪽에서부터 3분의 1 거리 위치로 정렬하게 생성
- 수직 방향으로 중앙에 위치하는 네 개의 사각형으로 구성된 HStack
HStack(alignment: .center){
Rectangle()
.foregroundColor(.green)
.frame(width: 50, height: 200)
Rectangle()
.foregroundColor(.red)
.frame(width: 50, height: 200)
Rectangle()
.foregroundColor(.blue)
.frame(width: 50, height: 200)
Rectangle()
.foregroundColor(.orange)
.frame(width: 50, height: 200)
}
→ 위 사각형 중 하나 이상의 사각형만 정렬을 변경할 때 정렬 가이드 사용 가능
- 다른 방법으로는 여러 뷰에 적용될 수 있는 커스텀 정렬 생성하는 방법
- 계산된 값을 반환하는 새로운 정렬 타입을 추가하기 위하여 VerticalAlignment, HorizontalAlignment를 확장
- 새로운 수직 정렬 타입을 생성하는 예제
extension VerticalAlignment{
private enum OneThird : AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat {
return context.height / 3 // 뷰 높이의 3분의 1이 반환 됨
}
}
static let oneThird = VerticalAlignment(OneThird.self)
}
- 이 extension은 AlignmentID 프로토콜을 따라는 열거형(enum)을 포함해야 함
- defaultValue()라는 이름의 함수가 구현되도록 지시함
- 이 함수는 뷰에 대한 ViewDimensions 객체를 받음
- 정렬 가이드 위치를 가리키는 계산된 CGFloat 값을 반환함
- 커스텀 정렬은 HStack 선언부에 사용 가능
HStack(alignment: .oneThird){
Rectangle()
.foregroundColor(.green)
.frame(width: 50, height: 200)
Rectangle()
.foregroundColor(.red)
.alignmentGuide(.oneThird, computeValue: { dimension in
dimension[VerticalAlignment.top]
})
.frame(width: 50, height: 200)
Rectangle()
.foregroundColor(.blue)
.frame(width: 50, height: 200)
Rectangle()
.foregroundColor(.orange)
.alignmentGuide(.oneThird, computeValue: { dimension in
dimension[VerticalAlignment.bottom]
})
.frame(width: 50, height: 200)
}
→ 빨간 뷰는 상단 기준, 오렌지 뷰는 하단 기준으로 정렬됨
스택 정렬 교차
- 표준 정렬 타입의 단점 : 스택 내의 뷰가 다른 스택에 있는 뷰와 정렬되도록 하는 방법을 제공하지 않음
- HStack 안에 VStack이 포함된 스택 구성도
HStack(alignment: .center, spacing: 20){
Circle()
.foregroundColor(.purple)
.frame(width: 100, height: 100)
VStack{
Rectangle()
.foregroundColor(.green)
.frame(width: 100, height: 100)
Rectangle()
.foregroundColor(.red)
.frame(width: 100, height: 100)
Rectangle()
.foregroundColor(.blue)
.frame(width: 100, height: 100)
Rectangle()
.foregroundColor(.orange)
.frame(width: 100, height: 100)
}
}
- VStack과 원을 나타내는 뷰는 HStack 내에서 수직 방향으로 중앙 정렬되어 있음
- 만약 원을 HStack에 있는 맨 위의 사각형이나 맨 아래 사각형과 정렬하고 싶다면 HStack 정렬을 .top, .bottom으로 변경하면 됨
- 그러나, 두 번째, 세 번째 사각형과 정렬 가능한 표준 정렬 타입은 없음 → 커스텀 정렬 사용
- 뷰의 아래쪽을 기준으로 한 정렬 값을 반환하는 커스텀 정렬 구현
extension VerticalAlignment {
private enum CrossAlignment : AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat {
return context[.bottom]
}
}
static let crossAlignment = VerticalAlignment(CrossAlignment.self)
}
- 만든 커스텀 정렬은 서로 다른 스택에 포함된 뷰를 정렬하기 위해 사용 가능함
- 원 뷰의 아래쪽이 VStack에 포함된 세 번째 사각형과 정렬 코드
HStack(alignment: .crossAlignment, spacing: 20){
Circle()
.foregroundColor(.purple)
.alignmentGuide(.crossAlignment, computeValue: { dimension in
dimension[VerticalAlignment.center]
})
.frame(width: 100, height: 100)
VStack{
Rectangle()
.foregroundColor(.green)
.frame(width: 100, height: 100)
Rectangle()
.foregroundColor(.red)
.frame(width: 100, height: 100)
Rectangle()
.foregroundColor(.blue)
.alignmentGuide(.crossAlignment, computeValue: { dimension in
dimension[VerticalAlignment.center]
})
.frame(width: 100, height: 100)
Rectangle()
.foregroundColor(.orange)
.frame(width: 100, height: 100)
}
}
ZStack 커스텀 정렬
- 디폴트로 하위 뷰는 중앙 정렬된 상태로 위로 겹치게 쌓임
- 표준 정렬을 이용하면 정렬을 바꿀 수 있음
- ZStack에서 커스텀 정렬을 사용하려면 수평 커스텀 정렬과 수직 커스텀 정렬을 결합해야 함
extension HorizontalAlignment {
enum MyHorizontal : AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat {
return context[HorizontalAlignment.center]
}
}
static let myAlignment = HorizontalAlignment(MyHorizontal.self)
}
extension VerticalAlignment {
enum MyVertical : AlignmentID {
static func defaultValue(in context: ViewDimensions) -> CGFloat {
return context[VerticalAlignment.center]
}
}
static let myAlignment = VerticalAlignment(MyVertical.self)
}
extension Alignment {
static let myAlignment = Alignment(horizontal: .myAlignment, vertical: .myAlignment)
}
→ 커스텀 정렬 구현 (ZStack)에서 사용 가능 myAlignment
ZStack(alignment: .myAlignment){
Rectangle()
.foregroundColor(.green)
.alignmentGuide(HorizontalAlignment.myAlignment, computeValue: { dimension in
dimension[.trailing]
})
.alignmentGuide(VerticalAlignment.myAlignment, computeValue: { dimension in
dimension[VerticalAlignment.bottom]
})
.frame(width: 100, height: 100)
Rectangle()
.foregroundColor(.red)
.alignmentGuide(VerticalAlignment.myAlignment, computeValue: { dimension in
dimension[VerticalAlignment.top]
})
.alignmentGuide(HorizontalAlignment.myAlignment, computeValue: { dimension in
dimension[HorizontalAlignment.center]
})
.frame(width: 100, height: 100)
Circle()
.foregroundColor(.orange)
.alignmentGuide(HorizontalAlignment.myAlignment, computeValue: { dimension in
dimension[.leading]
})
.alignmentGuide(VerticalAlignment.myAlignment, computeValue: { dimension in
dimension[.bottom]
})
.frame(width: 100, height: 100)
}
- 정렬 설정 변경 후 테스트
ZStack(alignment: .myAlignment){
Rectangle()
.foregroundColor(.green)
.alignmentGuide(HorizontalAlignment.myAlignment, computeValue: { dimension in
dimension[.leading]
})
.alignmentGuide(VerticalAlignment.myAlignment, computeValue: { dimension in
dimension[VerticalAlignment.bottom]
})
.frame(width: 100, height: 100)
Rectangle()
.foregroundColor(.red)
.alignmentGuide(VerticalAlignment.myAlignment, computeValue: { dimension in
dimension[VerticalAlignment.center]
})
.alignmentGuide(HorizontalAlignment.myAlignment, computeValue: { dimension in
dimension[HorizontalAlignment.trailing]
})
.frame(width: 100, height: 100)
Circle()
.foregroundColor(.orange)
.alignmentGuide(HorizontalAlignment.myAlignment, computeValue: { dimension in
dimension[.leading]
})
.alignmentGuide(VerticalAlignment.myAlignment, computeValue: { dimension in
dimension[.top]
})
.frame(width: 100, height: 100)
}
'SwiftUI' 카테고리의 다른 글
SwiftUI - Core Location 프레임워크 (0) | 2024.03.03 |
---|---|
SwiftUI - 리스트와 내비게이션 (0) | 2024.02.21 |
SwiftUI - Observable 객체와 Environment 객체 튜토리얼 (2) | 2024.01.27 |
SwiftUI - 슬라이더 뷰(Slider View) 사용 (1) | 2024.01.22 |
SwiftUI - 상태 프로퍼티 (State), Observable 객체, Environment 객체 사용 (0) | 2024.01.15 |