~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain-ios

1 collaborator
0
1
main
113 lines · 4.5 KB Raw History

Development

Requirements

brew install xcodegen just xcbeautify swiftformat swiftlint

Setup

Generate the Xcode project and open it:

just generate
open Grain.xcodeproj

If using a non-Xcode editor with SourceKit-LSP (e.g. Zed), run once after generating:

xcode-build-server config -scheme Grain -project Grain.xcodeproj

No local backend needed — just sim and just device both hit the production API at grain.social.

Device builds

Device builds require an Apple Developer account. Create a .env in the repo root:

APPLE_TEAM_ID=YOUR_TEAM_ID
BUNDLE_ID=com.yourorg.grain
BUNDLE_NAME=Grain

Then re-run just generate before building.

Pass your device UDID directly or via an env var:

just device 00000000-0000000000000000   # explicit UDID
just device $iphonemax                  # via shell env var

Find your device UDID with xcrun devicectl list devices.

Commands

just sim               # Build + install + launch on simulator (production API)
just sim-local         # Build + install + launch on simulator (local/dev API)
just sim-next          # Build + install + launch on simulator (next.grain.social)
just sim-fresh         # Same as `just sim`, but uninstalls first (wipes app sandbox)
just sim-local-fresh   # Same as `just sim-local`, but uninstalls first
just device DEVICE_ID  # Build + install to a plugged-in iOS device
just test              # Run tests (iPhone 17 Pro Max simulator)
just generate          # Regenerate Xcode project from project.yml
just basemap-style     # Regenerate the MapLibre style assets (needs node + npm)
just format-fix        # Fix formatting in-place
just lint-fix          # Fix lint violations
just release           # Bump build, archive, upload to App Store Connect

The *-fresh variants xcrun simctl uninstall your app's bundle id before installing, which clears URLCache, UserDefaults, on-disk caches (FeedCache, LabelDefinitionsCache), and Documents/Caches. Pass SIM_UDID=<udid> to target a specific simulator (otherwise uses the booted one).

The location screens draw grain's own Protomaps basemap through MapLibre — the same tiles as grain.social, served from tiles.grain.social, with no API key. The style ships as two bundle resources under Grain/Resources/Basemap, generated from the same theme package the web uses by tools/basemap; the output is committed, so a build needs neither node nor npm. Label fonts and sprite icons come from Protomaps' public asset host until the tile Worker serves them.

Note: The Xcode project is generated from project.yml — run just generate after adding or removing Swift files, or after pulling changes that touch project.yml.

Releasing

Builds ship to TestFlight from Xcode Cloud. Because Grain.xcodeproj is XcodeGen output and isn't committed, ci_scripts/ci_post_clone.sh runs on every Xcode Cloud build to pull git-lfs assets, generate the project, and copy the committed package pins into place.

Build numbers are assigned by Xcode Cloud, which overwrites CFBundleVersion with its own run counter at archive time — CURRENT_PROJECT_VERSION in project.yml is ignored on CI. They only need to be unique and increasing within a marketing version, so a new train starting at a low number is fine.

MARKETING_VERSION is still bumped by hand. Once a marketing version is approved on the App Store that train closes, and further uploads against it fail with 90186/90062 — so raise it in project.yml before releasing against an already-approved version.

just release still archives and uploads from your Mac if you need it, but don't mix the two: it uploads project.yml's CURRENT_PROJECT_VERSION, which is far ahead of Xcode Cloud's run counter, and once a higher build number exists on the current train Xcode Cloud's next run would be rejected as not increasing.

When SPM dependencies change, refresh the committed pins — Xcode Cloud disables automatic package resolution, and the live file lives inside the gitignored .xcodeproj:

just generate
xcodebuild -resolvePackageDependencies -project Grain.xcodeproj -scheme Grain
cp Grain.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved .