<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Eric Guan</title>
    <description>The latest articles on DEV Community by Eric Guan (@eric_guan).</description>
    <link>https://dev.to/eric_guan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3928440%2F8b7e3a7a-6f13-46e3-a516-e596037d561e.png</url>
      <title>DEV Community: Eric Guan</title>
      <link>https://dev.to/eric_guan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eric_guan"/>
    <language>en</language>
    <item>
      <title>I built my daughter a geography app. It took 67 days and zero dependencies.</title>
      <dc:creator>Eric Guan</dc:creator>
      <pubDate>Wed, 13 May 2026 06:17:15 +0000</pubDate>
      <link>https://dev.to/eric_guan/i-built-my-daughter-a-geography-app-it-took-67-days-and-zero-dependencies-2h4d</link>
      <guid>https://dev.to/eric_guan/i-built-my-daughter-a-geography-app-it-took-67-days-and-zero-dependencies-2h4d</guid>
      <description>&lt;p&gt;Two months ago, I started building an iOS app for my daughter. This week, I shipped it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MapSprout&lt;/strong&gt; is a US geography app for kids — they learn the 50 states + DC through quizzes and a 3D map road-trip mode. It took 67 days from first commit, 920 commits total, pure SwiftUI with zero third-party dependencies, and it's in Apple's Made-for-Kids 9–11 category.&lt;/p&gt;

&lt;p&gt;Here's what shipping solo actually taught me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built this in the first place
&lt;/h2&gt;

&lt;p&gt;My daughter Emma is at the age where she's starting to learn US geography. I looked at what was on the App Store. Nothing felt right.&lt;/p&gt;

&lt;p&gt;So I built the thing I wanted her to have.&lt;/p&gt;

&lt;h2&gt;
  
  
  The build
&lt;/h2&gt;

&lt;p&gt;67 days from first commit to launch. 920 commits. Six commits on day one — welcome screen, home screen, state list, and a first cut of the quiz all landed on the first afternoon. The shape of the app didn't really change after that. Everything since was polish, content authoring, and the App Store machinery itself.&lt;/p&gt;

&lt;p&gt;A few decisions I made early ended up shaping everything:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Submitting to the Made-for-Kids 9–11 category.&lt;/strong&gt; This is a one-way decision per Apple — once approved, every future update has to honor the Kids category guidelines, or you have to register a brand-new app. It closed a lot of doors. No behavioral analytics. No third-party SDKs. No free-text input on the kid side. The feedback form is gated behind a parent check (a multiplication problem) so kids can't submit unsupervised. I said no to a lot of "obvious" features early because Kids guidelines closed the door on them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero external dependencies.&lt;/strong&gt; Pure SwiftUI plus Apple frameworks only. No analytics SDK, no crash reporter beyond Xcode Organizer. Every time I felt the urge to reach for a library, I asked whether Apple's framework could do it. Usually yes, if I was willing to write 50 more lines. Total dep-management cost over the build: 0 hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  What took the time
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1052gq2mvce63epbt45g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1052gq2mvce63epbt45g.png" alt="MapSprout's Practice Mode showing a tap-on-map question — " width="800" height="556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The interactive US map.&lt;/strong&gt; The map is 50 custom SwiftUI Shapes plus DC, not a MapKit view. Each is its own tappable region with point-in-path hit testing (not bounding-box, or you'd register a tap on Wyoming when you meant Colorado). Smallest states render last so they draw on top of bigger ones for tap priority — otherwise New York eats Vermont's tap area. On questions where the kid has to tap the state on the map, the map zooms toward the target so DC and Rhode Island actually have something to hit. AK and HI use inset boxes; geographic position would put HI a thousand pixels to the left of the mainland. Interior state borders are a separate overlay because filled Shapes double up at every shared edge. Region highlighting (Northeast / South / Midwest / West) is yet another overlay layer. I rebuilt the rendering pipeline twice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cold-start performance.&lt;/strong&gt; First builds did all setup synchronously in the launch path: load saved progress, build 50+ state shape paths, pre-compute the map sort order, warm the sound synthesizer (no bundled audio files; tones are generated at runtime). Welcome screen lagged on a real device — kids tap the icon and want movement instantly. Refactored to a two-phase startup: Phase 1 loads only what's needed to show the welcome screen and gets the UI on screen; Phase 2 warms everything else in a detached background task at &lt;code&gt;.userInitiated&lt;/code&gt; priority after the UI is visible. The user is already interacting before the heavier work finishes. Cold start now feels instant.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I got wrong
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Algorithmic hints aren't enough.&lt;/strong&gt; I started with the standard moves — "remove two wrong answers", "reveal first letter", "highlight a region." Realized late that kids actually needed a written nudge anchored to a fact they'd already seen in the app. So I authored a written hint for every question, capped at 80 chars, Grade 3 vocab.&lt;/p&gt;

&lt;p&gt;Then I audited the catalog and caught a whole class of accidental hint→answer leaks I'd written without thinking: &lt;em&gt;"small stone you might skip"&lt;/em&gt; → Little Rock. &lt;em&gt;"agreement or harmony"&lt;/em&gt; → Concord. &lt;em&gt;"north of South Carolina"&lt;/em&gt; → North Carolina. &lt;em&gt;"three-word name beside a giant salty lake"&lt;/em&gt; → Salt Lake City.&lt;/p&gt;

