Advanced Git, Second Edition

Git is key to great version control and collaboration on software projects.
Stop struggling with Git and spend more time on the stuff that matters!

Home iOS & Swift Tutorials

What’s New With Privacy?

Learn about the new privacy features introduced in iOS 14.5, including accessing the new privacy-focused image picker, handling location permissions and more.

5/5 3 Ratings

Version

  • Swift 5, iOS 14, Xcode 12

Since the introduction of the first iPhone, the capabilities of smartphones have grown rapidly. The cellphone is no longer a tool for just calling or texting. Apps can now provide directions to your favorite destinations, access contacts to create meetings, provide professional photo editing tools and much more. The smartphone is a whole computer in your pocket.

But along with more capabilities, privacy concerns have arrived. How can users trust that apps are not maliciously using their data? Apple prides itself on being a privacy-focused company, creating and building features to better improve user privacy.

In this tutorial, you’ll learn about:

  • The new Photos library and how it’s safer and privacy-focused.
  • How to handle location permissions.
  • iOS 14’s new Clipboard prompts.
  • The new camera and microphone indicators.

Additionally, you’ll learn about iOS 14.5’s App Tracking Transparency feature and how to ask permission to track.

Getting Started

Download the project materials by clicking the Download Materials button at the top or bottom of the tutorial. Open YourPrivacy.xcodeproj in the starter folder. Build and run.

An empty view with a placeholder text "Add a picture by tapping the photo icon button" and a photo icon navigation button in the top right corner.

YourPrivacy is a simple app to add a caption and location to an image. The app stores those images and information in a list inside the app the user can later open.

Currently, the app is empty and tells the user to tap the button at the top of the screen to add a picture. Tapping it takes you to another screen with a button to select a picture, a field to add a caption and a location section. Currently, the location section toggle is disabled and tapping the Select Image button opens a blank screen with a placeholder text. You’ll add these functionalities during this tutorial.

Understanding Library Permissions and the New Restricted API

Before iOS 14, users could pick pictures from their Photos library using UIImagePickerController. This view controller has been a part of iOS since iOS 2, being the default way of fetching pictures. You also could do this using PHCollectionList, which allows you to fetch images from the Photos library and use them in your app.

But users could only allow or deny full access to their Photos library, even when they wanted to work with only a single picture or album, giving access to apps to do whatever they wanted with the user’s pictures.

With iOS 14, Apple introduced new ways of accessing photos that solved this problem: PHPickerViewController and presentLimitedLibraryPicker(from:). These APIs are safer and more privacy-focused, giving access only to pictures the user allows.

Working with Limited Libraries

iOS 14 introduced the concept of limited libraries. Instead of giving full access to the user’s library, iOS creates an intermediary library for each app, with only the pictures the user picks. That way, an app has access only to pictures and albums the user chooses, not the full library.

Users can choose to share a whole album or just some pictures with each app. With the new presentLimitedLibraryPicker(from:), users select which pictures are available to the app, and when they later change this selection, the app gets a notification to update the UI. Under Settings ▸ Privacy ▸ Photos, users can modify the level of Photos library access, as well as the selected photos available to each app.

iOS acts as an intermediary between your app and what the user wants to share.

Designing for Limited Libraries

When designing your app, it’s important to determine what kind of access your app requires. YourPrivacy saves only one picture at a time. Instead of requesting access to the entire library, letting the user pick one picture is simpler and easier.

The new PHPickerViewController works great for use cases like this. It’s built with privacy features that provide only the images the user wants to share. You’ll use this to fetch a photo from the user’s library and save it.

Adding the New PHPickerViewController

PHPickerViewController is a UIKit view controller. To use it with SwiftUI, you’ll have to create a UIViewControllerRepresentable to encapsulate it.

On Xcode, create a new Swift file inside the View group and name it ImagePickerView.swift. Add the following code to the file:

import SwiftUI
import PhotosUI

// 1
struct ImagePickerView: UIViewControllerRepresentable {
  // 2
  @Binding var image: UIImage?
  @Binding var isPresented: Bool

