Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI: Tricky layouts with pinning and neighbors?

Tags:

ios

swift

swiftui

The following layout is easy to achieve with old-school Autolayout, but I can't see how to do the same thing with SwiftUI. Here is an example of the view I want to render:

enter image description here

This shows 3 variants of the same view. The view has 3 text children. Left, center, and right, with the following properties:

  • Left and right views pinned to the leading and trailing of the superview, center view always remains centered in the superview (1)
  • Center view remains centered if either side view is missing or empty (2)
  • Center view grows until it hits either side view, and then clips its contents (3)

The best I could do is this sample below, but it has the drawback that the center view takes priority over the side views and thus clips them.

How can I achieve this with SwiftUI layout?

import SwiftUI

struct MyExample: View {

    let leftText:String
    let mainText:String
    let rightText:String

    var body: some View {
        HStack{
            HStack {
                Text(self.leftText)
                    .modifier(MyBigFont())

                Spacer()
            }

            Text(self.mainText)
                .modifier(MyBigFont())
                .layoutPriority(1.0)

            HStack {
                Spacer()
                Text(self.rightText)
                    .modifier(MyBigFont())

            }
        }
    }

}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        VStack {
            MyExample(leftText:"ABC", mainText:"1234", rightText:"XYZ").padding()
            MyExample(leftText:"", mainText:"1234", rightText:"XYZ").padding()
            MyExample(leftText:"ABC", mainText:"1234567890", rightText:"XYZ").padding()
            Spacer()
        }
    }
}

struct MyBigFont: ViewModifier {
    func body(content: Content) -> some View {
        content
            .lineLimit(1)
            .font(Font.system(size: 42).bold())
    }
}

Output:

enter image description here

like image 830
Mike Bernardo Avatar asked Nov 29 '25 00:11

Mike Bernardo


1 Answers

This can uncenter the middle section if the left or right are so wide they would need to truncate, but other than that, I believe adding fixedSize() to the side Texts is all you need. fixedSize() on a Text indicates that it's not allowed to truncate or wrap, which is exactly what you're looking for.

var body: some View {
    HStack{
        HStack {
            Text(self.leftText)
                .modifier(MyBigFont())
                .fixedSize()   // <====

            Spacer()
        }

        Text(self.mainText)
            .modifier(MyBigFont())
            .padding(.horizontal)
            .layoutPriority(1.0)


        HStack {
            Spacer()
            Text(self.rightText)
                .modifier(MyBigFont())
                .fixedSize()    // <====
        }
    }
}

enter image description here


Your question in the comment is really good, and a great excuse to explain how this works. Nothing here is tricky IMO, so it's good to understand it.

From the 'layoutPriority' docs: "A parent layout should offer children with the highest layout priority all the space offered to the parent minus the minimum space required for all its lower-priority children, and so on for each lower priority value." (emphasis mine) Doesn't that imply SwiftUI should give the side texts a minimum size to fit their content?

SwiftUI is giving the side texts a minimum size to fit their content. The minimum size for a Text with tail-truncation is the space required to draw one character and an ellipsis. The ideal (horizontal) size is the size required to draw all of the text on one line without wrapping.

So the outer HStack first asks the two inner HStacks for their minimum sizes. They in turn ask their Text for its minimum size (the first character plus an ellipsis), and the Spacer's minimum is zero.

The outer HStack then takes those two minimums and subtracts them from its total and offers that to the center Text.

When you add fixedSize, that tells the Text to report its minimum size as the size required to draw the whole string on one line, so now each inner HStack has that as its minimum, and the center Text gets offered less space.

In both cases, the center Text then tells the outer HStack how much space it's going to use, truncating if necessary (since it doesn't have fixedSize).

The outer HStack subtracts that space and then considers the other two children. They are equally flexible (since both include a Spacer), so it offers half the space to the left one. The left one takes exactly what it was offered (*), and so the other half is offered to the right one, which also takes all of it.

(*) In most cases that we're talking about here. In a narrow enough container, it's possible that the minimum size required will be greater than what is available. In that case, the child will take more than it's offered, and things will overflow their bounds.

like image 179
Rob Napier Avatar answered Nov 30 '25 14:11

Rob Napier



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!