&lt;p&gt;A hint that maps to exactly one answer with zero recall isn't a hint, it's a free pass. Re-audit took two evenings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The privacy and listing legal layer is its own project.&lt;/strong&gt; What looked like one afternoon of policy writing turned into: GDPR section, COPPA section, UK Age-Appropriate Design Code section, App Privacy questionnaire (which is &lt;em&gt;not&lt;/em&gt; the same as the privacy policy, and Apple checks both), a deploy pipeline for the legal pages, DNS hardening across two domains, Google Workspace migration for the support inbox. Probably 20% of total project hours, and almost none of it touches the app binary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Some technical surprises&lt;/strong&gt; I had to discover the hard way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SwiftUI's &lt;code&gt;withAnimation&lt;/code&gt; interpolates &lt;code&gt;.position(CGPoint)&lt;/code&gt; linearly between start and end — even when the CGPoint comes from a curve-sampling helper. I burned an evening trying to animate a marker along a Bézier path before figuring this out. The fix is an &lt;code&gt;AnimatableModifier&lt;/code&gt; whose &lt;code&gt;animatableData&lt;/code&gt; is the scalar &lt;code&gt;t&lt;/code&gt;, so SwiftUI interpolates &lt;code&gt;t&lt;/code&gt; and re-samples the curve every frame.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;.storekit&lt;/code&gt; config file is only loaded by the Xcode scheme, not by &lt;code&gt;simctl launch&lt;/code&gt;. I tried to capture a paywall screenshot via the simulator CLI and the price showed "Loading price…" forever.&lt;/li&gt;
&lt;li&gt;TestFlight IAP bypasses the sandbox tester flow entirely. Sandbox tester accounts (the ones you create in App Store Connect) are useless inside TestFlight builds.&lt;/li&gt;
&lt;li&gt;CloudKit's public DB &lt;code&gt;_world&lt;/code&gt; role is forced read-only. Spent an hour trying to give anonymous users write access to a feedback bucket. CloudKit doesn't let you.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Apple review wrinkle
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkt1mygih9mzifq006umx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkt1mygih9mzifq006umx.png" alt="App Privacy questionnaire flipped from No to Yes with three Not-Linked categories: User Content, Diagnostics, Usage Data" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Submitted May 7 at 8:46 PM PT. About 36 hours later I got a Guideline 2.1 Information Needed message about our Made-for-Kids (1.3) data practices.&lt;/p&gt;

&lt;p&gt;The App Privacy questionnaire I'd filed said "No" to all data collection. But App Review noticed CloudKit was writing user-generated quiz answers off-device. Even though they're anonymous, even though it's the user's own private database — that still counts as "collection" in App Privacy terms.&lt;/p&gt;

&lt;p&gt;Reply went out same day. I flipped the questionnaire to "Yes" with three Not-Linked / App-Functionality categories. Approved May 11. The actual app didn't change. The disclosure did.&lt;/p&gt;

&lt;p&gt;The App Privacy questionnaire and the privacy policy are two different things. Disclose anything that leaves the device on both. The questionnaire is what the review team reads first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Launch day itself
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7xmyujz5u9v3yq8n0ec2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7xmyujz5u9v3yq8n0ec2.png" alt="App Store listing:&amp;lt;br&amp;gt;
MapSprout's live App Store listing on iPad — app icon, title MapSprout: 50 States for Kids, Get button, and screenshots carousel" width="800" height="556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Faster than I expected. The "Release This Version" click → "Ready for Distribution" status flip → public App Store URL returning HTTP 200 took about 2.5 minutes of CDN propagation. I polled the URL every 30 seconds until the title and price showed up. The post-launch admin (README badge, privacy policy revision note, calendar reminder to capture the keyword ranking baseline at 48h) took longer than the release itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I'm going with this
&lt;/h2&gt;

&lt;p&gt;Launch isn't the verification. The first 30 days of real-user data is. I deferred a few polish items that are queued for v1.0.1, shipping within two weeks.&lt;/p&gt;

&lt;p&gt;I've been studying the existing kids' geography apps in the category. Most of them haven't shipped meaningful updates in over a year. I have fewer features today but I think I can build them. Instead of going wide and starting more apps, I'm going deep on this one.&lt;/p&gt;

&lt;p&gt;For the next month: watch first-week signal (App Store Analytics, reviews, crashes, in-app feedback), ship v1.0.1 with the deferred polish items, and start on v1.1.&lt;/p&gt;




&lt;p&gt;If you want to take a look, &lt;strong&gt;MapSprout&lt;/strong&gt; is on the App Store at &lt;a href="https://apps.apple.com/app/id6766167316" rel="noopener noreferrer"&gt;apps.apple.com/app/id6766167316&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'm Eric Guan, an indie iOS developer building under &lt;a href="https://www.vectoryapps.com" rel="noopener noreferrer"&gt;Vectory LLC&lt;/a&gt;. MapSprout is my first app. I'll keep posting here as I learn — drop a follow if shipping-solo retros are useful to you.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>indiedev</category>
      <category>appstore</category>
      <category>swift</category>
    </item>
  </channel>
</rss>
