SwiftUI

AlignmentGuide

Phililip
728x90

안녕하세요.

 

이번에는 alignmentGuide에 대해 알아볼게요.

 

 


 

# 1. alignmentGuide 란?

alignmentGuide는 쉽게 말해서, 뷰를 정렬하는데 기준이 되는 가이드라인을 말해요.

 

이게 뭔 말이냐...

 

 

만약에 VStack에서 leading alignment를 준다면,

 

VStack(alignment: .leading) {
    Text("Hello World").font(.title)
    Text("My name is Philip.").font(.title)
}

 

이렇게 보이겠죠??

 

 

근데 이게 사실은 alignmentGuide라는 가이드라인이 있고, alignmentGuide를 기준으로 뷰를 배치하는 것이에요.

(leading alignment이니 뷰의 왼쪽 부분이 alignmentGuide에 붙게끔 뷰를 배치하는 것이죠)

 

이번에는 HStack에 top alignment를 줘볼까요???

 

HStack(alignment: .top) {
    Text("Hello World").font(.largeTitle)
    Text("My name is Philip.")
}

 

 

top alignment를 주니 뷰가 alignmentGuide 아래쪽에 오게끔 배치되었어요.

 

 

 

 

 

 

 

근데 만약, "leading alignment를 줄 건데.... A라는 뷰는 왼쪽 정렬 후에 100만큼 더 왼쪽으로 들어갔으면 좋겠어" 같은 규칙을 주고 싶을 땐 어떻게 할까요??

 

이럴 때 필요한게 alignmentGuide(_:computeValue:) method입니다.

 

 
 

# 2. alignmentGuide(_:computeValue:) method

vertical alignment 설정을 할 수 있는 alignmentGuide method

 

 

 

horizontal alignment 설정을 할 수 있는 alignmentGuide method가 있어요.

 

 

(두 method 모두 개념은 동일하니 horizontal을 기준으로 설명하겠습니다.)

 

 

우선 파라미터 먼저 볼게요.

 

g : A HorizontalAlignment(또는 VerticalAlignment) value at which to base the offset.

computeValue : A closure that returns the offset value to apply to this view.

 

 

g는 쉽게 말해서, 정렬 기준을 말해요. (leading, trailing, top, bottom)

 

그리고 computeValue는 그 정렬 기준으로부터 얼마만큼의 offset을 이동시키느냐를 의미합니다. offset은 상수값을 넘겨도 되고, ViewDimensions 인수를 클로저에 사용해서 리턴값을 계산할 수도 있습니다.

 

 

 

 

위에서 언급한 "leading alignment를 줄 건데.... A라는 뷰는 왼쪽 정렬 후에 100만큼 더 왼쪽으로 들어갔으면 좋겠어"를 구현해볼게요.

 

우선 VStack에 leading alignment를 줍니다.

 

VStack(alignment: .leading) {
    Text("Hello World").font(.title)
    Text("My name is Philip.").font(.title)
}

 

그리고 아래쪽 뷰가 왼쪽으로 100만큼 더 들어가도록 alignmentGuide(_:computeValue:) method를 사용합니다.

 

 

 

VStack(alignment: .leading) {
    Text("Hello World").font(.title)
    Text("My name is Philip.").font(.title)
        .alignmentGuide(.leading) { _ in
            100
        }
}

 

 

자.. 이게 무슨 의미인지 하나씩 살펴볼게요.

 

 

저희는 VStack에 leading alignment를 줬죠??

 

그럼 하위 뷰들도 leading alignment의 규칙이 적용됩니다. (alignmentGuide 왼쪽에 뷰 배치)

 

하지만 아래쪽 뷰("My name is Philip.")에는 alignmentGuide 수식어를 사용했죠?

 

그럼 기본 alignment 규칙이 아닌 클로저를 규칙으로 삼아서 뷰를 배치합니다.

 