  // 3
  func makeUIViewController(context: Context) -> some PHPickerViewController {
    var configuration = PHPickerConfiguration()
    configuration.filter = .images
    configuration.selectionLimit = 1
    let pickerView = PHPickerViewController(configuration: configuration)
    pickerView.delegate = context.coordinator
    return pickerView
  }

  // 4
  func updateUIViewController(
    _ uiViewController: UIViewControllerType, 
    context: Context
  ) { }

  // 5
  func makeCoordinator() -> Coordinator {
    Coordinator(parent: self)
  }

  // 6
  class Coordinator: PHPickerViewControllerDelegate {
    private let parent: ImagePickerView

    init(parent: ImagePickerView) {
      self.parent = parent
    }

    // 7
    func picker(
      _ picker: PHPickerViewController, 
      didFinishPicking results: [PHPickerResult]
    ) {
      parent.isPresented.toggle()
      guard
        let result = results.first,
        result.itemProvider.canLoadObject(ofClass: UIImage.self)
      else { 
        return 
      }
      result.itemProvider.loadObject(
        ofClass: UIImage.self
      ) { [weak self] image, error in
        guard error == nil else { return }
        DispatchQueue.main.async {
          self?.parent.image = image as? UIImage
        }
      }
    }
  }
}

Here’s a breakdown of the code:

  1. Declare a new UIViewControllerRepresentable to bridge PHPickerViewController with SwiftUI.
  2. Declare Binding properties to set the picked photo and to dismiss the picker view.
  3. Implement makeUIViewController(context:) to return a view controller used in SwiftUI. Further, you create a new instance of PHPickerConfiguration, which sets the filter to show only images and limit the selection to one picture. Then, you create an instance of PHPickerViewController with your configuration and set its delegate.
  4. Declare updateUIViewController(_:context:). This method is empty because you won’t update PHPickerViewController, but UIViewControllerRepresentable requires ImagePickerView to have this method.
  5. Create and returns a Coordinator as declared below.
  6. Declare a Coordinator to conform to PHPickerViewControllerDelegate. This class acts as the delegate and will receive the picked photo from PHPickerViewController.
  7. Implement picker(_:didFinishPicking:) to receive the image the user picked and save it to the property.

Now, open NewPictureView.swift and replace the following code:

Text("To Do Open Image Picker View")

With:

ImagePickerView(image: $store.image, isPresented: $imagePickerIsPresented)

Here, you start using ImagePickerView as the presented content when the user taps the select image button.

Build and run. Tap the Select Image button to open the new picker view and select a picture from the library.

A grid view with pictures from the user's library.

How cool is that? PHPickerViewController follows the layout of iOS’s Photos library, so you don’t have to worry about creating a custom UI for picking photos. It already filters the content to show only pictures and has built-in albums, multi-selection and even a search bar.

Not only that, but the user didn’t have to allow access to their library. That’s because PHPickerViewController runs on a different process than your app, allowing access only to the picture the user selects. This allows users to pick images they want without giving access to their full library.

Working with Location Services

Just like photos, the user’s location is a quite personal piece of data that not all users want to share with every app. Starting with iOS 14, apps have to ask permission to access the user’s location and also how often they can access this data. Instead of just allowing or denying access to their location, users can now choose to Always Allow or Allow While Using App. They can also choose to Allow Once. That way, iOS asks permission the next time the user opens the app.

Users can also decide the extent of the accuracy of their location data. Instead of giving a precise location, iOS gives an approximate location of the user, already enough for features that look for locations around them.

This helps give the user control over when and how the app accesses their location. The user has full control over this, with the ability to change each app authorization and accuracy whenever they want, using the Settings app.

Designing for Location Privacy

Some apps, such as Apple Maps, require full accuracy for some features to work properly. Precise location affects the estimated time of arrival, relevant places and even navigation.

These apps have to adapt their UI and features to give an optimal experience, either for when the user prefers using precise location or not.

While using precise location, Apple Maps uses a pulsating blue dot on the map to show the user’s location.

