I am building a iOS in-house Time Card app, and I added functionality to track user clock-in location. This will be used to just to get a basic idea of where the user clocked in.
The issue I am facing is the prompt for location access is never given to the user. Which makes the location grab always fail.
I have implemented the privacy settings for the info.plist, however, it is still not appearing.
Here is my config.xml
<?xml version='1.0' encoding='utf-8'?>
<widget id="com.blductless.workhour" version="2.3.86" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>WorkHour</name>
<description>Time Card App for BL Ductless</description>
<author email="cgreen@blductless.com" href="https://blductless.com">
BL Ductless
</author>
<content src="index.html" />
<access origin="https://blductless.com/*" />
<allow-navigation href="https://blductless.com/*" />
<allow-intent href="http://*/*" />
<allow-intent href="https://*/*" />
<preference name="AllowUniversalAccessFromFileURLs" value="true" />
<preference name="AllowFileAccessFromFileURLs" value="true" />
<preference name="DisallowOverscroll" value="true" />
<preference name="UIWebViewBounce" value="false" />
<plugin name="cordova-plugin-fingerprint-aio" />
<plugin name="cordova-pdf-generator" />
<plugin name="cordova-plugin-nativegeocoder" />
<plugin name="cordova-plugin-geolocation" />
<platform name="ios">
<edit-config file="*-Info.plist" mode="merge" target="NSLocationWhenInUseUsageDescription">
<string>Your location is used to track where you clock-in.</string>
</edit-config>
<edit-config file="*-Info.plist" mode="merge" target="NSLocationAlwaysAndWhenInUseUsageDescription">
<string>Your location is used to track where you clock-in/work on projects.</string>
</edit-config>
<!-- Privacy Manifest: “Do we track users across apps or websites?” -->
<edit-config file="*-Info.plist" mode="merge" target="NSPrivacyTracking">
<!-- set to true only if you use IDFA or other cross‑app trackers -->
<false/>
</edit-config>
<edit-config file="*-Info.plist" mode="merge" target="NSPrivacyTrackingDomains">
<!-- list any domains involved in ad/network tracking, or leave empty -->
<array/>
</edit-config>
<!-- Privacy Manifest: “Which sensitive APIs do we access?” :contentReference[oaicite:0]{index=0} -->
<edit-config file="*-Info.plist" mode="merge" target="NSPrivacyAccessedAPITypes">
<array>
<dict>
<key>NSPrivacyAccessedAPITypesIdentifier</key>
<string>Location</string>
<key>NSPrivacyAccessedAPITypesPurpose</key>
<string>To confirm correct location when updating time card status (clock in, clock out, edit time card)</string>
</dict>
</array>
</edit-config>
<icon height="57" src="resources/ios/icon/57.png" width="57" />
<icon height="114" src="resources/ios/icon/114.png" width="114" />
<icon height="29" src="resources/ios/icon/29.png" width="29" />
<icon height="58" src="resources/ios/icon/58.png" width="58" />
<icon height="87" src="resources/ios/icon/87.png" width="87" />
<icon height="40" src="resources/ios/icon/40.png" width="40" />
<icon height="80" src="resources/ios/icon/80.png" width="80" />
<icon height="50" src="resources/ios/icon/50.png" width="50" />
<icon height="100" src="resources/ios/icon/100.png" width="100" />
<icon height="72" src="resources/ios/icon/72.png" width="72" />
<icon height="144" src="resources/ios/icon/144.png" width="144" />
<icon height="76" src="resources/ios/icon/76.png" width="76" />
<icon height="152" src="resources/ios/icon/152.png" width="152" />
<icon height="167" src="resources/ios/icon/167.png" width="167" />
<icon height="180" src="resources/ios/icon/180.png" width="180" />
<icon height="60" src="resources/ios/icon/60.png" width="60" />
<icon height="120" src="resources/ios/icon/120.png" width="120" />
<icon height="1024" src="resources/ios/icon/1024.png" width="1024" />
</platform>
</widget>
Am I missing something? Everything I've read in the documentation has shown that this is the setup required to use Geolocation in iOS (however outdated).
I have verified that the app has not previously requested for permission, in my settings it says "Allow location when prompted" which is intended behavior for a application that has not yet prompted for location use.
For reference, here is a snippet of my JavaScript clock-in functionality:
// Clock In.
window.clockInTime = new Date();
window.clockedIn = true;
clockBtn.innerText = "Clock Out";
document.getElementById("statusLabel").innerText = "Clocked In";
window.timerInterval = setInterval(updateTimer, 1000);
var finalAddress = "";
navigator.geolocation.getCurrentPosition(onSuccessGetLocation, onErrorGetLocation);
function onSuccessGetLocation(position) {
var msg =
'Latitude: ' + position.coords.latitude + '\n' +
'Longitude: ' + position.coords.longitude + '\n' +
'Altitude: ' + position.coords.altitude + '\n' +
'Accuracy: ' + position.coords.accuracy + '\n' +
'Altitude Accuracy: ' + position.coords.altitudeAccuracy + '\n' +
'Heading: ' + position.coords.heading + '\n' +
'Speed: ' + position.coords.speed + '\n' +
'Timestamp: ' + position.timestamp;
nativegeocoder.reverseGeocode(reverseLocationSuccess, reverseLocationFailure, position.coords.latitude, position.coords.longitude, { useLocale: true, maxResults: 1 });
}
// onError Callback receives a PositionError object
//
function onErrorGetLocation(error) {
}
function reverseLocationSuccess(result) {
var firstResult = result[0] || {};
// extract the pieces you want
var street = firstResult.thoroughfare || '';
var city = firstResult.locality || '';
var state = firstResult.administrativeArea || '';
// join them with commas, skipping any empty values
var formattedAddress = [street, city, state]
.filter(function(part) { return part.length; })
.join(', ');
finalAddress = formattedAddress;
console.log('Formatted Address:', formattedAddress);
fetch("redacted", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
employeeId: employee.id,
clockInTime: window.clockInTime.toISOString(),
projectId: projectId,
location: finalAddress
})
})
.then(r => r.json())
.then(d => console.log("Clock in recorded:", d))
.catch(e => console.error("Error clocking in:", e));
}
I attempted to modify my info.plist with correct location permissions and usage descriptions, ran on real hardware, ran unit tests with a test configuration with location set to a specified entry, and switched geolocation plugins.
The solution ended up being making sure to include 'NSPrivacyCollectedDataTypes', without this iOS will not prompt for location access as it is missing from info.plist
So, I added:
<!-- Privacy Manifest: “What personal data do we collect?” :contentReference[oaicite:1]{index=1} -->
<edit-config file="*-Info.plist" mode="merge" target="NSPrivacyCollectedDataTypes">
<array>
<dict>
<key>NSPrivacyCollectedDataTypesIdentifier</key>
<string>DeviceLocation</string>
<key>NSPrivacyCollectedDataTypesPurposes</key>
<array>
<string>We collect location data to accurately verify time </string>
</array>
</dict>
</array>
</edit-config>
And it works!