iOS 개발 기록

SwiftUI - View의 크기를 구하는 방법 본문

SwiftUI

SwiftUI - View의 크기를 구하는 방법

택꽁이 2022. 7. 7. 18:50
728x90

 

구현해야 하는 화면 예

앱을 만들면서 Text가 개행되는 것에 따라 라인을 그려줘야하는 View를 구현해야 했다.

Text 한 줄의 view의 높이를 구해서 전체 View를 나누면 라인의 수가 나올거라 생각해 그 방법으로 구현하려고 했다.

그런데 SwiftUI에서는 View의 크기를 구하는게 UIKit에 비해 까다로웠다. 

여기서는 SwiftUI에서 View의 크기를 구하는 두가지 방법을 소개한다.

사실 두개 다 아이디어는 똑같기는 하다.

구하고자 하는 뷰와 똑같은 크기의 하위뷰를 만들어의 하위뷰의 크기를 전달하는 것이다.

 

 

 

 

1. PreferenceKey

Preference 는 Key-Value 로직으로 하위 뷰의 정보를 상위 뷰에 전달할 수 있다.

이를 위해 PreferenceKey 프로토콜을 따르는 Key를 정의해주어야 한다. 

PreferenceKey를 통해 단순히 뷰의 크기 뿐만 아니라 ScrollView에서의 스크롤 위치 등 뷰와 관련된 정보들을 얻을 수 있다. 

struct ViewSizeKey: PreferenceKey {
    static var defaultValue = CGSize()
    //reduce 메소드는 ViewSizeKey를 사용하는 뷰들의 값을 취합하는 역할을 한다.
    static func reduce(value: inout CGSize, nextValue: () -> Value) {
        value = nextValue()
    }
}

 

 

사용

struct ContentView: View {
    var body: some View {
        ScrollView(.vertical) {
            ForEach(0..<100) { num in
                HStack {
                    Text("\(num)")
                }
            }///ForEach
           .background(
                GeometryReader { geo in           
                	// 동일한 크기의 투병한 배경을 깔고 Geometry로 구한 View의 크기를 ViewSizeKey로 전달한다.
                    Color.clear.preference(key: ViewSizeKey.self, value: geo.frame(in: .local).size)
                }
                .onPreferenceChange(ViewSizeKey.self) {
                    print(geo)
                })
    }///body
}

 

구하고자 하는 상위 뷰(ScrollView)의 배경으로 투명한 배경(하위뷰)을 깔고, 배경의 Preference를 Key에 전달한다. 

SwiftUI에서는 Color 또한 View 프로토콜을 따른다. 

 

 

위의 코드로 그린 화면과 크기값

 

 

 

 

 

 

2. Combine

역시나 투명한 하위 뷰를 만들어서 Geometry로 크기를 구한다는 아이디어는 동일하다. 

여기서는 View의 크기를 저장하기 위한 @State 변수 하나를 만들어 초기값이 변할때 Combine으로 감지해 값을 출력한다. 

 

import SwiftUI
import Combine		

struct ContentView: View {
    ///사용하는 메인뷰의 높이를 구하기위해 사용하는 변수
    @State var writeViewSize : CGSize = .zero

    var body: some View {
        ScrollView(.vertical) {
            ForEach(0..<100) { num in
                HStack {
                    Text("\(num)")
                }
            }///ForEach
            .background(
                GeometryReader { geo in
                    Color.clear
                         .onAppear() {			// 초기에 뷰가 생성될 때의 값
                            self.writeViewSize = proxy.size     
                            print(self.writeViewSize)
                        }
                        .valueChanged(value: proxy.size) { value  in       // 뷰의 크기가 변한다면 이를 받을 값
                            self.writeViewSize = proxy.size
                            print(self.writeViewSize)
                        }
                })
        }///scrollView
    }/// body
}



/// View Extension
extension View {
    @ViewBuilder func valueChanged<T: Equatable>(value: T, onChange: @escaping (T) -> Void) -> some View {
        if #available(iOS 14.0, *) {
            self.onChange(of: value, perform: onChange)
        } else {
            self.onReceive(Just(value)) { (value) in
                onChange(value)
            }
        }
    }
}

 

 

 

combine으로 구한 View의 크기

 

 

 

 

결론 

결국에는 같은 아이디어를 PreferenceKey로 구현하냐, Combine으로 구현하냐의 차이인 것 같다. 

개인적으로는 첫번째 방법이 더 간편한 것 같다.  

 

 

 

 

 

참고

https://stackoverflow.com/questions/66485411/dynamic-texteditor-overlapping-with-other-views

 

Dynamic TextEditor overlapping with other views

So I have made a successful dynamic TextEditor but when I try to combine views, it overlaps the other elements and no longer expands as you type. I am pretty sure this has to do with Geometry Reade...

stackoverflow.com

 

'SwiftUI' 카테고리의 다른 글

[iOS] WidgetKit  (0) 2023.02.06
[iOS] swiftUI에서 UIKit 사용하기  (0) 2023.02.06
SwiftUI - Realm  (0) 2022.04.26
SwiftUI - 애니메이션  (0) 2022.04.21
SwiftUI의 4가지 원칙  (0) 2022.04.13