반응형
LinkedIn 개발자로 성장하면서 남긴 발자취들을 확인하실 수 있습니다.
Github WWDC Student Challenge 및 Cherish, Tiramisul 등 개발한 앱들의 코드를 확인하실 수 있습니다.
개인 앱 : Cherish 내 마음을 들여다보는 시간, 체리시는 디자이너와 PM과 함께 진행 중인 1인 개발 프로젝트입니다.
10년 후, 20년 후 나는 어떤 스토리 텔러가 되어 있을지 궁금하다. 내가 만약에 아직 조금 더 탐구하고 싶은 게 있고, 궁금한 게 있다면, 그게 설사 지금 당장의 내 인생에 도움이 안 되는 것 같더라도 경험해보자. 그 경험들을 온전히 즐기며 내 것으로 만들고, 내 일에 녹여내고... 그러다보면 그 점들이 모여 나란 사람을 그려내는 선이 될 테니까.

Recent Posts
Recent Comments
Total
관리 메뉴

꿈꾸는리버리

[SwiftUI] GeometryReader 뽀개기 본문

오뚝이 개발자/SwiftUI

[SwiftUI] GeometryReader 뽀개기

rriver2 2024. 4. 21. 15:33
반응형

GeometryReader를 계속 제대로 공부해야지.. 해야지... 하다가 이번 기회에 확.. 제대로 개념을 잡고 넘어가기 위해 공부를 했다.

 

 🌷 GeometryReader 란 ? 

 

어려운 말들을 많이 써놨지만,,

내가 공부하면서는 GeometryReader란 GeometryReader로 감싸고 있는 뷰 안의 위치를 다양한 방법으로 제공해주는 View라고 정의를 했다.

 

🌷 GeometryReader가 나오게 된 배경 

이거는 애플의 공식 이야기는 아니고, 여러 칼럼들을 찾아보고 정리한 개인적인.. 의견입니다. 

왜 필요한지를 알아야 적재적소할 수 있을 것 같아서 정리해봅니다. 인게이지에 도움이 되시길!

 

1️⃣ UIScreen.main.bounds의 한계

UIScreen.main.bounds 같은 경우에는 기기의 가로, 세로 크기를 접근할 수 있는 요소이다.

struct GeometryReaderView: View {
    var body: some View {
        HStack {
            Rectangle()
                .fill(.red)
                .frame(width: UIScreen.main.bounds.width * 0.5)
            
            Rectangle()
                .fill(.green)
        }
    }
}

 

 

UIScreen.main.bounds.width를 사용한 위 코드를 세로, 가로 모드로 캡쳐한 화면이다.

UIScreen.main.bounds.width의 반을 빨간색으로 칠했기 때문에 가로 모드를 해도, 세로 화면을 기준으로 빨간색이 칠해진 것을 확인할 수 있다. 이를 해결하기 위해서는 디바이스가 가로 모드이면, UIScreen.main.bounds.height을 기준으로 0.5를 하면 되겠지만, 개발자들은, "지금 현재 화면을 기준으로 값을 사용하고 싶어!" 라는 needs로 인해 GeometryReader가 나온 것 같다.

 

다음은 GeometryReader를 쓴 뷰

struct GeometryReaderView: View {
    var body: some View {
        GeometryReader { proxy in
            HStack {
                Rectangle()
                    .fill(.red)
                    .frame(width: proxy.size.width * 0.5)
                
                Rectangle()
                    .fill(.green)
            }
        }
    }
}

 

2️⃣  현재 View의 위치를 알고 싶어

struct GeometryReaderView: View {
    var body: some View {
        ScrollView {
            ForEach([Color.red, Color.blue, Color.green, Color.yellow], id: \.self) { color in
                GeometryReader { gr in
                    RoundedRectangle(cornerRadius: 20)
                        .fill(color)
                        .overlay(
                            VStack {
                                Text("X: \(Int(gr.frame(in: .global).origin.x))")
                                Text("Y: \(Int(gr.frame(in: .global).origin.y))")
                        })
                }.frame(height: 400)
            }
        }
    }
}

 

위와 같이 스크롤을 올리거나 내릴 때마다 Y의 값이 변하는 것을 알 수 있다. 해당 요소가 View의 어느 위치에 있는지 알고 싶을 때 사용할 수 있는 !! 이거보면 되게 GeometryReader가 다양한 부분에서 활용될 수 있음을 직감할 수 있다.

 

🌷 GeometryReader 널 더 알고 싶어. 

 

 

1️⃣ GeometryReader의 기본

GeometryReader의 자식뷰들은 Zstack 처럼 .topLeading에 위치해서 배치가 된다.

또한 따로 설정을 하지 않아도 전체 View를 감싸는 것을 알 수 있다. ( width와 height가 .infinite 임)

 

⚠️ Zstack 대신 GeometryReader을 사용하는 게 좋나요?

No!! 왜냐면 GeometryReader는 계속 View의 크기 및 위치를 트래킹하기 때문에 정말 필요할 때만 사용하는 것이 권유된다. 따라서 Zstack이나 UIScreen.main.bounds를 사용해도 무방하다면, GeometryReader를 사용하지 않는 것이 좋다.

그리고 Zstack처럼 alignment 기능을 제안하지 않기 때문에, 불편할.. 거에요..(?)

 

2️⃣ GeometryProxy를 사용하는 방법

 

GeometryProxy는 GeometryReader의 크기와 좌표 공간을 표현한 것으로, 다음과 같은 접근이 가능하다. 

size, safeAreaInsets, frame, bounds 하나하나를 알아봅세다 ~

 

🌟 size 

 