Apple Maps with a pulsating blue dot indicating the user's location

With precise location off, this dot turns into a shaded circular area, displaying a general location of the user. Also, notice the text at the top of the view indicating precise location is off.

Apple Maps with a shaded circular blue area indicating a general location of the user and a button at the top of the view explaining precise location is off

These UI changes indicate the state of accuracy on Apple Maps.

Other changes were made to features that require precise location. While precise location is off, Apple Maps Favorites don’t show an estimated time of arrival (ETA). Calculating an ETA requires precise data. An approximate location could compromise an ETA.

Favorites places displayed without ETA

While designing your app, you must keep in mind users might not want to share their precise location. Giving the user power to choose makes them feel safer and in control of their data.

Adding Location Data

Now that you know how important it is to be transparent with the user’s location data, it’s time to add location to the picture. To do this, you’ll add a button for asking authorization to access the user’s location and to show a map displaying their location.

Open NewPictureStore.swift and add the following under hasLocation:

private lazy var locationManager: CLLocationManager = {
  let manager = CLLocationManager()
  manager.delegate = self
  return manager
}()

This code adds a lazy property of CLLocationManager you’ll use to track the user’s location. Xcode triggers a compiler error because NewPictureStore does not conform to CLLocationManagerDelegate. You’ll fix that later, but first add the following properties after hasLocation:

@Published 
var locationAuthorizationStatus: CLAuthorizationStatus = .notDetermined
@Published 
var locationAccuracyAuthorization: CLAccuracyAuthorization = .reducedAccuracy

The first property tracks the app’s authorization status to use location services. The second property tracks the accuracy preferred for this app by the user.

The app’s authorization status can be either:

  1. .notDetermined: The user has not yet chosen to allow or prevent location data to this app.
  2. .restricted: The app is not authorized to use location services, not necessarily because the user denied access.
  3. .denied: The user denied access to location services or location services is disabled in Settings.
  4. .authorizedAlways: The app has the authorization to use location services whenever the app requires it, even in the background.
  5. .authorizedWhenInUse: The user has authorized use of location services only while the app is in the foreground.

The accuracy of location data can be either .fullAccess, when the app has authorization to the full accuracy data, or .reducedAccuracy, meaning the app has only the user’s approximate location.

Next, add the following method at the end of the class:

func updateLocationAndAccuracyStatus() {
  // 1
  locationAuthorizationStatus = locationManager.authorizationStatus
  // 2
  locationAccuracyAuthorization = locationManager.accuracyAuthorization
}

This method does two things:

  1. Updates the current location authorization status.
  2. Updates the current accuracy authorization.

You’ll use this method to update both properties whenever location or accuracy status changes.

Conforming to CLLocationManagerDelegate

To conform NewPictureStore to CLLocationManagerDelegate, add the following extension at the end of the file:

// MARK: - CLLocationManagerDelegate
extension NewPictureStore: CLLocationManagerDelegate {
  // 1
  func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
    updateLocationAndAccuracyStatus()
  }

  // 2
  func locationManager(
    _ manager: CLLocationManager, 
    didUpdateLocations locations: [CLLocation]
  ) {
    guard let userLocation = locations.first else { return }
    self.userLocation.center = userLocation.coordinate
  }
}

Here’s a breakdown of what’s happening in each method:

  1. CLLocationManager calls locationManagerDidChangeAuthorization(_:) whenever a change happens on the app’s location authorization. When that happens, you update both properties with updateLocationAndAccuracyStatus().
  2. CLLocationManager calls this method whenever new location data is available. Then, you update the user location property with the latest data.

This handles updating location data, location accuracy and location authorization. Now, you’ll add code to show the user’s location on a map and request authorization for said feature.

Showing User Location on Map

Add the following extension at the bottom of NewPictureStore.swift:

// MARK: - Location Authorization
extension NewPictureStore {
  // 1
  var locationIsDisabled: Bool {
    locationAuthorizationStatus == .denied ||
      locationAuthorizationStatus == .notDetermined ||
      locationAuthorizationStatus == .restricted
  }

