2

Imagine something like a navigation bar — there is an item on the left and in the center. I can create this layout with SwiftUI. (A ZStack is one way.)

But if the text is too long, I want to avoid overlap by shifting the center item over. I can do this with autolayout by constraining the center item to the center of its parent with one priority, and then constraining them not to overlap with a higher priority.

I keep wishing I had something like constraints in SwiftUI.

Any way to do this layout in SwiftUI?

enter image description here

2
  • If you downvote the question, comment and tell me why. Commented Apr 19, 2020 at 2:06
  • I have attempted a number of pure SwiftUI solutions that all do not result in the exact behavior. First, using a GeometryReader, an extra spacer can be added to the right that has the size of the left inset, but has a lower layoutPriority such that it decreases when the text is longer. The downside is that the center text height is ignored. Second solution, using a combination of GeometryReader and PreferenceKey to add a spacer to the right with the same width as the left side text allows for the center text to occupy multiple lines, but the right side space won't ever be used. Commented May 1, 2020 at 12:47

2 Answers 2

5

You can do this with a combination of embedded HStacks, fixedSized(), and layoutPriority() manipulations.

Try to centre but shift if needed

Source Code

struct CentreTheDarnTextView: View {
    var titleText: String
    var body: some View {
        HStack {
            HStack {
                Text("I am Left Text")
                    .fixedSize()
                Spacer()
            }
            Text(titleText)
                .layoutPriority(1)
                .minimumScaleFactor(0.5)
                .lineLimit(1)
                .foregroundColor(.red)
            HStack {
                Spacer()
                Text("")
            }
        }.padding(.all, 8)
    }
}

struct CentreTheDarnTextView_Previews: PreviewProvider {
    static var previews: some View {
        CentreTheDarnTextView(titleText: "I am Centre Text")
            .previewLayout(.fixed(width:400, height:44))
        CentreTheDarnTextView(titleText: "I am much longer Centre Text")
            .previewLayout(.fixed(width:400, height:44))
        CentreTheDarnTextView(titleText: "I am rediculously long Centre Text that makes programmers cry")
            .previewLayout(.fixed(width:400, height:44))
    }
}
  • .fixedSize() forces the text to not be clipped, and layout full size.
  • .layoutPriority(1) is very similar, but lets you still apply .minimumScaleFactor(), .lineLimit() to handle running out of space.
  • That empty Text("") is the secret sauce.
Sign up to request clarification or add additional context in comments.

3 Comments

This is great, thank you. One followup question: how did you know to do this? Specifically, did you experiment with different view modifiers until something worked? Or do have a mental model of the SwiftUI layout process that allows you to do this more directly? I ask because I'm constantly frustrated that I don't have the latter. I'm reduced to the former (aka, "flailing"). I feel like Apple needs to produce detailed documentation of how layout works, and how these modifiers like fixedSize, layoutPriority, etc. and tricks like Text(""), control it. But I haven't see that documentation yet.
Complete flailing lol. I was typing up a large complex question that was pretty much parallel to yours (but with more complex left and right content). I'd found a Spacer().overlay(HStack) | centered-stuff | Spacer().overlay(HStack) solution on here but it was squeezing the left content all the time. Tried "Just one more thing" about 20 times and stumbled upon it. :-)
Holy smokes, you're a genius @MichaelKernahan. Thanks!
0

one option is to simply use HStack, the UI will try to wrap the long text into multilines:

    HStack {
        Text("first text www eee rrr ttt yyy uuuuu")
        Spacer()
        Text("second text xxxx ccccc vvvvv bbbbb")
        Spacer()
    }.padding(10).border(Color.black)

2 Comments

That won't center the second text though, correct? (In the case where there is space to do so.)
it looks centerish on Mac, on iPhone you may have to add a second last Spacer(), and it looks centered.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.