javascripthtmlioscordova

iOS Cordova Geolocation not prompting for Location Permission


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.


Solution

  • 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!