klioop for iOS

SwiftUI AlignmentGuide 2, Custom AlignmentGuide 본문

SwiftUI

SwiftUI AlignmentGuide 2, Custom AlignmentGuide

klioop2@gmail.com 2021. 5. 27. 16:59

이번 포스팅에서는 지난 포스팅(https://klioop.tistory.com/31)에 이어서 AlignmentGuide 에 대해 알아보겠습니다.

 

alignmentGuide 를 적용했을 때 Stack 안에서 뷰 들이 어떻게 정렬될지 예상이 되어야 

이번 포스팅을 이해하실 수 있습니다.

 

시작은 지난 번 포스팅과 동일한 예제로 가겠습니다.

 

struct DaysView: View {
    
    var body: some View {
        
        VStack {
            Day(label: "월요일")
            Day(label: "화요일")
            Day(label: "수요일")
            Day(label: "목요일")
            Day(label: "금요일")
            Day(label: "토요일")
            Day(label: "일요일")
            
        }
        .frame(width: 200)
        .padding(.vertical)
        .border(Color.gray)
    }
}

struct Day: View {
    
    let label: String
    
    var body: some View {
        
        Text(label)
            .padding(10)
            .background(RoundedRectangle(cornerRadius: 8)
                            .fill(Color.red.opacity(0.9))
            )
            .foregroundColor(.white)
        
    }
    
}

 

 

Custom AlignmentGuide 는 말 그대로 alignmentGuide 를 커스텀 해서 사용하는 것을 말합니다.

왜 커스텀을 만들어서 사용할까요

 

가장 주요한 목적은 Stack 이 다른 뷰들 사이에 정렬을 위해서 사용합니다.

 

먼저 커스텀을 위해서 어떤 과정이 필요한지 알아보겠습니다.

 

지난 번 포스팅을 보시면 아시겠지만, alignmentGuide 를 명시적으로 이용하려면 

Stack Alignment 와 Guide Alignment 가 같아야 합니다.

 

VStack(alignment: .leading) {
	...
    
    SomeView()
    	.alignmentGuide(.leading) { ... }
    
    ...
    }

 

그러면 .leading 처럼, 커스텀 alignment 나타낼 ID 가 필요할 것 같아요.

 

VStack alignment 의 경우 HorizontalAlignment 타입이므로 

이를 extension 해서 하나 만들면 되겠네요. 

코드를 작성하고 다시 볼게요.

 

extension HorizontalAlignment {
    
    enum MyCustomAlignment: AlignmentID {
        
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            d.height
        }
    }
    
    static let myCustomALignment = HorizontalAlignment(MyCustomAlignment.self)
    
}

 

MyCustomAlignment 라는 커스텀 정렬을 enum 으로 하나 만들었습니다.

얘는 AlignmentID 프로토콜을 따라야 합니다.

 

이 프로토콜은 defaultValue 함수를 하나 선언하기를 요구하는데요.

이름에서 추측 가능하듯이 기본 값을 정해주는 함수 입니다.

어떤 것의 기본 값 일까요

 

지난 번 포스팅에서도 언급했지만 알고있어야 하는 중요한 사실은 

Stack 안 모든 뷰들은 각자 명시적으로든, 암묵적으로든 alignmentGuide 를 갖는다는 것이었습니다.

 

커스텀 alignmentGuide 를 정의한다면 기본적으로 

alignmnetGuide 가 명시적으로 정의되지 않을 때 Stack 안 뷰들이 갖는 암묵적인 alignmentGuide 값을 

정해주어야 합니다.

 

따라서 defaultValue 함수는

이 값의 기본 값을 정해주는 함수입니다.

 

왜 struct이나 class 가 아니라 enum 으로 정의했냐면,

이 경우 객체(instance) 나 상속이 전혀 필요없기 때문입니다.

 

그리고 HorizontalAlignment 에 myCustomAlignment 속성을 하나 추가했습니다. 

HorizontalAlignment 공식문서를 그대로 따른거에요. 

 

 

방금 만든 커스텀 alignment 를 적용하면 뷰들이 재밌는 형태로 정렬될 것입니다.

기본 값으로 각 뷰의 높이에 해당하는 지점으로 정렬선이 지나가도록 해놓았기 때문인데요.

 

Day 뷰에 높이를 인자로 받도록 추가하고 커스텀 정렬을 적용해볼게요.

처음 코드와 달라집니다. VStack 의 frame 도 뻇습니다.

 

struct DaysView: View {
    
    var body: some View {
        
        VStack(alignment: .myCustomALignment, spacing: 5) {
            
            Rectangle()
                .fill(Color.primary)
                .frame(width: 1)
                .alignmentGuide(.myCustomALignment) { d in d[.leading]}
            
            Day(label: "월요일", height: 30)
            Day(label: "화요일", height: 40)
            Day(label: "수요일", height: 50)
            Day(label: "목요일", height: 60)
            Day(label: "금요일", height: 50)
            Day(label: "토요일", height: 40)
            Day(label: "일요일", height: 30)
            
            Rectangle()
                .fill(Color.primary)
                .frame(width: 1)
                .alignmentGuide(.myCustomALignment) { d in d[.leading]}
            
        }
        .padding()
        .border(Color.gray)
    }
}