Text("My name is Philip.").font(.title)
    // leading alignment 규칙을 바꿀거야.
    // 규칙은 클로저를 보면 됨.
    .alignmentGuide(.leading) { _ in
        // 100만큼 들어가게 뷰를 배치할거야.
        100
    }

 

 

감이 좀 잡히시나요???

 

 

 

또 다른 예시를 볼게요.

 

만약 아래처럼 배치하고 싶다면 

(뷰의 trailing 쪽이 alignmentGuide에 위치하게끔 배치되었죠?)

 

 

 

클로저 안에 ViewDimension 인자를 사용해서 trailing을 넘겨주면 됩니다.

 

VStack(alignment: .leading) {
    Text("Hello World").font(.title)
    Text("My name is Philip.").font(.title)
        .alignmentGuide(.leading) { d in
            d[.trailing]
        }
    Text("Good").font(.title)
}

 

 

 

 

# 3. offset 하고 차이점

그럼 이쯤에서, 

 

굳이 alignmentGuide 써야 함?? offset으로 적당히 뷰를 옮기면 안 됨??

 

이라고 생각이 들 수도 있어요.

 

언뜻 보면 똑같아보일 수 있지만, offset과 alignmentGuide는 뷰의 영역에서 큰 차이를 보입니다.

 

 

 

alignmentGuide는 뷰 자체를 옮겨서 화면에 보여주기 때문에, 현재 화면에 보이는 뷰가 실제 뷰의 영역이 됩니다.

 

하지만, offset은 뷰 영역을 잡은 뒤에 offset 만큼 이동시켜서 화면에 보여주기 때문에, 현재 화면에 보이는 뷰가 실제 뷰의 영역이 아닙니다.

 

 

 

즉, 아래 같은 차이를 보이는 것이죠

 

 

(전체 코드입니다.)

VStack(spacing: 20) {
    VStack(spacing: 10) {
        Text("alignmentGuide")
        VStack(alignment: .leading) {
            Text("Hello World").font(.title)
            Text("My name is Philip.").font(.title)
                .alignmentGuide(.leading) { _ in
                    100
                }
        }
        .background(.yellow.opacity(0.4))
    }

    VStack {
        Text("offset")
        VStack(alignment: .leading) {
            Text("Hello World").font(.title)
            Text("My name is Philip.").font(.title)
                .offset(x: -100)
        }
        .background(.yellow.opacity(0.4))
    }

}
.border(.gray)

 

 

alignmentGuide와 offset의 차이를 알고 적절히 사용하면 됩니다ㅎㅎㅎ

 

 

 

 

# 4. Custom alignmentGuide

custom alignmentGuide를 만들기 전에 custom alignmentGuide가 왜 필요한 지부터 생각해볼게요.

 

 

예를 들어서

 

 

 

A라는 뷰와 B라는 뷰를 기준으로 가운데 정렬을 하고 싶은 경우라면,

 

A, B 뷰는 VStack이라는 컨테이너에 속해 있긴 하지만,

 

A는 VStack, B는 HStack에 속해있습니다.

 

즉, A와 B의 정렬 기준이 다르다는 의미이고, 정렬 기준이 다르기 때문에 A와 B를 기준으로 가운데 정렬할 수가 없는 것이죠.

 

 

이럴 때 alignmentGuide를 커스텀해서 사용하는 것입니다ㅎㅎㅎ

 

 

 

 

 

직접 구현해보면서 설명할게요.

 

 

우선, 기본 뼈대부터 만들겠습니다.

 

VStack {
    Text("Timer")		// A
        .font(.title)

    HStack(spacing: 16) {
        Text("13:18:14.813")	// B
            .font(Font.system(.body, design: .monospaced))
            
        Button(role: .destructive) {

        } label: {
            Image(systemName: "gobackward")
                .imageScale(.large)
        }
        .buttonStyle(.borderedProminent)
    }
}

 

center alignment를 줬는데 저희가 기대한 것이 아닌, 위쪽 뷰와 HStack이 서로 가운데 정렬이 되었습니다.

(당연한 얘기겠죠??ㅎㅎ)

 

 

 

 

 