  // 2
  var showAllowLocationButton: Bool {
    locationAuthorizationStatus == .notDetermined
  }

  // 3
  func requestLocationAuthorization() {
    locationManager.requestWhenInUseAuthorization()
  }

  // 4
  func startUpdatingUserLocation() {
    locationManager.startUpdatingLocation()
  }
}

The code above creates an extension with a couple of handy properties and methods. Here’s what they do:

  1. locationIsDisabled is a Boolean to disable the location section if the user has denied access to location data.
  2. showAllowLocationButton is a Boolean to show a button if iOS didn’t ask for location services permission yet.
  3. This method calls requestWhenInUseAuthorization() to show an alert requesting authorization to access location data.
  4. And finally, this method calls startUpdatingLocation() to start updating the user’s location data.

Now, open NewPictureView.swift, find the following line inside locationSection:

.disabled(true)

And replace it with this:

.disabled(store.locationIsDisabled)

This makes sure the location section will be disabled only if the user didn’t authorize access to location data.

Next, find // TODO: Add map here and add the following code below the comment:

if store.hasLocation {
  MapWithLocation(coordinates: $store.userLocation)
    .onAppear(perform: store.startUpdatingUserLocation)
  // TODO: Precise location off button
}

MapWithLocation is a custom SwiftUI view that has a Map and a Text with latitude and longitude. When it appears on the view, the modifier onAppear(perform:) calls startUpdatingUserLocation() to update the user’s location.

Finally, find // TODO: Add allow location data button here and add this code under:

if store.showAllowLocationButton {
  Button("Allow Location Data", action: store.requestLocationAuthorization)
}

This adds the button to ask for authorization for the app to access location data.

Build and run and tap the new button to allow location.

A form with a button to open an image picker view, field for caption and a location section with an allow button

Nothing happened! But check out the console in Xcode.

Xcode terminal displaying an error to add a description for using location when in use of the app.

To ask authorization to access location data, you need to add a string to Info.plist that iOS uses to explain to users why your app requires access to their location. That’s why nothing happens when you tap the button.

Adding When in Use Description to Info.plist

Open Info.plist and add the following key:

  • Privacy – Location When In Use Usage Description with a value of YourPrivacy uses your current location to add this data to your pictures.

This key allows iOS to ask for access to the user’s location whenever the user is using the app. If the app requires access to location data at all times, even in the background, you have to use Privacy – Location Always and When In Use Usage Description. iOS uses the value of this key as the reason on the alert when asking users for their location authorization.

Build and run the app again and try to allow location data once again.

Alert asking user to allow location access only once, while using the app or to deny access.

Success! iOS displays an alert asking permission for the user’s location. The user can choose either Allow Once, Allow While Using App or Don’t Allow.

“Allowing Once” will prompt the alert again the next time the user opens the app, whereas “Allow While Using App” allows the app to access the location data whenever the user is using the app.

Allow location data and the section toggle for location becomes enabled. Toggle the section to see the map with the user’s location.

Form with an image of a waterfall, a caption and a map with the user's location

Note: When running the app in the simulator, Xcode simulates a location for the device. If you want to test on other locations, you can go to DebugSimulate Location and select a specific location.

Handling Denied Authorization

So far so good. Users can select an image, add a caption and add their location data. But what happens if the user doesn’t allow location access?

Form with an image of waterfall, a caption and location section disabled

The view simply doesn’t have any feedback that the user previously denied access to location data. This can cause some confusion should the user later want to add location information. To fix this, you’ll add text explaining the user denied location data and a button for opening the Settings app to change the permission.

In NewPictureStore.swift, after showAllowLocationButton, add a new computed property:

var showOpenSettingsButton: Bool {
  locationAuthorizationStatus == .denied || 
    locationAuthorizationStatus == .restricted
}

This property returns true if the user denies access to location data. You’ll use it for showing the text and button for opening the Settings app.

Now, in NewPictureView.swift, find // TODO: Add open location settings button here and add the following code:

