SwiftUI 스택 정렬과 정렬 가이드

3 분 소요

컨테이너 정렬

  • 가장 기본적인 정렬 방법
  • 스택에 포함된 하위 뷰들이 스택 내에서 정렬되는 방식
  • 지정된 정렬이 따로 없다면, 스택에 적용된 정렬이 하위 뷰에 적용(암묵적 정렬(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")
        }
      
  • 수평 스택(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)
        }
      
  • 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)
}

스크린샷 2022-05-31 오전 10 56 19

  • 두 번째 뷰만 앞쪽에서 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)
    }
    

    스크린샷 2022-05-31 오전 10 59 14
    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)
    }
    

    스크린샷 2022-05-31 오전 11 20 13

  • ViewDimensions 객체는 뷰의 HorizontalAlignment와 VerticalAlignment 프로퍼티에 대한 접근을 제공
    .alignmentGuide(.leading, computeValue: {
      d in d[HorizontalAlignment.trailing] + 20
    })
    

    스크린샷 2022-05-31 오전 11 25 11


커스텀 정렬 타입

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)
}

스크린샷 2022-05-31 오전 11 48 09


스택 정렬 교차하기

표준 정렬의 단점: 스택 내의 뷰가 다른 스택에 있는 뷰와 정렬되도록 하는 방법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)
    }
}

스크린샷 2022-05-31 오후 12 42 06

원을 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)
    }
}

스크린샷 2022-05-31 오후 2 44 38


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)
        //오른쪽, 아래
}

스크린샷 2022-05-31 오후 2 40 42
[.top]은 [VerticalAlignment.top]의 생략
뷰의 위치는 뷰의 정렬 설정과 반대방향

카테고리:

업데이트: