fix(ios): geotag in-app camera captures (#266)#268
Merged
Conversation
The in-app camera used UIImagePickerController and read the result as a bare UIImage, so camera photos reached the pipeline with no GPS (only the photo-library path got EXIF location). Location drives the range-prior re-ranker, so in-app captures could not benefit from geography. - Add NSLocationWhenInUseUsageDescription to Info.plist - New LocationService (CLLocationManager wrapper, when-in-use only) - Request authorization when the camera opens; stamp the device's current coordinate onto camera-captured ProcessedPhoto (graceful nil if denied) - Register LocationService.swift in the Xcode project Level 2 (custom AVFoundation camera + live detection overlay) tracked in #267.
The initial LocationService used the CLLocationManagerDelegate pattern, which fights Swift 6 complete strict concurrency (the project targets iOS 26 / Swift 6): non-Sendable CLLocationManager captured across the main-actor hop, delegate isolation assertions. Rewrite on the modern CLLocationUpdate.liveUpdates() AsyncSequence (iOS 17+): a @mainactor class that streams updates in a Task, no delegate. liveUpdates() auto-prompts when authorization is .notDetermined; a denied stream yields nil locations, so captures degrade gracefully to ungeotagged. requestWhenInUseAuthorization() on start() prompts on button tap. - start()/stop() replace requestAuthorization(); view updated accordingly.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR addresses missing GPS coordinates for in-app camera captures on iOS by requesting when-in-use location permission and attaching the device’s latest coordinate to camera-captured photos before they enter the photo-processing pipeline.
Changes:
- Added a new
LocationService(CLLocationManager wrapper) to fetch the device’s current coordinate during the in-app camera flow. - Updated the camera capture path to store
(image, lat, lon)per capture and to persist those coordinates intoProcessedPhoto.gpsLat/gpsLon. - Added
NSLocationWhenInUseUsageDescriptiontoInfo.plistand registeredLocationService.swiftin the Xcode project.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| ios/WingDex/Views/AddPhotosFlow/PhotoSelectionView.swift | Requests location authorization before opening the camera and attempts to pass the latest coordinate into addCameraPhoto. |
| ios/WingDex/ViewModels/AddPhotosViewModel.swift | Stores per-camera-photo location and applies it when creating ProcessedPhoto entries. |
| ios/WingDex/Services/LocationService.swift | Introduces a new CoreLocation wrapper to provide a “latest coordinate” for capture-time geotagging. |
| ios/WingDex/Info.plist | Adds the required location permission usage string for when-in-use authorization prompts. |
| ios/WingDex.xcodeproj/project.pbxproj | Registers LocationService.swift in the Xcode project sources. |
Comments suppressed due to low confidence (1)
ios/WingDex/Services/LocationService.swift:69
locationManagerDidChangeAuthorizationcaptures the delegate parametermanager(a non-SendableCLLocationManager) insideTask { @MainActor in ... }. Under Swift 6 strict concurrency this commonly produces sendability errors. Prefer using the actor-isolatedself.managerinside the@MainActortask and avoid capturing the delegate callback parameter across the concurrency boundary.
- LocationService: clear updatesTask when the liveUpdates() stream ends or throws, so a later start() can restart it (previously a terminated stream left the service permanently 'running'). Guarded on !isCancelled so stop()/restart isn't clobbered. - Correct misleading comment: camera photos use processing time as the timestamp, not shutter time.
1 task
jlian
added a commit
that referenced
this pull request
Jul 22, 2026
…0683) PR #268 added NSLocationWhenInUseUsageDescription to WingDex/Info.plist, but CI runs `xcodegen generate`, which regenerates Info.plist from the `info.properties` block in project.yml and overwrites the hand-added key. The 0.6.1 (build 1) TestFlight upload therefore still shipped without the string and got ITMS-90683 again. Add the key to project.yml (the real source of truth) so the generated Info.plist -- and the archived binary -- includes it. Verified: git-tracked WingDex/Info.plist already has the key (harmless, kept in sync); project.yml was the missing piece.
github-actions Bot
pushed a commit
that referenced
this pull request
Jul 22, 2026
# [wingdex-ios-v0.6.2](ios-v0.6.1...ios-v0.6.2) (2026-07-22) ### Bug Fixes * **ios:** add location purpose string to xcodegen project.yml (ITMS-90683) ([20eb805](20eb805)), closes [#268](#268)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #266.
Problem
Photos captured with the in-app camera ("Take Photo") reached the pipeline with no GPS (
gpsLat: nil, gpsLon: nil). Only the photo-library path got location (via EXIF,PhotoService.extractEXIF). Root cause: the camera usedUIImagePickerController(sourceType: .camera)and readinfo[.originalImage]as a bareUIImage(pixels only, no metadata), and the app had no location permission configured.Location drives the range-prior re-ranker at inference (and is a training signal for the on-device model, #260), so in-app captures couldn't benefit from geography.
Fix (Level 1, pragmatic)
Keep
UIImagePickerController; stamp the device's current location at capture time.NSLocationWhenInUseUsageDescriptiontoInfo.plist.LocationService(CLLocationManager wrapper, when-in-use only, no background tracking;@MainActorwithnonisolateddelegate callbacks marshaling back to the main actor).addCameraPhoto(_:lat:lon:); write it onto theProcessedPhoto. Stop updates on camera dismiss.cameraPhotosnow carries(image, lat, lon)per capture.LocationService.swiftin the Xcode project (project doesn't use synchronized groups, so manual registration).Denied/unavailable location degrades gracefully: the photo is still captured, just ungeotagged (same as before).
Testing
gpsLat/gpsLonpopulated (verify in outing detail / clustering).Not compiled/tested on device yet (authored off-Mac); needs an Xcode build + device check.
Follow-up
Level 2 (custom AVFoundation camera with live bird-detection overlay) is tracked in #267, deferred until the on-device model (#260) lands.