if store.showOpenSettingsButton {
  VStack(spacing: 16) {
    Text(locationDeniedText)
      .multilineTextAlignment(.center)
    Button("Open Location Settings", action: openLocationSettings)
      .font(.body.bold())
  }
}

This code adds a text explaining why they can’t add location and a button for opening the Settings app.

Build and run. Deny access to location data to see the new text and button.

Form with explanation why location is disabled and a button to take the user to the Settings app.

Note: If you allowed access to location data, open the Settings app, go to PrivacyLocation ServicesYourPrivacy and choose Never to see the new text and button.

Neat! Now users know why they don’t have access to the location section when they deny location data and also how to allow this if they want.

Handling accuracy

Now that users can allow access to their location, it’s time to handle accuracy. Like before, there’s no indication whether the user allows full accuracy or reduced accuracy. On Apple Maps, a button indicating full accuracy is off and asking for full accuracy helps users understand their data is not precise. You’ll add this button now.

Open Info.plist and add the following key as a dictionary:

  • Privacy – Location Temporary Usage Description Dictionary

Now, add the following key to the dictionary:

  • temporaryAccuracyLocation with the value of YourPrivacy uses location accuracy to better pinpoint the picture’s location.

This text describes the app’s reason for requesting full accuracy.

Back in NewPictureStore.swift, inside the Location Authorization extension, add the following code:

func requestTemporaryFullAccuracy() {
  locationManager.requestTemporaryFullAccuracyAuthorization(
    withPurposeKey: "temporaryAccuracyLocation"
  )
}

You call requestTemporaryFullAccuracyAuthorization(withPurposeKey:), passing the key you added in Info.plist, to show an alert asking for access to full accuracy with the reason you provided.

Finally, in NewPictureView.swift, add the following after // TODO: Precise location off button:

if store.locationAccuracyAuthorization == .reducedAccuracy {
  Button("Precise Location: Off", action: store.requestTemporaryFullAccuracy)
}

Build and run. Allow access to location data but leave precise location off to see the new button.

Alert requesting full accuracy location

Understanding iOS 14’s New Clipboard Prompt

To make iOS more transparent, Apple has focused on privacy-focused features already built into iOS. These features make apps a bit more transparent whenever they access data that the user might think is sensitive. The new Camera and Microphone indicators and Clipboard prompt help keep apps transparent when accessing this kind of data.

The clipboard has been available since iOS 3, and many users have been accustomed to it ever since its introduction on computers. However, nothing stood between an app accessing and tracking what the user has been copying and pasting. Apps have full access to read and write to the clipboard.

Starting with iOS 14, the system shows a new prompt whenever an app copies the content of the clipboard, even if it wasn’t an action from the user.

Form with a prompt at the top saying YourPrivacy pasted some text from Safari

That way, whenever an app reads from the clipboard, the user is aware of this and even knows where the content came from.

Understanding the New Camera and Microphone Indicators

The new indicators are also features to keep apps more transparent. Like the green light near the camera on MacBooks, iOS now displays a green circular dot at the top-right corner of the screen whenever an app uses the front-facing or rear camera.

Top view of the camera app with a green dot

Note: When the Differentiate Without Color setting is on, iOS uses a green square instead of a dot to indicate camera use.

And even after the app has stopped using the camera, you can still open the control center to see the name of the app that recently used the camera.

Top view of control center with text saying which app recently used the camera

The same is true for the microphone. Whenever an app uses the microphone, an orange dot will appear at the top-right corner of the screen.

Voice recorder app with an orange indicator at the top

These indicators keep users aware when an app accesses the camera and microphone, increasing transparency and awareness to the user and also preventing some embarrassing moments when leaving the camera on during a work call. :]

iOS 14.5’s New App Tracking Transparency

Until now, apps have had access to a unique device identifier to track user activity between apps and websites and to target specific ads, without any explicit permission from the user. Starting with iOS 14.5, Apple requires every app that tracks users to explicitly request authorization to access the device identifier, giving users control over their privacy.

