Dev.to WebDev 🛠 Dev 👁 0 📖 3 min read

Shipping one fitness app to iPhone, iPhone Duo, iPad and Apple TV: what I learned

I build Sprung, a mobility app: you take an 8-check mobility test, and it builds daily 8, 12 or 20-minute routines around your tightest zones and your sport. It runs on iPhone, iPad and Apple TV, and since this autumn on

I build Sprung, a mobility app: you take an 8-check mobility test, and it builds daily 8, 12 or 20-minute routines around your tightest zones and your sport. It runs on iPhone, iPad and Apple TV, and since this autumn on the foldable iPhone Duo too. Here is what shipping one app to all of those taught me.

One catalog, generated into every app

The app has 335 exercises, each with a video, cues, zones and equipment. They live in one exercises.json file. The shared core (Kotlin Multiplatform, compiled into a framework the SwiftUI app links) holds the catalog, the test scoring and the routine engine, so the rules that pick your routine are written once.

The same file also feeds the website. A build script prerenders a page for every exercise, so the whole library is free to browse at getsprung.app/exercises.

Lesson: keep content as data from day one. Renaming an exercise is one edit, and a check script flags it when an app and the site drift apart.

"iPad width" is not a layout rule

I first switched to the wide, two-column layout when the screen was iPad-sized. The unfolded iPhone Duo broke that: it is 669 points wide, wider than a phone but narrower than any iPad, and it can fold back to a phone mid-session.

What worked was deciding by the available width, not by device type: one landscape split layout (list on the left, content on the right) for any wide window, and the phone layout otherwise. The same code now covers iPad and the unfolded Duo, and folding the Duo during a workout just reflows the screen.

Apple TV is a different app, not a bigger screen

tvOS shares most of the code, but focus changes everything. The one that bit me hardest: set the initial focus with @FocusState when a screen appears (in .task). Otherwise focus gets lost after a push or a results screen, and the remote seems dead.

Prerendering 366 pages on Cloudflare Workers

The site is a React app, but every exercise, sport and zone page is prerendered at build time and served as static HTML from Cloudflare Workers, with a sitemap of 366 pages.

Google discovered nearly all of them within days, yet indexed only about 40 at first. "Discovered – currently not indexed" mostly means Google doesn't think a page matters yet. The fix I'm testing: link to exercise pages from pages that get traffic (each sport page now links to 8 of its exercises) instead of leaving them reachable only through the sitemap.

Automate App Store Connect early

With four device types, the store work multiplies: screenshots for iPhone 6.9", iPad 13", Apple TV and the iPhone Duo, app previews, in-app events, and custom product pages per sport (golf, running, tennis, climbing). Doing that by hand in the browser doesn't scale.

Small Node scripts against the App Store Connect API (a JWT signed with the API key, then plain fetch) now upload screenshots and previews, create events and fill the custom product pages. Two gotchas:

  • The iPhone Duo screenshot size is exactly 2853×2007. The simulator captures 2852×2006, which App Store Connect rejects.
  • You can't change app previews while a version is "Waiting for Review". Upload them before you submit.

App preview videos stutter for sneaky reasons

My preview videos looked choppy when scrolling. Two causes:

  1. Screen recordings are variable frame rate: a frame is only stored when the screen changes. Trimming with -ss before -i and a plain re-encode dropped frames. Converting to a constant rate first (-vf "fps=30,trim=...,setpts=PTS-STARTPTS") fixed it.
  2. The background still in my ffmpeg overlay was looped at the default 25 fps, and the overlay took that rate, repeating every sixth frame of a 30 fps scroll. -loop 1 -framerate 30 fixed it.

A quick way to find stutters: tblend=all_mode=difference,signalstats. A near-zero frame difference in the middle of a scroll is a repeated frame.

Takeaways

  • Content as data, generated into every target.
  • Lay out by available width, never by device name.
  • Treat tvOS focus as its own feature.
  • Script the store work before you have four device types of it.

If you have shipped to the iPhone Duo or Apple TV, I'd love to hear what tripped you up. The app is at getsprung.app.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.