아래는 위의 코드를 실행한 결과이다. 

 

geometryProxy.size.width / .height 을 하게 되면, 해당 GeometryReader가 감싸고 있는 View의 가로와 세로 크기를 알 수 있다. 앞서 언급한 대로 GeometryReader는 기본적으로 width와 height가 .infinite 임을 기억하자. 따라서 첫번째 빨간 View의 가로는 393, 세로는 459임을 알 수 있고, 노란 view의 경우에는 Frame의 크기로 GeometryReader의 크기를 제한했기 때문에 가로 세로 모두 300이다.

 

처음에는 그래서 이 휴대폰의 가로는 393, 세로는 459+300 = 759라고 생각했다. 하지만...!

 

웬걸,,,?

Text("기기의 Width: \(UIScreen.main.bounds.width)")
Text("기기의 Height: \(UIScreen.main.bounds.height)")

 

예상을 빗나갔다. 왜 Height이... 852이지..?

 

🌟 safeAreaInsets

struct GeometryReaderView: View {
    var body: some View {
        VStack(spacing: 0) {
            GeometryReader { geometryProxy in
                VStack(spacing: 10) {
                    Text("Width: \(geometryProxy.size.width)")
                    Text("Height: \(geometryProxy.size.height)\n")
                    
                    Text("기기의 Width: \(UIScreen.main.bounds.width)")
                    Text("기기의 Height: \(UIScreen.main.bounds.height)\n")
                    
                    
                    Text("세이프 leading: \(geometryProxy.safeAreaInsets.leading)")
                    Text("세이프 trailing: \(geometryProxy.safeAreaInsets.trailing)")
                    Text("세이프 top: \(geometryProxy.safeAreaInsets.top)")
                    Text("세이프 bottom: \(geometryProxy.safeAreaInsets.bottom)\n")
                }
                .padding()
                .foregroundStyle(.white)
                .background(Color.blue)
            }
            .background(Color.pink)
        }
    }
}

 

 

바로 SafeArea의 값이 누락되었던 것이다...!

geometryProxy의 세로와 SafeArea top, bottom의 값을 더하면, 기기의 세로 값이 제대로 나온다.

 

 

만약 가로모드로 전환을 하게 된다면, 다음과 같이 SafeArea 값이 바뀌게 된다.

 

🌟 frame

frame에는 .local과 .global, .named 이렇게 3가지 종류가 있다.

 

우선 .local과 .global를 비교해보자.

1️⃣ .local

 

local의 경우에는 시작점이 "Current View"이다. 따라서 해당 geometryReader의 최상위 Viewleading x 좌표가 시작점이 된다. 즉, 아래 코드에서는 blue 박스의 leading의 x좌표가 시작점이 된다. blue 박스의 가로는 313이기 때문에 local의 minX는 0, midX는 313/2, maxX는 313이 된다.

geometryProxy.frame(in: CoordinateSpace.local).origin.x의 경우에 minX와 값이 같다.

2️⃣ .global

한변 global의 경우에는 시작점이 "Root View"이기 때문에, 위의 그림 같은 경우에 minX는 40이 된다.

 

 

✅ 비슷한 코드의 Y 버전이다.

이 경우는 SafeArea를 감안해서 보면, X와 같은 맥락임을 확인할 수 있다.

(SafeArea top: 59, bottom: 34)

 

 

✅ 퀴즈...

아래 View에서 흰 Box안에 들어갈.. 숫자를 맞춰보시오...

struct GeometryReaderView: View {
    var body: some View {
        HStack(spacing: 0) {
            Rectangle()
                .fill(.cyan)
                .frame(width: 30)
            GeometryReader { geometryProxy in
                VStack(spacing: 10) {
                    VStack(spacing: 10) {
                        Text("local")
                            .font(.largeTitle)
                        Text("X: \(geometryProxy.frame(in: .local).origin.x)")
                        HStack(spacing: 0) {
                            Text("minX: \(Int(geometryProxy.frame(in: .local).minX))")
                            Spacer()
                            Text("midX: \(Int(geometryProxy.frame(in: .local).midX))")
                            Spacer()
                            Text("maxX: \(Int(geometryProxy.frame(in: .local).maxX))")
                        }
                    }
                    .padding()
                    .frame(width: 300)
                    .foregroundStyle(.white)
                    .background(Color.blue)
                    
                    VStack(spacing: 10) {
                        Text("global")
                            .font(.largeTitle)
                        Text("X: \(geometryProxy.frame(in: .global).origin.x)")
                        HStack(spacing: 0) {
                            Text("minX: \(Int(geometryProxy.frame(in: .global).minX))")
                            Spacer()
                            Text("midX: \(Int(geometryProxy.frame(in: .global).midX))")
                            Spacer()
                            Text("maxX: \(Int(geometryProxy.frame(in: .global).maxX))")
                        }
                    }
                    .padding()
                    .frame(width: 200)
                    .foregroundStyle(.white)
                    .background(Color.blue)
                }
                .background(Color.yellow)
            }
            .padding(.vertical, 40)
            .padding(.horizontal, 40)
            .background(Color.pink)
        }
    }
}

정답: 0, 0, 141, 283, 70, 70, 211, 353

 

3️⃣ .named

이거는 잘 정리해두신 블로그가 있어서 링크를 달아두겠습니다~

 


 

GeometryReader 공부 완료 !! 드디어... Chips... 구현하러 갑니다. 낄낄


출처

https://developer.apple.com/documentation/swiftui/geometryreader

 

GeometryReader | Apple Developer Documentation

A container view that defines its content as a function of its own size and coordinate space.

developer.apple.com

 

반응형
Comments