SwiftUI 스택 정렬과 정렬 가이드
컨테이너 정렬
- 가장 기본적인 정렬 방법
- 스택에 포함된 하위 뷰들이 스택 내에서 정렬되는 방식
- 지정된 정렬이 따로 없다면, 스택에 적용된 정렬이 하위 뷰에 적용(암묵적 정렬(implicitly aligned))
- 수직 스택(VStack)은 하위 뷰를 수평 방향 정렬
VStack { Text("This is some text") Text("This is some longer text") Text("This is short") }
특정 컨테이너 정렬(alignment) 값이 없으면, 중앙 정렬(.center)이 디폴트
- .leading (앞쪽 정렬)
VStack(alignment: .leading) { Text("This is some text") Text("This is some longer text") Text("This is short") }
- .trailing (뒤쪽 정렬)
VStack(alignment: .trailing) { Text("This is some text") Text("This is some longer text") Text("This is short") }
- .leading (앞쪽 정렬)
- 수평 스택(HStack)은 하위 뷰를 수직 방향 정렬
HStack(spacing: 20) { Text("This is some text") .font(.largeTitle) Text("This is some much longer text") .font(.body) Text("This is short") .font(.headline) }
여백이 있는 중앙 정렬을 디폴트
텍스트 베이스라인 정렬 뿐만 아니라 상단 정렬과 하단 정렬도 제공- .firstTextBaseline (첫 줄 기준)
HStack(alignment: .firstTextBaseline, spacing: 20) { Text("This is some text") .font(.largeTitle) Text("This is some much longer text") .font(.body) Text("This is short") .font(.headline) }
- .lastTextBaseline (마지막 줄 기준)
HStack(alignment: .lastTextBaseline, spacing: 20) { Text("This is some text") .font(.largeTitle) Text("This is some much longer text") .font(.body) Text("This is short") .font(.headline) }
- .firstTextBaseline (첫 줄 기준)
- ZStack은 수평/수직 정렬 값을 모두 사용
정렬 가이드
- 뷰가 스택에 포함된 다른 뷰와 정렬해야 할 때 사용되는 커스텀 포지션을 정의하는데 사용
- 표준 정렬보다 더 복잡한 정렬을 구현
- 길이의 3분의 2 위치, 상단에서 20포인트 기준…
- alignmentGuide() 수정자 사용
- 클로저(인자) : 뷰 내에 위치를 가리키는 값을 계산하여 반환
- ViewDimensions 객체 와 뷰의 표준 정렬 위치 (.top, .bottom, ..등)가 클로저에 전달
- 클로저(인자) : 뷰 내에 위치를 가리키는 값을 계산하여 반환
EX)
VStack(alignment: .leading) {
Rectangle()
.foregroundColor(Color.green)
.frame(width: 120, height: 50)
Rectangle()
.foregroundColor(Color.red)
.frame(width: 200, height: 50)
Rectangle()
.foregroundColor(Color.blue)
.frame(width: 180, height: 50)
}
- 두 번째 뷰만 앞쪽에서 120포인트 안쪽으로 돌어가게
VStack(alignment: .leading) { Rectangle() .foregroundColor(Color.green) .frame(width: 120, height: 50) Rectangle() .foregroundColor(Color.red) .alignmentGuide(.leading, computeValue: { d in 120.0 }) // d: ViewDimensions 객체 .frame(width: 200, height: 50) Rectangle() .foregroundColor(Color.blue) .frame(width: 180, height: 50) }
alignmentGuide() 수정자에 지정된 정렬 타입은 부모 스택에 적용된 정렬 타입과 일치(.leading) - ViewDimensions 객체의 프로퍼티를 정렬 가이드 위치를 계산하는데 이용
VStack(alignment: .leading) { Rectangle() .foregroundColor(Color.green) .frame(width: 120, height: 50) Rectangle() .foregroundColor(Color.red) .alignmentGuide(.leading, computeValue: { d in d.width / 3 }) .frame(width: 200, height: 50) Rectangle() .foregroundColor(Color.blue) .frame(width: 180, height: 50) }
- ViewDimensions 객체는 뷰의 HorizontalAlignment와 VerticalAlignment 프로퍼티에 대한 접근을 제공
.alignmentGuide(.leading, computeValue: { d in d[HorizontalAlignment.trailing] + 20 })
커스텀 정렬 타입
EX)
.oneThird(커스텀 정렬 타입): 뷰의 지정된 끝쪽에서부터 3분의 1 거리 위치로 정렬
extension VerticalAlignment {
private enum OneThird : AlignmentID {
static func defaultValue(in d: ViewDimensions) -> CGFloat {
return d.height / 3
}
}
static let oneThird = VerticalAlignment(OneThird.self)
}
AlignmentID 프로토콜을 따르는 열거형을 포함(enum)
defaultValue() 함수 구현
ViewDimensions 객체를 받아야 함
CGFloat 값을 반환
HStack(alignment: .oneThird) {
Rectangle()
.foregroundColor(Color.green)
.frame(width: 50, height: 200)
Rectangle()
.foregroundColor(Color.red)
.alignmentGuide(.oneThird, computeValue: { d in d[VerticalAlignment.top] })
.frame(width: 50, height: 200)
Rectangle()
.foregroundColor(Color.blue)
.frame(width: 50, height: 200)
Rectangle()
.foregroundColor(Color.orange)
.alignmentGuide(.oneThird, computeValue: { d in d[VerticalAlignment.top] })
.frame(width: 50, height: 200)
}
스택 정렬 교차하기
표준 정렬의 단점: 스택 내의 뷰가 다른 스택에 있는 뷰와 정렬되도록 하는 방법x
EX)
HStack(alignment: .center, spacing: 20) {
Circle()
.foregroundColor(Color.purple)
.frame(width: 100, height: 100)
VStack(alignment: .center) {
Rectangle()
.foregroundColor(Color.green)
.frame(width: 100, height: 100)
Rectangle()
.foregroundColor(Color.red)
.frame(width: 100, height: 100)
Rectangle()
.foregroundColor(Color.blue)
.frame(width: 100, height: 100)
Rectangle()
.foregroundColor(Color.orange)
.frame(width: 100, height: 100)
}
}
원을 VStack의 맨 위의 사각형과 정렬: .top, 맨 아래: .bottom
두 번째 또는 세 번째 사각형과 정렬: 커스텀 정렬
extension VerticalAlignment {
private enum CrossAlignment : AlignmentID {
static func defaultValue(in d: ViewDimensions) -> CGFloat {
return d[.bottom]
}
}
static let crossAlignment = VerticalAlignment(CrossAlignment.self)
}
두 번째 사각형과 정렬
HStack(alignment: .crossAlignment, spacing: 20) {
Circle()
.foregroundColor(Color.purple)
.alignmentGuide(.crossAlignment,
computeValue: { d in d[VerticalAlignment.center] })
.frame(width: 100, height: 100)
VStack(alignment: .center) {
Rectangle()
.foregroundColor(Color.green)
.frame(width: 100, height: 100)
Rectangle()
.foregroundColor(Color.red)
.alignmentGuide(.crossAlignment,
computeValue: { d in d[VerticalAlignment.center] })
.frame(width: 100, height: 100)
Rectangle()
.foregroundColor(Color.blue)
.frame(width: 100, height: 100)
Rectangle()
.foregroundColor(Color.orange)
.frame(width: 100, height: 100)
}
}
ZStack 커스텀 정렬
수평 커스텀 정렬과 수직 커스텀 정렬 모두가 하나의 커스텀 정렬로 결합
extension HorizontalAlignment {
enum MyHorizontal: AlignmentID {
static func defaultValue(in d: ViewDimensions) -> CGFloat {
d[HorizontalAlignment.center]
}
}
static let myAlignment = HorizontalAlignment(MyHorizontal.self)
}
extension VerticalAlignment {
enum MyVertical: AlignmentID {
static func defaultValue(in d: ViewDimensions) -> CGFloat {
d[VerticalAlignment.center]
}
}
static let myAlignment = VerticalAlignment(MyVertical.self)
}
extension Alignment {
static let myAlignment = Alignment(horizontal: .myAlignment, vertical: .myAlignment)
}
ZStack(alignment: .myAlignment) {
Rectangle()
.foregroundColor(Color.green)
.alignmentGuide(HorizontalAlignment.myAlignment)
{ d in d[.leading] }
.alignmentGuide(VerticalAlignment.myAlignment)
{ d in d[VerticalAlignment.bottom] }
.frame(width: 100, height: 100)
// 오른쪽, 위
Rectangle()
.foregroundColor(Color.red)
.alignmentGuide(VerticalAlignment.myAlignment)
{ d in d[VerticalAlignment.center] }
.alignmentGuide(HorizontalAlignment.myAlignment)
{ d in d[HorizontalAlignment.trailing] }
.frame(width: 100, height: 100)
// 가운데, 왼쪽
Circle()
.foregroundColor(Color.orange)
.alignmentGuide(HorizontalAlignment.myAlignment)
{ d in d[.leading] }
.alignmentGuide(VerticalAlignment.myAlignment)
{ d in d[.top] }
.frame(width: 100, height: 100)
//오른쪽, 아래
}
[.top]은 [VerticalAlignment.top]의 생략
뷰의 위치는 뷰의 정렬 설정과 반대방향