Skip to content

Instantly share code, notes, and snippets.

@ValentinWalter
Created April 22, 2026 12:23
Show Gist options
  • Select an option

  • Save ValentinWalter/8161f3bcce236ce439bb6cdf87db485e to your computer and use it in GitHub Desktop.

Select an option

Save ValentinWalter/8161f3bcce236ce439bb6cdf87db485e to your computer and use it in GitHub Desktop.
//
// BalancedWidth.swift
// SemanticalKit
//
// Created by Valentin Walter on 09.04.23.
//
import SwiftUI
extension View {
/// Reduces the view's width to the smallest size without altering the
/// height. Used with text views to eliminate widows and make line breaks,
/// particularly in titles and headings, more readable.
///
/// This modifier can be used on any `View`, but it's best to use it near
/// the relevant `Text` view. The modifier is wrapping the view, which may
/// result in unexpected loss of information up the view hierarchy.
///
/// - Important: This will break layout when used on `Group` or other
/// collector views.
///
/// - Parameter ratio: Strength of the effect. A value of 0 means no effect.
public func balancedWidth(ratio: Double = 1) -> some View {
BalancedWidthLayout(ratio: ratio) {
self
}
}
}
/// An implementation detail of the ``View.balancedWidth()`` modifier. **Only
/// takes the first subview into account.**
///
/// Based on [React Wrap Balancer](https://react-wrap-balancer.vercel.app).
private struct BalancedWidthLayout: Layout {
let ratio: Double
init(ratio: Double) {
assert(
(0...1).contains(ratio),
"Balanced Width ratio must not exceed 0 to 1.",
)
self.ratio = ratio
}
func sizeThatFits(
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout Void,
) -> CGSize {
let proposedSize = proposal.replacingUnspecifiedDimensions()
guard let subview = subviews.first else {
return proposedSize
}
let size = subview.sizeThatFits(
.init(
width: proposedSize.width,
height: nil,
)
)
// Binary search for minimum width without height change
var lower = size.width / 2 - 0.25
var upper = size.width + 0.5
var middle: Double
while lower + 1 < upper {
middle = round((lower + upper) / 2.0)
let middleSize = subview.sizeThatFits(
.init(
width: middle,
height: nil,
)
)
if middleSize.height == size.height {
upper = middle
} else {
lower = middle
}
}
return CGSize(
width: upper + (size.width - upper) * (1 - ratio),
height: size.height,
)
}
func placeSubviews(
in bounds: CGRect,
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout Void,
) {
guard let subview = subviews.first else { return }
let size = sizeThatFits(proposal: proposal, subviews: subviews, cache: &cache)
let point = CGPoint(x: bounds.minX, y: bounds.minY)
subview.place(at: point, proposal: .init(size))
}
}
// MARK: - Library Content
struct BalancedWidth_LibraryContent: LibraryContentProvider {
@LibraryContentBuilder
@MainActor
func modifiers(base: some View) -> [LibraryItem] {
LibraryItem(
base.balancedWidth(),
category: .layout,
)
}
}
// MARK: - Previews
#Preview {
@Previewable @State var maxWidth = 280.0
VStack(spacing: 32) {
Spacer()
Group {
Text("The quick brown fox jumps over the lazy dog")
.balancedWidth()
Text("The quick brown fox jumps over the lazy dog")
.foregroundStyle(.secondary)
}
.border(.pink)
.frame(maxWidth: maxWidth)
.border(.blue)
.font(.title)
.multilineTextAlignment(.center)
Spacer()
Text("max width: \(maxWidth.rounded(), format: .number)")
.contentTransition(.identity)
.monospaced()
Slider(value: $maxWidth, in: 100...400)
}
.animation(.interactiveSpring(), value: maxWidth)
.padding()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment