A custom Layout can be used for this.
To pass a parameter to a custom layout, you need to define a LayoutValueKey. Let's do this for the layout weight. As a convenience, a view extension can also be defined, for applying the layout weight.
private struct LayoutWeight: LayoutValueKey {
static let defaultValue = 1
}
extension View {
func layoutWeight(_ weight: Int) -> some View {
layoutValue(key: LayoutWeight.self, value: weight)
}
}
Here is an example Layout implementation which works as follows:
A cache is used to save the layout weights of the subviews. The maximum ideal height is also cached.
The function sizeThatFits computes the widths for the subviews by dividing the container width in accordance with the weights of the subviews. The size returned by this function is always the full container width, but the height is the maximum height required by the subviews.
The function placeSubviews works similarly. Each subview is positioned at the center of the space available to it. There is no spacing between subviews.
struct WeightedHStack: Layout {
typealias Cache = ViewInfo
struct ViewInfo {
let weights: [Int]
let idealMaxHeight: CGFloat
var isEmpty: Bool {
weights.isEmpty
}
var nWeights: Int {
weights.count
}
var sumOfWeights: Int {
weights.reduce(0) { $0 + $1 }
}
}
func makeCache(subviews: Subviews) -> ViewInfo {
var weights = [Int]()
var idealMaxHeight = CGFloat.zero
for subview in subviews {
let idealViewSize = subview.sizeThatFits(.unspecified)
idealMaxHeight = max(idealMaxHeight, idealViewSize.height)
weights.append(subview[LayoutWeight.self])
}
return ViewInfo(weights: weights, idealMaxHeight: idealMaxHeight)
}
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ViewInfo) -> CGSize {
var maxHeight = cache.idealMaxHeight
if !cache.isEmpty, subviews.count == cache.nWeights, let containerWidth = proposal.width {
let unitWidth = containerWidth / CGFloat(cache.sumOfWeights)
for (index, subview) in subviews.enumerated() {
let viewWeight = cache.weights[index]
let w = CGFloat(viewWeight) * unitWidth
let viewSize = subview.sizeThatFits(ProposedViewSize(width: w, height: nil))
maxHeight = max(maxHeight, viewSize.height)
}
}
return CGSize(width: proposal.width ?? 10, height: maxHeight)
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ViewInfo) {
if !cache.isEmpty, subviews.count == cache.nWeights {
let unitWidth = bounds.width / CGFloat(cache.sumOfWeights)
var minX = bounds.minX
for (index, subview) in subviews.enumerated() {
let viewWeight = cache.weights[index]
let w = CGFloat(viewWeight) * unitWidth
let viewSize = subview.sizeThatFits(ProposedViewSize(width: w, height: bounds.height))
let h = viewSize.height
let x = minX + ((w - viewSize.width) / 2)
let y = bounds.minY + ((bounds.height - h) / 2)
subview.place(at: CGPoint(x: x, y: y), proposal: ProposedViewSize(width: w, height: h))
minX += w
}
}
}
}
Some examples of use:
1. Three simple Text views
WeightedHStack {
Text("Text1").layoutWeight(1).background(.yellow)
Text("Text2").layoutWeight(3).background(.orange)
Text("Text3").layoutWeight(1).background(.yellow)
}
.border(.red)

2. Three text views, each with padding and maximum width
WeightedHStack {
Text("Text1")
.padding()
.frame(maxWidth: .infinity)
.layoutWeight(1)
.background(.yellow)
Text("Text2")
.padding()
.frame(maxWidth: .infinity)
.layoutWeight(3)
.background(.orange)
Text("Text3")
.padding()
.frame(maxWidth: .infinity)
.layoutWeight(1)
.background(.yellow)
}
.border(.red)

3. As above, but using a larger text in the middle
let loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
