티스토리 뷰

 

swiftUI에서 텍스트에 따라 너비와 높이가 동적으로 조절되어야 하는 경우가 많다.

그래서 이번엔 텍스트에 따라 동적으로 너비와 높이가 조절되는 UI를 구현해보고자 한다.

 

 


1. 간단한 UI 구현

 

 

동적 UI를 적용하기 전에 간단하게 UI를 구현해 보자.

 

struct ContentView: View {
    let content: String = "안녕하세요"
    let maxWidth: CGFloat = 151

    var body: some View {
        ZStack{
            Rectangle()
                .fill(.blue)
                .border(.black)
                .cornerRadius(6)

            Text(content)
                .font(.headline)
                .foregroundStyle(.white)
                .padding(.horizontal, 16)
                .padding(.vertical, 12)

        }
        .frame(minWidth: 0, maxWidth: maxWidth)
    }
}

 

maxWidth로 최대 너비를 정해놓고 minWidth를 0으로 설정했는데 텍스트에 딱 맞게 너비가 조절되지 않았다...

그리고 높이는 왜 저러지...? ㅎㅎㅎ

 

 


2. 높이 동적으로 조절하기 

 

높이를 동적으로 조절하기 위한 시도

 

방법 1: 최소/최대 높이를 지정하자

 

최소 높이: 0, 최대 높이: 151로 설정했으니 "안녕하세요" 문자에 맞추어지겠지..? 했는데 역시 안된다.

struct ContentView: View {
    let content: String = "안녕하세요"
    let maxWidth: CGFloat = 151
    let maxHeight: CGFloat = 151

    var body: some View {
        ZStack{
           ...
        }
        
        .frame(minWidth: 0, maxWidth: maxWidth)
        .frame(minHeight: 0, maxHeight: maxHeight)
    }
}

 

최소, 최대 높이를 정해도 높이가 최대에 맞혀져서 나오는 거다,,, 그리고 텍스트는 또 왜 중간에 있는 거야,,

왜 그럴까??

 

 

왜 높이가 maxHeight로 나오는가?

ZStack은 기본적으로 내부의 콘텐츠 크기에 맞게 동적으로 크기가 조정된다고 한다. 하지만 maxHeight를 명시하면, 내부 콘텐츠가 더 작더라도 높이가 maxHeight에 맞춰진다. 그래서 결국, Text(content)의 높이만큼만 공간을 차지해야 할 텍스트가 maxHeight 값으로 인해 더 큰 공간을 차지하게 된 것이다!!!!

 

어떻게 해결할지 찾아보다가 fixedSize를 알게 되었다.

 

fixedSize(horizontal:vertical:)는 SwiftUI에서 뷰의 크기를 고정하여 크기 조정 방식을 제어하는 모디파이어라고 한다.

=> 콘텐츠가 충분한 공간을 가질 수 있게 만들고, 해당 축에서 더 커지지 않도록 제한해 준다.

 

그럼 바로 사용해 보자~~

 

방법 2: fixedSize를 사용하자

 

struct ContentView: View {
    ...
    var body: some View {
        ZStack(alignment: .leading){
            ...
        }
        .frame(minWidth: 0, maxWidth: maxWidth)
        .frame(minHeight: 0, maxHeight: maxHeight)
        .fixedSize(horizontal: false, vertical: true)
    }
}

 

와~~ 드디어 잘 나온다 ㅎㅎㅎ

최대 높이까지 제한되어서 나오고 최대 높이를 벗어나는 경우 ...으로 텍스트가 더 이상 보이지 않는다~~

 

글자가 중앙에 나오던 문제도 ZStack(alignment: .leading)으로 왼쪽에 위치하도록 해서 해결했다. 

 

 

높이 문제는 해결되었지만 여전히 너비가 동적으로 조절되지 않는 문제가 해결되지 않았다,,, 

 

 

 


3. 너비 동적으로 조절하기 

 

너비를 동적으로 조절하기 위해 찾아보다가 알게 된 것이 있다! 

바로 GeometryReader

 

GeometryReader는 SwiftUI에서 부모 뷰의 사이즈나 위치와 같은 레이아웃 정보를 제공하는 뷰라고 한다.

이걸 사용해서 텍스트의 너비를 판단해 보자!!

 

 

GeometryReader | Apple Developer Documentation

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

developer.apple.com

 

1️⃣ WidthPreferenceKey 생성

 

텍스트 너비를 파악하기 위해 자식 뷰에서 상위 뷰로 값을 전달하는 데 사용하는 PreferenceKey를 사용했다.

 

struct TextWidthPreferenceKey: PreferenceKey {
    static var defaultValue: CGFloat = 0

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

 

TextWidthPreferenceKey의 역할:

 

  • 텍스트의 너비를 추적하여, 상위 뷰로 전달
  • reduce()로 여러 자식 뷰의 값이 전달되더라도 가장 큰 너비 값이 상위 뷰로 전달되도록 한다.

 

2️⃣ 텍스트 너비 측정

 

Text(content)의 너비를 측정하기 위해 GeometryReader를 사용한다.

 

