ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [SwiftUI] - GeometryReader
    iOS/SWIFTUI 2024. 7. 26. 20:57
    728x90
    반응형

    오늘은 어려운 GeometryReader를 가져와봤다.

     

    솔직히 GeometryReader는 거의 모르는 상태로 왜 쓰는지도 모르고 사용해 왔던 것 같다. 하지만 이번 기회에 개념을 확실히 잡아 내 마음대로 자유자재로 사용하고자 하는 취지로 포스팅하게 되었다.

     

    GeometryReader

     

    OverView를 해석하자면 부모뷰의 레이아웃 정보를 불러오는 데 사용된다고 한다.

    GeometryReader는 GeometryProxy를 파아미터로 받는 클로져를 반환하는데, 여기서 GeometryProxy는 GeometryReader 내부에서 사용되는 뷰의 기하학적 정보를 포함하고 있다. 

     

    여기서 말하는 정보는 GeometryProxy의 attribute를 살펴보면 되는데,

     

    • size : 뷰의 크기 정보
    • safeAreaInsets : 뷰의 안전 영역 정보
    • frame(in:) : 지정된 좌표 공간에서의 뷰의 프레임

    전체적으로 GeometryReader로 읽어 들인 내용을 바탕으로 이벤트를 처리하거나 화면에 출력하기 위함으로 쓰이는 느낌이다.

    자세히 하나씩 알아보자.

     

    size & safeAreaInsets
    import SwiftUI
    
    struct GeomertyReaderView: View {
        var body: some View {
            GeometryReader{ geometry in
                VStack{
                    Text("\(geometry.safeAreaInsets)")
                    Text("\(geometry.size)")
                    
                }
            }
        }
    }
    
    #Preview {
        GeomertyReaderView()
    }

     

     

     

    위의 코드를 실행 시키면 이런 화면이 나온다.

     

    위에서도 말했듯이 Geometry는 상위뷰의 레이아웃 정보를 불러오는 놈이다.

     

    현재 상위뷰는 최상위 뷰임으로 이 기종으로 보여줄 수 있는 최대 값의 안전영역을 제외한 값이 size가 되는 것이다. (참고로 기종은 iPhone 15 pro max)

     

    size는 width, height를 각각 사용할 수 있다.

    geometry.size.width // 상위 뷰의 가로길이
    geometry.size.height // 상위 뷰의 세로길이

     

    safeAreaInsets 같은 경우는 안전 영역, 말 그대로 Safe Area를 뜻한다. 즉 이 영역을 무시하지 않는 한 상위 뷰 레이아웃에서 표시할 수 있는 전체 영역을 뜻한다.

    geometry.safeAreaInsets.top		// 최상단 으로 부터의 거리
    geometry.safeAreaInsets.bottom		//최히단 으로 부터의 거리
    geometry.safeAreaInsets.leading		//좌측 으로 부터의 거리
    geometry.safeAreaInsets.trailing 	//우측 으로 부터의 거리

     

    그래서 지금은 top - 59.0, bottom - 34.0이 띄워져 있지만 만약. ignoresSafeArea()을 해당 컨테이너에 적용할 경우

    이렇게 나온다. 안전영역을 무시했으니 자동으로 상위뷰의 시작점이 디바이스 기준으로 설정되어 height size값이 759 -> 852로 바뀐 것을 볼 수 있다.

     

    그럼 만약 

    var body: some View {
            ZStack{
                Color.yellow
                GeometryReader{ geometry in
                    VStack{
                        Text("\(geometry.safeAreaInsets)")
                        Text("\(geometry.size)")
                        GeometryReader{ proxy in
                            Color.cyan
                            VStack{
                                Text("\(proxy.safeAreaInsets)")
                                Text("\(proxy.size)")
                            }
                        }
                        
                    }
                }
            }
        }

     

    이렇게 GeometryReader안에 GeometryReader가 있으면 어떻게 될까?

    만약 부모 GeometryReader컨테이너를 A, 자식 GeometryReader 컨테이너를 B라고 한다면

    여기서 A,B의 상위뷰인 Zstack의 크기만큼의 size와 Safe Area가 각각 A, B 내부 Text들에 출력된다.

     

    frame(in:)

     

    frame같은 경우는 3가지 옵션이 각각 존재한다.

     

    • global : (기본적으로 Safe Area를 제외) 전체 스크린 전체에서의 좌표
    • local : 상위 컨테이너에서의 좌표
    • name : 이름을 붙인 컨테이너에서의 좌표

    말로 들으면 이해가 가지 않으니 예제로 살펴보자

    var body: some View {
            ZStack{
                VStack(spacing:0){
                    Circle()
                        .frame(width: 100,height: 100)
                    GeometryReader{ geometry in
                        let globalFrame = geometry.frame(in: .global)
                        let localFrame = geometry.frame(in: .local)
                        let nameFrame = geometry.frame(in: .named("GEO"))
                        VStack(alignment:.leading){
                            Text("글로벌 : \(globalFrame.midX) \(globalFrame.minY)")
                            Text("로컬 : \(localFrame.midX) \(localFrame.minY)")
                            Text("네임 : \(nameFrame.midX) \(nameFrame.minY)")
                            Text("디바이스 : \(mainWidth) \(mainHeight)")
                        }
                        //mainWidth = UIScreen.main.bounds.width
                        //mainWidth = UIScreen.main.bounds.height
                    }
                }
            }
            .coordinateSpace(name: "GEO")
        }

     

    하나씩 살펴보자

     

    global

     

    GeometryReader안에 존재한다 하더라도 전체 스크린(디바이스 크기) 기준으로부터의 좌표를 나타내고 싶을 때 이 옵션을 선택한다.

    눈치 빠른 사람들은 알겠지만 위에 코드와 결과에 따르면 safeArea top의 길이는 총 59였다.

    그리고 Circle의 frame 중 height의 길이는 100 

    그렇다면 전체 스크린에서 safe area 59 + Circle() height 100 = 총 159의 거리만큼이 현제 global 좌표값이다.

     

     

    local

     

    GeometryReader안을 기준으로 한 좌표를 나타내고 싶을 때 이 옵션을 선택한다.

    예제에서 컨테이너는 총 4개의 TextView를 포함하고 있고, 첫 번째 TextView의 시작 좌표가 바로 이 local 좌표값이다. 그러므로 당연히 0부터 시작.

    이 옵션의 좌표를 얻는 것은 GeometryReader를 사용하는 이유 중 가장 많은 이유일 것이고, 위의 내용을 이해했다면 당연한 내용임으로 그냥 넘어가겠다.

     

    name

     

    GeometryReader안에 존재한다 하더라도 특정 컨테이너를 정해서 그 컨테이너를 기준으로의 좌표를 나타내고 싶을 때 이 옵션을 선택한다.

    예제에서 .coordinateSpace(name: "GEO")라는 옵션은 최 상위뷰의 Zstack에 붙어있다.

    그럼 Zstack을 기준으로의 좌표가 되는 것이다.

     

    global 같은 경우 safe area를 포함한 좌표였지만, 기준점을 Zstack부터로 잡았기 때문에 그 컨테이너 안 Circle()의 height 즉, 100의 거리만큼이 현재 "GEO"라는 이름의 커스텀 옵션 좌표값인 것이다.

     

     

    생각보다 Geometry가 설명하기가 힘들었다. 예제를 직접 만들어보는 과정에서 착오가 많았지만, 이런 식으로 원리부터 이해하고 학습하면 자유자재로 사용할 수 있을 것 같다. 캐러셀이나 스크롤 애니메이션 등 Geometry는 활용도가 굉장히 높은 편이니 꼭 확실히 공부해보자

    'iOS > SWIFTUI' 카테고리의 다른 글

    [SwiftUI] - @ViewBuilder  (0) 2024.07.16
    [SwiftUI] #문법 - @ObservedObject  (0) 2023.04.14
    [SWIFTUI] #문법 - @Binding  (1) 2023.01.04
    [SWIFTUI] #문법 - @State  (0) 2022.10.09
Designed by Tistory.