Skip to content

fix(ios): geotag in-app camera captures (#266)#268

Merged
jlian merged 3 commits into
mainfrom
feat/camera-location
Jul 22, 2026
Merged

fix(ios): geotag in-app camera captures (#266)#268
jlian merged 3 commits into
mainfrom
feat/camera-location

Conversation

@jlian

@jlian jlian commented Jul 21, 2026

Copy link
Copy Markdown
Owner

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 used UIImagePickerController(sourceType: .camera) and read info[.originalImage] as a bare UIImage (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.

  • Add NSLocationWhenInUseUsageDescription to Info.plist.
  • New LocationService (CLLocationManager wrapper, when-in-use only, no background tracking; @MainActor with nonisolated delegate callbacks marshaling back to the main actor).
  • Request authorization when the camera opens; pass the current coordinate into addCameraPhoto(_:lat:lon:); write it onto the ProcessedPhoto. Stop updates on camera dismiss.
  • cameraPhotos now carries (image, lat, lon) per capture.
  • Register LocationService.swift in 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

  • On-device: first "Take Photo" shows the location permission prompt.
  • Captured photo lands with gpsLat/gpsLon populated (verify in outing detail / clustering).
  • Denying location still lets capture + upload proceed (ungeotagged).

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.

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.
Copilot AI review requested due to automatic review settings July 21, 2026 22:04
@github-actions
github-actions Bot temporarily deployed to preview (#268) July 21, 2026 22:07 Inactive
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.
@github-actions
github-actions Bot temporarily deployed to preview (#268) July 21, 2026 22:16 Inactive

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 into ProcessedPhoto.gpsLat/gpsLon.
  • Added NSLocationWhenInUseUsageDescription to Info.plist and registered LocationService.swift in 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

  • locationManagerDidChangeAuthorization captures the delegate parameter manager (a non-Sendable CLLocationManager) inside Task { @MainActor in ... }. Under Swift 6 strict concurrency this commonly produces sendability errors. Prefer using the actor-isolated self.manager inside the @MainActor task and avoid capturing the delegate callback parameter across the concurrency boundary.

Comment thread ios/WingDex/Views/AddPhotosFlow/PhotoSelectionView.swift
Comment thread ios/WingDex/ViewModels/AddPhotosViewModel.swift Outdated
Copilot AI review requested due to automatic review settings July 21, 2026 22:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comment thread ios/WingDex/Services/LocationService.swift
- 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.
Copilot AI review requested due to automatic review settings July 21, 2026 22:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

@jlian
jlian merged commit 51bd501 into main Jul 22, 2026
4 of 5 checks passed
@jlian
jlian deleted the feat/camera-location branch July 22, 2026 01:09
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

In-app camera captures have no GPS location

2 participants