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="[email protected]" 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.