 Text(content)
    .font(.headline)
    .foregroundStyle(.white)
    .padding(.horizontal, 16)
    .padding(.vertical, 12)
    .background(
        GeometryReader { geo in
            Color.clear.preference(key: TextWidthPreferenceKey.self, value: geo.size.width)
        }
    )

 

 

3️⃣ 상위뷰에 너비 적용

 

onPreferenceChange를 통해 TextWidthPreferenceKey에 변화가 생길 때마다 textWidth 상태 변수를 업데이트한다.

그리고 textWidth를 frame의 minWidth에 넣어서 최소 높이에 따라 바뀌도록 설정하였다.

 

ZStack(alignment: .leading) {
   ...
}
.onPreferenceChange(TextWidthPreferenceKey.self) { width in
    self.textWidth = width
}
.frame(minWidth: textWidth, maxWidth: maxWidth)

 

 

 

🔎 동작 과정

1. GeometryReader로 텍스트의 너비 측정: geo.size.width를 통해 텍스트 뷰의 너비를 가져온다. 이때 Color.clear는 실제 화면에 그려지지는 않지만, 레이아웃 계산을 위한 컨테이너로 사용된다.
2. preference(key:value:)로 값 전달: 텍스트 뷰의 너비를 TextWidthPreferenceKey라는 키와 함께 전달한다. 이 키를 통해 너비 값이 상위 뷰로 전달된다.
3. 상위 뷰에서 너비 값 사용: 상위 뷰(ZStack)는 onPreferenceChange로 이 값을 감지하고, 해당 값을 활용해 너비를 동적으로 변경할 수 있다.

 

GeometryReader로 텍스트 너비를 판단할 수 있다니,, 또 이렇게 배워간다 ㅎ

휴 드디어 너비가 동적으로 잘 나오겠지~~ 

 

 

결과 확인했는데.... 왜 안된 거지..?

침착하게 다시 코드를 확인해 보자.

 

Rectangle의 frame에 텍스트너비를 넣어주지 않았던 게 문제였다,, ㅎ

Rectangle()
    .fill(.blue)
    .border(.black)
    .cornerRadius(6)
    .frame(width: textWidth)

 

이렇게 하니까 해결~~~

 

 

 

  • 완성 코드
struct ContentView: View {
    let content: String = "안녕하세요"
    let maxWidth: CGFloat = 151
    let maxHeight: CGFloat = 151
    @State private var textWidth: CGFloat = .zero

    var body: some View {
        ZStack(alignment: .leading) {
            Rectangle()
                .fill(.blue)
                .border(.black)
                .cornerRadius(6)
                .frame(width: textWidth)
            
            Text(content)
                .font(.headline)
                .foregroundStyle(.white)
                .padding(.horizontal, 16)
                .padding(.vertical, 12)
                .background(
                    GeometryReader { geo in
                        Color.clear.preference(key: TextWidthPreferenceKey.self, value: geo.size.width)
                    }
                )
        }
        .onPreferenceChange(TextWidthPreferenceKey.self) { width in
            self.textWidth = width
        }
        .frame(minHeight: 0, maxHeight: maxHeight)
        .frame(minWidth: textWidth, maxWidth: maxWidth)
        .fixedSize(horizontal: false, vertical: true)
    }
}

 

 


4. GeometryReader와 fixedSize의 차이점

 

 

처음에 높이 동적으로 조절하는 거 구현할 때 fixedSize를 사용해서 해결했어서 가만히 생각하다 보니 너비도 GeometryReader를 사용하지 않아도 되겠는데..?라는 생각이 들었다.

 

그래서 GeometryReader 코드 다 지우고 fixedSize만 사용해서 horizontal과 vertical을 true로 주었더니 너비와 높이 모두 동적으로 잘 나오는 걸 확인했다.....

 

struct ContentView: View {
    let content: String = "안녕하세요"
    let maxWidth: CGFloat = 151
    let maxHeight: CGFloat = 151

    var body: some View {
        ZStack(alignment: .leading) {
           ...
        }
        .frame(minHeight: 0, maxHeight: maxHeight)
        .frame(minWidth: 0, maxWidth: maxWidth)
        .fixedSize(horizontal: true, vertical: true)
    }
}

 

그럼 대체 GeometryReader와 fixedSize의 차이는 뭘까?

 

언제 fixedSize 사용?

  • 단순한 크기 조정이 필요할 때: 예를 들어, 텍스트의 크기만큼 뷰의 크기를 고정하고 싶을 때 
  • 뷰 크기를 텍스트에 맞게 맞추고 싶을 때: 레이아웃 계산 없이 텍스트나 콘텐츠에 맞는 최소 크기를 유지하고 싶을 때.

 

언제 GeometryReader사용?

  • 동적 레이아웃 계산이 필요할 때: 뷰의 크기가 상위 뷰나 화면 크기에 따라 달라져야 하는 경우
  • 복잡한 레이아웃 조정: 뷰가 다른 뷰와 상호작용하여 동적으로 크기나 위치가 변경되어야 하는 경우, GeometryReader를 사용하면 보다 유연하게 레이아웃을 조정 가능
  • 뷰의 크기를 추적하거나 외부로 전달할 때: PreferenceKey와 함께 사용하여 자식 뷰의 레이아웃 정보를 상위 뷰로 전달하고, 그에 맞춰 뷰를 조정해야 하는 경우

 

결론

  • 간단한 경우: fixedSize는 단순한 크기 고정이 필요할 때 적합
  • 복잡한 경우: 레이아웃 계산이나 동적 크기 조정이 필요할 때는 GeometryReader가 적합. 예를 들어, 뷰 간의 복잡한 상호작용이 필요하거나, 뷰 크기를 추적하고 이를 다른 곳에 적용하고자 할 때 유용하다.

 

 

내가 생각하기에는 이 예제는 단순히 텍스트의 너비와 높이에 따라 뷰 크기를 조정하면 되기 때문에, fixedSize를 사용하는 것이 적합한 것 같다.

하지만, 다른 뷰와 상호작용이 필요하거나 뷰의 크기가 상위 뷰에 영향을 미치는 경우에는 GeometryReader를 사용하는 것이 더 유용할 것 같다.

 

두 가지 방법 다 알아두면 좋치~~

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함