Morpheus
Morpheus

Reputation: 1289

SwiftUI: Limit width of Text in VStack without making it grow unnecessarily

I want to limit the width of a Text to a given number. I thought that using frame(maxWidth:) would do the job, however it does not behave as expected at all. It seems that setting a max width makes the text grow to this size, whatever the size it should be.

Here's a code snippet:

    VStack(spacing: 20) {
        VStack {
            Text("Some Text")
                .frame(maxWidth: 200)
        }
        .background(Color.yellow)
        .border(.black, width: 1)

        VStack {
            Text("A longer text that should wrap to the next line.")
                .frame(maxWidth: 200)
        }
        .background(Color.yellow)
        .border(.black, width: 1)
    }

Expected result:

Expected result

Actual result:

Actual result

Upvotes: 4

Views: 1516

Answers (3)

Asperi
Asperi

Reputation: 257711

Assuming that we need a generic component which should work similarly with specified behavior independently of provided string, like

VStack(spacing: 20) {
   TextBox("Some Text")
       .background(Color.yellow)
       .border(.black, width: 1)

   TextBox("A longer text that should wrap to the next line.")
       .background(Color.yellow)
       .border(.black, width: 1)
}

then some run-time calculations required.

Here is a demo of possible solution (with real edge markers). Tested with Xcode 13.4 / iOS 15.5

demo

Main part:

struct TextBox: View {
    private let string: LocalizedStringKey
    private let maxWidth: CGFloat

    @State private var width: CGFloat

    init(_ text: LocalizedStringKey, maxWidth: CGFloat = 200) {  // << any default value
        // ... 
    }

    // ... 

        Text(string)
            .background(GeometryReader {
                Color.clear
                    .preference(key: ViewSideLengthKey.self, value: $0.frame(in: .local).size.width)
            })
            .onPreferenceChange(ViewSideLengthKey.self) {
                self.width = min($0, maxWidth)
            }
            .frame(maxWidth: width)

Test code on GitHub

Upvotes: 2

RelativeJoe
RelativeJoe

Reputation: 5084

The VStack adjusts its width to the max width of its elements. This is the native behavior of the VStack.

However, if you do the following you'll get the result you want:

VStack(spacing: 20) {
    VStack {
        Text("Some Text")
            .background(Color.yellow)
            .border(.black, width: 1)
    }.frame(maxWidth: 200)
    VStack {
        Text("A longer text that should wrap to the next line.")
            .frame(maxWidth: 200)
    }.background(Color.yellow)
        .border(.black, width: 1)
}

Upvotes: 1

Steven-Carrot
Steven-Carrot

Reputation: 3051

Generally, Text automatically grow/shrink by itself. To solve your problem, just set maxWidth to your VStack instead of Text to control your maxWidth, then add backgrounds modifiers to your text instead. Code is below the image: enter image description here

    VStack(spacing: 20) {
           VStack {
               Text("Some Text")
                   .background(Color.yellow)
                   .border(.black, width: 1)
           }
           .frame(maxWidth: 200)

           VStack {
               Text("A longer text that should wrap to the next line.")
               .background(Color.yellow)
               .border(.black, width: 1)
           }
           .frame(maxWidth: 200)
    }

Upvotes: 4

Related Questions