Each app that tracks users must request this permission, leaving it completely up to users the choice of which apps they do or don’t want to share their activity with.

Note: Users can open the Settings app and see a list of apps that track user activity. They can change their permission and even deny tracking to all apps, even for new apps that didn’t ask for permission yet.

Requesting Permission to Track the User Activity

To access the unique device identifier, you first have to request tracking authorization from the user. To do that, you’ll use the new AppTrackingTransparency framework to ask for user authorization.

Open Info.plist and add the following key:

  • Privacy – Tracking Usage Description with a value of YourPrivacy tracks your data to provide personalized ads to you.

This key gives iOS the reason why the app wants to track the user. iOS uses this text when asking the user permission to track them.

Open AppMain.swift and add the following at the top of the file:

import AppTrackingTransparency

Now, add the following extension at the end of the file:

// MARK: - App Tracking Transparency and Advertising Identifier
extension AppMain {
  func showAppTrackingAlert() {
    ATTrackingManager.requestTrackingAuthorization { _ in
      // TODO: Print Advertising Identifier
    }
  }
}

Here, you call requestTrackingAuthorization(completionHandler:) to prompt an alert asking permission to track the user. Once the user accepts or denies, ATTrackingManager calls the completion handler with an AuthorizationStatus.

The app’s authorizations status can be one of the following:

  1. .notDetermined: The user has not yet received a request to allow access to app tracking.
  2. .denied: The user has denied app tracking for the app or has denied app tracking for every app in the Settings app.
  3. .restricted: There’s a restriction to track the app. If the status is .restricted, the system won’t present the alert requesting authorization.
  4. .authorized: The user has authorized tracking for your app.

Once the user has allowed tracking, the system returns a unique identifier for the device that you use to track the user activity.

Note: This advertising identifier returns as all zeroes in some specific conditions:
  1. In the simulator.
  2. On macOS.
  3. On iOS 14.5 if you haven’t requested app tracking permission yet, or if the user has denied app tracking.
  4. When a profile or configuration restricts app tracking.

Retrieving the Advertising Identifier

Using the AdSupport framework, you can access the unique device identifier to track the user.

Still in AppMain.swift, add the following at the top of the file:

import AdSupport

Next, add the following code at the end of the class extension:

func printAdvertisingIdentifier() {
  // 1
  guard ATTrackingManager.trackingAuthorizationStatus == .authorized else { 
    return 
  }
  // 2
  let advertisingIdentifier = ASIdentifierManager.shared()
    .advertisingIdentifier
  // 3
  print("Advertising Identifier: \(advertisingIdentifier)")
}

Here’s what’s happening:

  1. Make sure the user has allowed app tracking.
  2. Use ASIdentifierManager shared instance to retrieve the advertising identifier.
  3. Use this advertising identifier to send to a third party or a tracking service. Here, you simply print the identifier to the console.

Next, find // TODO: Print Advertising Identifier and replace with the following:

printAdvertisingIdentifier()

This calls the method for retrieving and printing the advertising identifier on the console.

Finally, add the following modifiers after // TODO: Add onAppear(perform:) for App Tracking:

.onAppear(perform: printAdvertisingIdentifier)
.onAppear(perform: showAppTrackingAlert)

This code adds two modifiers. The first tries to print the advertising identifier. If the user has not yet authorized app tracking, nothing happens. The second tries to request authorization for tracking from the user.

Build and run to see the app tracking alert.

Alert asking app to track the user activity for targeted ads

After allowing tracking, take a look at the console to see the advertising identifier.

Xcode console with the device advertising identifier

Where to Go From Here?

Download the final project by clicking the Download Materials button at the top or bottom of the tutorial.

In this tutorial, you learned how Apple is improving user privacy by adding new permissions and features to make apps more transparent and give control back to users. Apple strives to keep users’ privacy safe and secure.

If you want to dive more deeply into new restrictions and permissions for Local Networks, look at Apple’s session Support local network privacy in your app from WWDC 2020.

We hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!

Average Rating

5/5

Add a rating for this content

3 ratings

More like this

Contributors

Comments