문제상황

struct BlueRectangle: View {
    @State var blueRectSize = CGSize(width: 20, height: 40)

    var body: some View {
        VStack {
            Rectangle()
                .frame(width: blueRectSize.width, height: blueRectSize.height)
                .foregroundStyle(.blue)

            BlackRectangle()
                .frame(height: 60)
        }
    }
}
struct BlackRectangle: View {
    var body: some View {
        HStack {
            Rectangle()
            Rectangle()
            Rectangle()
            Rectangle()
        }
    }
}

BlueRectangle 1개와 BlackRectangle 3개로 구성되어있다.

여기서 BlackRectangle의 사이즈는 동적으로 결정되었다.

이 상황에서 BlueRectangle 객체의 사이즈를 BlackRectangle 한 개의 사이즈과 똑같이 만들고 싶다.

따라서 이 장에서는 다음을 해결해야한다.

  • BlackRectangle 한 개의 사이즈를 구한다.
  • 구한 사이즈를 상위뷰로 전달한다.

BlackRectangle Size 구하기

GeometryReader를 이용해서 구할 수 있다.

뷰를 GeometryReader로 감싸면 GeometryProxy 타입의 변수를 통해 Size를 알 수 있다.

ForEach structure에 감싸져 있던 Rectangle을 GeometryReader로 감싸서,

overlay로 width값을 확인해 보자.

GeometryReader { proxy in
    Rectangle()
        .overlay {
            Text("\(proxy.size.width)")
                .foregroundStyle(.white)
        }  
}

 

이제 오른쪽 화면을 보면 BlackRectangle이 101.5의 width값을 가짐을 확인했다.

즉, GeometryReader를 통해 뷰의 Size를 구할 수 있음을 알았다.

이제 사이즈를 값을 분리된 상위뷰인 BlueRectangle에 전달해보자.

상위뷰로 데이터 전달하기

PreferenceKey 프로토콜를 통해 데이터를 상위뷰로 전달할 수 있다.

먼저, PreferenceKey를 준수하는 struct를 하나 정의한다.

struct GeometryPrefereneKey: PreferenceKey {
    typealias Value = CGSize

    static var defaultValue: CGSize = .zero

    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}

preference modifier를 통해 데이터를 송신시키자.

GeometryReader { proxy in
      Rectangle()
          .overlay {
              Text("\(proxy.size.width)")
                   .foregroundStyle(.white)
          }
          .preference(key: GeometryPrefereneKey.self, value: proxy.size)
}

이제 onPreferenceChange modifier를 통해 데이터를 수신할 수 있다.

onPreferenceChange는 blueRectangle 변수에 값을 넣을 수 있는 뷰에 적당히 넣자. 나는 VStack을 선택했다.

그리고 수신된 데이터를 blueRectSize에 저장하면 문제가 해결된다.

(추가로 blueRectangle의 width값의 확인을 위해 overlay로 화면에 width를 렌더링했다)

VStack {
       Rectangle()
            .frame(width: blueRectSize.width, height: blueRectSize.height)
            .foregroundStyle(.blue)
            .overlay {
                Text("\(blueRectSize.width)")
            }

       BlackRectangle()
           .frame(height: 60)
}
.onPreferenceChange(GeometryPrefereneKey.self) { blackRectangleSize in
    blueRectSize = blackRectangleSize
}

대충 순서를 보면 다음과 같다 :

GeometryReader를 통해 Size 찾기

-> BlackRectangle에서 데이터 송신

-> BlueRectangle에서 데이터 수신

-> 수신된 데이터를 blueRectSize에 저장

-> @State에 의해 뷰 reRendering

-> blueRectangle의 사이즈 변경됨

'SwiftUI' 카테고리의 다른 글

[SwiftUI] ForEach and ID  (0) 2023.11.06

+ Recent posts