struct Day: View {
    
    let label: String
    let height: CGFloat
    
    var body: some View {
        
        Text(label)
            .frame(height: height)
            .padding(.horizontal, 15)
            .background(RoundedRectangle(cornerRadius: 8)
                            .fill(Color.red.opacity(0.9))
            )
            .foregroundColor(.white)
    }    
}


extension HorizontalAlignment {
    
    enum MyCustomAlignment: AlignmentID {
        
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            d.height
        }
    }
    
    static let myCustomALignment = HorizontalAlignment(MyCustomAlignment.self)
    
}

https://swiftui-lab.com/alignment-guides 예시를 참고하고 조금 변형한 형태입니다.

 

 

Day 뷰들은 alignmentGuide 가 암묵적으로 적용되고 있는데요.

alignmentGuide 의 값으로 defaultValue 에서 정한 기본 값이 적용되고 있다는 말과 동일합니다.

기본 값은 각 뷰의 높이였습니다. 

그 말은 각 뷰의 시작점으로부터 그 높이만큼 떨어진 x 지점으로 수평정렬선이 통과한다는 의미입니다. 

 

 

 

커스텀 정렬을 어떻게 만드는 지 살펴보고 사용도 해봤는데요.

자, 그럼 커스텀 정렬이 어떤 상황에서 사용되는지 예시를 통해 살펴볼게요.

https://swiftui-lab.com/alignment-guides 에서 사용된 예시를 그대로 사용하겠습니다.

 

 

출처: https://swiftui-lab.com/alignment-guides  

 

위 구조를 보면 화살표 이미지와 텍스트는 서로 다른 Stack 에 살고 있습니다.

저는 텍스트를 선택했을 때 선택된 텍스트와 이미지가 수직 가운데 정렬되기를 원합니다.

그런데 VStack 의 정렬은 수평정렬 타입이잖아요? 

따라서 VStack 정렬로는 수직 정렬을 이용할 수가 없어요.

더욱이, HStack 에 있는 뷰와 VStack 에 있는 뷰를 정렬시키는 거라 기존의 주어진 정렬만으로는 방법이 없습니다.

 

여기서 커스텀 정렬이 문제를 해결할 수 있습니다!!

 

ImageTextAlignment 라는 커스텀 정렬을 만들어서 이미지와 선택된 텍스트가 수직 가운데 정렬 되도록 해보겠습니다.

alignmentGuide 가 명시되지 않는 뷰들은 각 Stack 의 정렬을 암묵적으로 따라 정렬될 것입니다.

바로 코드와 결과를 보겠습니다.

 

struct CustomAlignmentView: View {
    
    let days = ["월요일", "화요일", "수요일", "목요일", "금요일", "토요일", "일요일"]
    
    @State private var position = 1
    
    var body: some View {
        
        HStack(alignment: .imageTextAlignment ) {
            Image(systemName: "arrow.right.circle.fill")
                .foregroundColor(.red)
                .alignmentGuide(.imageTextAlignment) { d in
                    d[VerticalAlignment.center]
                }
            
            VStack {
                
                ForEach(days.indices, id: \.self) { idx in
                    
                    Group {
                        if idx == self.position {
                            Text(days[idx])
                                .alignmentGuide(.imageTextAlignment) { d in
                                    d[VerticalAlignment.center]
                                }
                        } else {
                            Text(days[idx])
                                .onTapGesture {
                                    withAnimation(.easeInOut) {
                                        self.position = idx
                                    }
                                }
                        }                                                
                    } // Group 끝
                    .padding(.vertical, 2)
                    
                }
            }
        } // HStack 끝
        .font(.largeTitle)
    }
}

extension VerticalAlignment {
    
    enum ImageTextAlignment: AlignmentID {
        
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            d[.bottom]
        }
    }
    
    static let imageTextAlignment = VerticalAlignment(ImageTextAlignment.self)
}

다시 말하지만, 이미지와 텍스트는 서로 다른 Stack 에서 존재합니다.

 

여기까지 AlignmentGuide 를 다뤄봤습니다. 

제가 다룬 내용들은 거의 전부 https://swiftui-lab.com/alignment-guides  여기를 참고했습니다.

더 자세한 내용들이 많으니 꼭 참고하시길 바라요.

 

다음 포스팅에서는 GeometryReader 를 정리하겠습니다!

끝!

 

'SwiftUI' 카테고리의 다른 글

SwiftUI AlignmentGuide 1  (1) 2021.05.27
SwiftUI 레이아웃 시스템 2, Stack  (0) 2021.05.22
SwiftUI, 좌표공간 각도방향  (0) 2021.05.21
SwiftUI layout 시스템 1, 레이아웃 원칙  (0) 2021.05.19
SwiftUI, 선언적 뷰의 의미  (0) 2021.05.17