HorizontalAlignment를 extension 해서 customCenter라는 custom alignment를 만들어줄게요.

(A, B 뷰를 수평 방향으로 이동시켜서 정렬시켜야 하기 때문에 HorizontalAlignment를 extension 시켜줬습니다. 만약, 수직 방향으로 이동시켜야 한다면 VerticalAlignment를 extension 시켜야겠죠??)

 

customCenter의 기본 규칙은 HorizontalAlignment.center입니다.

 

extension HorizontalAlignment {
    private struct CustomCenter: AlignmentID {
        static func defaultValue(in context: ViewDimensions) -> CGFloat {
            context[HorizontalAlignment.center]		// 기본 규칙
        }
    }
    static let customCenter = HorizontalAlignment(CustomCenter.self)
}

 

 

A, B를 모두 포함하는 부모 뷰인 VStack에 customCenter alignment 설정을 줍니다.

 

VStack(alignment: .customCenter) { ✅
    // ...
}

 

A 뷰는 customCenter의 기본 규칙인 HorizontalAlignment.center를 따르게 됩니다.

 

 

 

그 다음엔 B 뷰한테 alignmentGuide 수식어를 사용해서, customCenter alignment 일 때 B 뷰가 가운데에 올 수 있도록 설정해줄게요.

 

VStack(alignment: .customCenter) {
    Text("Timer")
        .font(.title)

    HStack(spacing: 16) {
        Text("13:18:14.813")
            .font(Font.system(.body, design: .monospaced))
            .alignmentGuide(.customCenter) {	✅
                $0[HorizontalAlignment.center]
            }
        Button(role: .destructive) {

        } label: {
            Image(systemName: "gobackward")
                .imageScale(.large)
        }
        .buttonStyle(.borderedProminent)
    }
}

 

 

옆 버튼은 HStack에 의해 B 뷰가 A 뷰에 맞춰서 가운데 정렬이 된 후에 B 뷰 옆에 위치하게 됩니다.

 

 

 

 

(아래는 전체 코드입니다.)

 

extension HorizontalAlignment {
    private struct CustomCenter: AlignmentID {
        static func defaultValue(in context: ViewDimensions) -> CGFloat {
            context[HorizontalAlignment.center]
        }
    }
    static let customCenter = HorizontalAlignment(CustomCenter.self)
}


struct ContentView: View {
    var body: some View {
        VStack(alignment: .customCenter) {
            Text("Timer")
                .font(.title)

            HStack(spacing: 16) {
                Text("13:18:14.813")
                    .font(Font.system(.body, design: .monospaced))
                    .alignmentGuide(.customCenter) {
                        $0[HorizontalAlignment.center]
                    }
                Button(role: .destructive) {

                } label: {
                    Image(systemName: "gobackward")
                        .imageScale(.large)
                }
                .buttonStyle(.borderedProminent)
            }
        }
    }
}

 

 

 

## 참고

- https://developer.apple.com/documentation/swiftui/aligning-views-within-a-stack

- https://developer.apple.com/documentation/swiftui/view/alignmentguide(_:computevalue:)-7g4ky

- https://developer.apple.com/documentation/swiftui/view/alignmentguide(_:computevalue:)-330fz

- https://developer.apple.com/documentation/swiftui/aligning-views-across-stacks

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

- https://www.hackingwithswift.com/books/ios-swiftui/alignment-and-alignment-guides

- https://useyourloaf.com/blog/swiftui-stack-custom-center-alignment/

 

 


 

 

이번 글은 여기서 마무리.

 

 

 

반응형

'SwiftUI' 카테고리의 다른 글

animatableData  (0) 2022.03.03
renderingMode(_:)  (0) 2022.03.01
스크린샷 주의 팝업, 화면 녹화 방지  (3) 2022.02.25
SwiftUI Markdown 사용에 대한 3가지 특이점  (0) 2022.02.24
addArc(tangent1End:tangent2End:radius:)  (0) 2022.02.22