●MODEL — Claude Opus 4.8 lands, improving coding, agentic, and reasoning over 4.7 at the same price●CODE — Opus 4.8's Fast mode runs at 2.5x speed and is now three times cheaper than earlier models●CODE — Auto-mode command classification expands, with denial tracking and live bash path autocomplete●ENTERPRISE — Connector permissions in custom roles let admins control which tools each role can use●TEAM — Tag Claude directly in Slack and hand off tasks while you focus elsewhere●MCP — MCP servers now show startup auth notices, making connection status easier to track●MODEL — Claude Opus 4.8 lands, improving coding, agentic, and reasoning over 4.7 at the same price●CODE — Opus 4.8's Fast mode runs at 2.5x speed and is now three times cheaper than earlier models●CODE — Auto-mode command classification expands, with denial tracking and live bash path autocomplete●ENTERPRISE — Connector permissions in custom roles let admins control which tools each role can use●TEAM — Tag Claude directly in Slack and hand off tasks while you focus elsewhere●MCP — MCP servers now show startup auth notices, making connection status easier to track
Fixing Blurry Wallpapers on New iPhones with Claude Code: Safely Growing a Per-Device Resolution Map
Why wallpapers go blurry or letterboxed on brand-new iPhones, and how to collapse scattered device branches into a single source of truth you can extend in one line — walked through as a real Claude Code refactor with before/after code.
The weekend a new iPhone shipped, I got a report on my wallpaper app, Beautiful HD Wallpapers, that one new device showed a faint band above and below the image. Reproducing it in the simulator confirmed it: on the new model only, the wallpaper sat centered with the background color filling the top and bottom. The cause was mundane — the code that decides each device's resolution didn't know the new screen size, so it fell back to "the nearest old device" and served an image whose aspect ratio didn't match.
A wallpaper app lives or dies on one thing: serving the exactly correct pixel resolution for each device. Get the aspect ratio even slightly wrong and you either upscale into softness or letterbox into empty bands. Yet after years of updates, that decision had scattered itself across the codebase. Here's how I collapsed that sprawl into a single table with Claude Code, so a new device now takes one line — with the actual code along the way.
Why "nearest height" quietly breaks
Start with the precise root cause. An iOS screen is two-layered: logical points and physical pixels. UIScreen.main.bounds returns points; the real pixel count is that multiplied by nativeScale (3.0 on most recent devices). A wallpaper image has to line up with those physical pixels, or the system rescales it and softens the result.
In my app, the branch that picks an image size or layout constant per device had been appended to, screen by screen, with every new model — ending up as 29 ternary expressions. The typical shape:
// ❌ Before: matching by screen "height" alone// branches like this multiplied across the app with each new devicefunc wallpaperPixelHeight() -> CGFloat { let h = UIScreen.main.bounds.height if h >= 932 { // meant to be "Pro Max class" return 2796 } else if h >= 852 { // meant to be "standard Pro" return 2556 } else if h >= 844 { return 2532 } else { return 2436 // fall back to older devices }}
This has two weaknesses. First, when a new model's height lands in a gap between thresholds, it gets sucked into an unintended branch. Second, the only axis is height, so a device with a different aspect ratio gets treated as identical to a same-height device. For wallpapers the second one is fatal: even a few points of width difference at the same height means the image is slightly stretched or letterboxed.
The band on the new iPhone was exactly that — a device "close in height but different in width" was handled as an old device.
First, have Claude Code list every branch
The plan is clear: gather the scattered branches into one place, switch the axis from height to the screen's point size (a width/height pair), and change selection from "nearest height" to "exact size match, then closest aspect ratio." But rewriting blind would miss one of the 29 sites. The first job is to know the full set precisely.
This is where Claude Code helps. Asking it to find the branches semantically is faster and leakier than hand-grepping. I asked:
List every place in this repo that branches on UIScreen.main.bounds
height/width or UIDevice model name. Give me a table of file path,
line number, and what each branch switches (image size / inset /
font etc.). Do not make any changes yet.
The "do not make any changes yet" matters. Claude Code will happily rewrite everything at once, but a refactor is safer in the order understand → design → replace → verify. The returned list showed that of the 29 sites, 6 switched image size and the rest were insets or safe-area constants. That told me to carve out the image-size decision first.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Understand why wallpapers go soft or letterboxed only on new iPhones, and ship a fix that matches by aspect ratio instead of nearest height in your own app
✦Learn how to safely fold 20–30 scattered device-branching ternaries into one source-of-truth table using Claude Code, with before/after Swift
✦Rebuild the logic so a new device takes a single new line, and catch any miss with a snapshot test that fails loudly
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Next, gather the correct per-device values into one place. I keyed a table on each device's logical size (points) and used it to look up the pixel resolution to serve. The values I actually use in the app, excerpted (logical size × 3 gives the physical pixels):
Device class
Logical (pt)
Served (px)
Aspect
iPhone Air
420 × 912
1260 × 2736
0.461
iPhone 17 Pro
402 × 874
1206 × 2622
0.460
iPhone 16 / 17 Pro Max
440 × 956
1320 × 2868
0.460
iPhone 15 / 14 Pro
393 × 852
1179 × 2556
0.461
The aspect column exists for fallback. When no device matches exactly, I pick the profile closest in aspect ratio rather than height. That way an unknown new model lands on "nearly the same ratio" instead of a stretch, and bands or softness rarely appear.
In Swift I held the table as a plain array of structs:
// ✅ After: device profiles gathered into one placestruct DeviceProfile { let pointSize: CGSize // logical size (the match key) let pixelSize: CGSize // physical pixels to serve var aspect: CGFloat { pointSize.width / pointSize.height }}enum WallpaperResolution { // a new device = one new line, reflected across every screen static let profiles: [DeviceProfile] = [ .init(pointSize: .init(width: 420, height: 912), pixelSize: .init(width: 1260, height: 2736)), .init(pointSize: .init(width: 402, height: 874), pixelSize: .init(width: 1206, height: 2622)), .init(pointSize: .init(width: 440, height: 956), pixelSize: .init(width: 1320, height: 2868)), .init(pointSize: .init(width: 393, height: 852), pixelSize: .init(width: 1179, height: 2556)), ] /// Returns the best pixel resolution for the current device. static func best(for screen: UIScreen = .main) -> CGSize { let size = screen.bounds.size // 1) Prefer a profile whose logical size matches exactly. if let exact = profiles.first(where: { $0.pointSize.width == size.width && $0.pointSize.height == size.height }) { return exact.pixelSize } // 2) Otherwise fall back to the closest aspect ratio // (picking by height letterboxes ratio-mismatched devices). let targetAspect = size.width / size.height let nearest = profiles.min(by: { abs($0.aspect - targetAspect) < abs($1.aspect - targetAspect) }) return nearest?.pixelSize ?? .init(width: 1179, height: 2556) }}
Now a branch like the original wallpaperPixelHeight() collapses into one call: WallpaperResolution.best().height. With the basis in one place, you can also trace later "why this device gets this resolution" — a real benefit.
How to hand the 29 replacements to Claude Code
With the destination in place, swap the scattered 29 sites onto the new call. A blanket replace is risky here too: image-size branches and inset/safe-area branches mean different things but look alike, so even Claude Code can mistake the context. I split the request into stages:
From that list, target only the 6 sites that switch image size and
rewrite them to use WallpaperResolution.best(for:). Propose one file
at a time and show me the diff. Leave inset/safe-area branches alone
for now.
Constraining it to "one file at a time" and "show me the diff" lets you review as you go.
Three habits keep this staged replace safe.
Target only the image-size branches first
Inset and safe-area branches mean something different, so don't touch them in the same pass. Limiting scope to the 6 image-size sites keeps the review focused on one concern.
Read the diff one file at a time
Don't accept in bulk; read file by file. Exceptions that look like resolution but aren't — a display-scale calculation, say — only surface here.
Let a human decide on exceptions
Where a naive replace would change meaning, hold Claude Code's suggestion and rewrite by hand. Use the speed for candidates; keep the final call yourself. Midway, one screen turned out to compute a display scale rather than a resolution, so a naive replace would have changed its meaning. Exceptions like that get missed unless a human reads the diff once. Claude Code is genuinely fast, but skip the diffs in the name of speed and you'll find the bug on a real device later instead. In this kind of work I keep the roles split: Claude Code proposes candidates, I decide what's accepted. I wrote more about that split in two weeks of using Claude on Xcode vs. Claude Code.
A snapshot test to catch what slips through
The scariest part of a refactor is "you thought you fixed it, but one device kept the old behavior." Relying on eyeballing the simulator leaks more as devices multiply. So I pinned the expected pixels for every profile in a test:
import XCTest@testable import BeautifulWallpapersfinal class WallpaperResolutionTests: XCTestCase { // Every profile must match exactly at its own logical size. func testExactMatchForEveryProfile() { for p in WallpaperResolution.profiles { let screen = StubScreen(bounds: CGRect(origin: .zero, size: p.pointSize)) XCTAssertEqual(WallpaperResolution.best(for: screen), p.pixelSize, "no exact match at \(p.pointSize)") } } // An unknown size must fall back by aspect, not by nearest height. func testUnknownSizeFallsBackByAspectNotHeight() { // height is close to Pro Max, but the ratio is closer to a standard phone let weird = CGSize(width: 360, height: 950) let screen = StubScreen(bounds: CGRect(origin: .zero, size: weird)) let got = WallpaperResolution.best(for: screen) // assert it was NOT sucked into nearest height (Pro Max 2868) XCTAssertNotEqual(got.height, 2868) }}
StubScreen is a thin wrapper that lets you swap bounds, which is why best(for:) takes a UIScreen argument — making it testable straightened the design itself. The second test is the heart of this change: it explicitly guards "do not get pulled into nearest height." If a future model lands in a threshold gap again, this test goes red and tells you.
In numbers: image-size selection went from 6 ternaries to one table lookup, and supporting a new device is now "add one line to the array and one test." Since shipping the fix in a staged rollout, no new-device reports of bands or softness have come in.
If you want a more systematic grounding in iOS device sizing, Apple's Human Interface Guidelines plus a good Swift layout reference both help untangle the relationship between logical points and physical pixels.
The shift is from scrambling to fix 20 sites whenever a device ships, to finishing in one line. Start by having Claude Code list every place your project branches on screen height. The moment the full sprawl is visible, where to fold it becomes obvious on its own.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.