Opening
Layout math tangled across view bodies is the silent cause of many flakey UIs: janky rotations, Dynamic Type regressions, and list CPU spikes. This week I dig into treating layout as code — small, testable types that limit blast radius when you migrate screens from UIKit or ad‑hoc SwiftUI. The aim is predictable measurement, not chasing pixels.
This Week’s Big Story
Custom Layouts Using SwiftUI’s Layout Protocol
Converting scattered frame calculations into focused Layout types surfaces hard-to-observe nondeterminism in apps and forces you to make measurement explicit. The pain shows up as jumping frames during rotation, broken alignment at large accessibility sizes, and extra work done by lists. The piece argues for a controlled, incremental adoption of Layout so you get deterministic sizing and easier tests without a full rewrite.
Trend Signals
• Make macOS consistently bad unironically — a sharp, opinionated take that highlights persistent macOS UX inconsistencies engineers still wrestle with. [Source: HackerNews]
• Cocoa-Way – Native macOS Wayland compositor for running Linux apps seamlessly — community projects keep pushing macOS interoperability in unexpected directions that may affect cross-platform toolchains. [Source: HackerNews]
• Detecting file changes on macOS with kqueue — low‑level file-watching remains practical and relevant for tooling and dev workflows on Apple platforms. [Source: HackerNews]
• Swift 6.3 Released — Swift continues to iterate across the stack; teams should evaluate compiler and stdlib changes before upgrading projects. [Source: Swift.org Blog]
• Swift: Safari Web Extensions From 0 to 0.01 — browser extension work in Swift is getting practical attention again, especially for teams that want native tooling for web integrations. [Source: Medium]
Swift Snippet of the Week
import SwiftUI
struct TwoColumnLayout: Layout {
struct Cache { var leadingWidth: CGFloat = 0 }
var spacing: CGFloat
func makeCache(subviews: Subviews) -> Cache { Cache() }
func updateCache(_ cache: inout Cache, subviews: Subviews) {
// deterministic measurement: use the intrinsic size of leading column
if let leading = subviews.first {
let proposed = ProposedViewSize(width: .infinity, height: .infinity)
cache.leadingWidth = leading.sizeThatFits(proposed).width
}
}
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout Cache) -> CGSize {
guard subviews.count >= 2 else { return .zero }
let trailing = subviews[1].sizeThatFits(proposal)
let leading = CGSize(width: cache.leadingWidth, height: subviews[0].sizeThatFits(proposal).height)
let width = leading.width + spacing + trailing.width
let height = max(leading.height, trailing.height)
return CGSize(width: width, height: height)
}
// … (truncated for newsletter)
This pattern matters because it encodes a disciplined, testable approach to measurement and cache invalidation that prevents flakey layout behavior in production.
Community Picks
More community links next issue.
Until Next Time
Read the full article for a migration checklist: small Layout types, minimal caches, and where to call out to Instruments during rollout. Hit reply with the tradeoffs that surprised you when you moved measurement out of view bodies — I’ll share the most concrete examples next issue and start a LinkedIn thread for back-and-forth. Forward this to a teammate who keeps doing layout in view bodies; they’ll thank you later.