I'm trying to implement the App Tracking Transparency framework, and I'm stuck, how do I load non-personalised content when the user denies the prompt.
if #available(iOS 14.5, *) {
ATTrackingManager.requestTrackingAuthorization { (status) in
switch status {
case .denied:
// What do I do here?
//GADMobileAds.sharedInstance().start(completionHandler: nil)
case .restricted, .notDetermined, .authorized:
GADMobileAds.sharedInstance().start(completionHandler: nil)
#unknown default: break
}
}
} else {
GADMobileAds.sharedInstance().start(completionHandler: nil)
}
You don't need to do anything different. If the user denies tracking then the ad framework will simply receive 0000 for the IDFA. This prevents them from identifying the user and tracking them or providing personalised ads.
if #available(iOS 14.5, *) {
ATTrackingManager.requestTrackingAuthorization { (status) in
ADMobileAds.sharedInstance().start(completionHandler: nil)
}
} else {
GADMobileAds.sharedInstance().start(completionHandler: nil)
}
You could use the .denied status to show an alert that asks them to go into settings and allow it, but don't do that.
Related
I don't know can I use this functionality in my UI tests on iOS, but I try it, an have problem with this.
In my UI tests I can choose Allow tracking for my app or I can decline tracking, but after all these actions, I want checkout status IDFA via ATTrackingManager.AuthorizationStatus, but this method always returns notDetermined. If I go to Settings > Privacy > Tracking, here I see that settings applied correctly (switch Allow App To Request To Track is on and switch for my app in right state (on or off)).
I don't have any idea why I recieve wrong AuthorizationStatus.
Here is my code in my XCTestCase:
import AppTrackingTransparency
enum TrackingStatus {
case authorized
case denied
case notDetermined
}
func map(_ status: ATTrackingManager.AuthorizationStatus) -> TrackingStatus {
switch ATTrackingManager.trackingAuthorizationStatus {
case .notDetermined:
return .notDetermined
case .authorized:
return .authorized
default:
return .denied
}
}
func advertisingTrackingStatusCheckout(status: TrackingStatus) {
print("IDFA status: \(ATTrackingManager.trackingAuthorizationStatus)")
var currentTrackingStatus: TrackingStatus {
return map(ATTrackingManager.trackingAuthorizationStatus)
}
guard currentTrackingStatus == status else {
XCTFail("IDFA status: \(currentTrackingStatus), expected: \(status)")
return
}
}
After settings IDFA status in my UI test, i call this method, ex. advertisingTrackingStatusCheckout(status: TrackingStatus.denied)
But it always returns notDetermined.
It behaviors have only one exception: If I manually set switch Allow App To Request To Track to off-state, calling the ATTrackingManager.trackingAuthorizationStatus will returns denied.
Delete the App, And call your function in sceneDidBecomeActive with delay. So once your app become active then it will shown. Am facing the same issue now its resolved. Like this
func sceneDidBecomeActive(_ scene: UIScene) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.requestPermission()
}
}
func requestPermission() {
if #available(iOS 14, *) {
ATTrackingManager.requestTrackingAuthorization { status in
switch status {
case .authorized:
// Tracking authorization dialog was shown
// and we are authorized
print("Authorized Tracking Permission")
// Now that we are authorized we can get the IDFA
case .denied:
// Tracking authorization dialog was
// shown and permission is denied
print("Denied Tracking Permission")
case .notDetermined:
// Tracking authorization dialog has not been shown
print("Not Determined Tracking Permission")
case .restricted:
print("Restricted Tracking Permission")
#unknown default:
print("Unknown Tracking Permission")
}
}
} else {
// Fallback on earlier versions
}
}
I'm creating an iOS application in Swift and in the AppDelegate I insert the following code in the didFinishLaunchingWithOptions to ask the notifications permissions
let center = UNUserNotificationCenter.current()
center.delegate = self
// set the type as sound or badge
center.requestAuthorization(options: [.sound,.alert,.badge]) { (granted, error) in
guard granted else { return }
DispatchQueue.main.async(execute: {
application.registerForRemoteNotifications()
})
}
Meanwhile, in the ViewDidLoad, I ask the user the microphone permissions in this way:
func checkPermission()
{
switch AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeAudio)
{
case .authorized:
print("ok2")
self.addButton()
self.voice()
case .notDetermined:
AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeAudio, completionHandler: { granted in
if granted {
print("ok1")
self.addButton()
self.voice()
else {
print("ko")
}
})
case .denied:
print("ko")
case .restricted:
return
}
}
The problem is:
After accepting the microphone permissions, I accept the notification permissions, but, after that, the code in the ViewController does not proceed (methods addButton and voice are not executed).
Could you help me?
Thank you so much in advance
Maybe the problem is caused by threading issues: The completion handler in AVCaptureDevice.requestAccess is executed in an arbitrary worker thread. If you do UI stuff here (like adding a button), this has to be done in the main thread, e.g.
AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeAudio) { granted in
if granted {
print("ok1")
DispatchQueue.main.async { [unowned self] in
self.addButton()
self.voice()
}
else {
print ("ko")
}
}
But I wonder why UIKit does not crash (or at least complain) in your case.
I have app in which I have to add events in default calendar which worked fine until iOS 10. Now in iOS 10 it is not granting access. I have set use legacy swift language version to yes. My code is
let eventStore = EKEventStore()
switch EKEventStore.authorizationStatusForEntityType(EKEntityType.Event) {
case .Authorized:
//access
case .Denied:
print("Access denied")
case .NotDetermined:
eventStore.requestAccessToEntityType(EKEntityType.Event, completion:
{[weak self] (granted: Bool, error: NSError?) -> Void in
if granted {
//access
} else {
print("Access denied")
}
})
default:
print("Case Default")
}
While debug my app is crashing at eventStore.requestAccessToEntityType in Xcode 8.
My app is live and I need to solve it. Any help is appropriated. Thanks in advance.
On iOS 10 builds, you need to set a description message for the permission alert on Info.plist
Important: An iOS app linked on or after iOS 10.0 must include in its
Info.plist file the usage description keys for the types of data it
needs to access or it will crash. To access Reminders and Calendar
data specifically, it must include NSRemindersUsageDescription and
NSCalendarsUsageDescription, respectively.
from Apple Docs
let eventStore = EKEventStore()
switch EKEventStore.authorizationStatus(for: .event) {
case .authorized: break
//access
case .denied:
print("Access denied")
case .notDetermined:
eventStore.requestAccess(to: .event, completion:
{[weak self] (granted: Bool, error: Error?) -> Void in
if granted {
//access
} else {
print("Access denied")
}
})
default:
print("Case Default")
}
With XCode 8 and swift 3, this is the new format. Did you test your app on iOS 10.
I have push notifications in my app. Whenever the app is launched, I would like to check whether the user has enabled push notification for my application.
I do it this way :
let notificationType = UIApplication.sharedApplication().currentUserNotificationSettings()!.types
if notificationType == UIUserNotificationType.None {
print("OFF")
} else {
print("ON")
}
If push notifications are disabled by the user, is there any way to activate this from my app?
Or is there any alternative to send user to the push notification settings (Settings - Notifications - AppName)?
There is no way to change settings from the app. But you can lead user to application specific system settings using this code.
extension UIApplication {
class func openAppSettings() {
UIApplication.sharedApplication().openURL(NSURL(string: UIApplicationOpenSettingsURLString)!)
}
}
Updated for Swift 3.0
extension UIApplication {
class func openAppSettings() {
UIApplication.shared.openURL(URL(string: UIApplicationOpenSettingsURLString)!)
}
}
Updated for iOS 10+ & Swift 5+
extension UIApplication {
#objc class func openAppSettings() {
shared.open(URL(string: openSettingsURLString)!,
options: [:],
completionHandler: nil)
}
}
For iOS 10 or higher
Checking if push notifications have been enabled for your app has changed dramatically for Swift 3. Use this instead of the above examples if you are using Swift 3.
let center = UNUserNotificationCenter.current()
center.getNotificationSettings { (settings) in
if(settings.authorizationStatus == .authorized)
{
print("Push authorized")
}
else
{
print("Push not authorized")
}
}
Here is Apple's documentation on how to further refine your check: https://developer.apple.com/reference/usernotifications/unnotificationsettings/1648391-authorizationstatus
This is the Swift 3 version where you can check whether notifications are enabled or disabled.
let notificationType = UIApplication.shared.currentUserNotificationSettings?.types
if notificationType?.rawValue == 0 {
print("Disabled")
} else {
print("Enabled")
}
Swift 4 version of salabaha's answer
extension UIApplication {
class func openAppSettings() {
UIApplication.shared.open(URL(string: UIApplicationOpenSettingsURLString)!, options: [:], completionHandler: {enabled in
// ... handle if enabled
})
}
Here is the most thorough way, as of iOS12 and Swift 5:
_ = UNUserNotificationCenter.current().getNotificationSettings { (settings) in
switch settings.authorizationStatus {
case .notDetermined:
<#code#>
case .denied:
<#code#>
case .authorized:
<#code#>
case .provisional:
<#code#>
#unknown default:
<#fatalError()#>
}
}
Short answer:
No, if a user has denied your request for push notifications you cannot ask them again, you should point them to the settings page, you can do this with an alert informing them of the steps and two options "Ok" and "Go to settings", this is where you'd use Roman's code.
Long Answer:
You should only ask the user for push notifications when you absolutely need to AND if you're going to ask it helps to preface with a little view explaining why you want/need to send push notifications, this is a great TechCrunch article that will help you get better retention/acceptance from your users - http://techcrunch.com/2014/04/04/the-right-way-to-ask-users-for-ios-permissions/
I am developing a very simple video app. I use the official control: UIImagePickerController.
Here is the problem. When presenting the UIImagePickerController for the first time, the iOS will ask for the permission. The user can click yes or no. If the user clicks no, the control is not dismissed. Instead, if the user keeps clicking the start button, the timers go on while the screen is always black, and the user can't stop the timers or go back. The only thing the user can do is to kill the app. The next time the UIImagePickerController is presented, it is still a black screen and the user can't go back if clicking start.
I was wondering if it's a bug. Is there any way we can detect the permission of the camera so that we can decide to show the UIImagePickerController or not?
Check the AVAuthorizationStatus and handle the cases properly.
NSString *mediaType = AVMediaTypeVideo;
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType];
if(authStatus == AVAuthorizationStatusAuthorized) {
// do your logic
} else if(authStatus == AVAuthorizationStatusDenied){
// denied
} else if(authStatus == AVAuthorizationStatusRestricted){
// restricted, normally won't happen
} else if(authStatus == AVAuthorizationStatusNotDetermined){
// not determined?!
[AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) {
if(granted){
NSLog(#"Granted access to %#", mediaType);
} else {
NSLog(#"Not granted access to %#", mediaType);
}
}];
} else {
// impossible, unknown authorization status
}
Swift 4 and newer
Make sure to:
import AVFoundation
The code below checks for all possible permission states:
let cameraMediaType = AVMediaType.video
let cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: cameraMediaType)
switch cameraAuthorizationStatus {
case .denied: break
case .authorized: break
case .restricted: break
case .notDetermined:
// Prompting user for the permission to use the camera.
AVCaptureDevice.requestAccess(for: cameraMediaType) { granted in
if granted {
print("Granted access to \(cameraMediaType)")
} else {
print("Denied access to \(cameraMediaType)")
}
}
}
Since iOS 10 you need to specify
NSCameraUsageDescription key in your Info.plist to be able ask for camera access, otherwise your app will crash at runtime. See APIs Requiring Usage Descriptions.
An interesting sidenote from Apple Developer forum:
The system actually kills your app if the user toggles your app's
access to camera in Settings. The same applies to any protected
dataclass in the Settings→Privacy section.
Swift Solution
extension AVCaptureDevice {
enum AuthorizationStatus {
case justDenied
case alreadyDenied
case restricted
case justAuthorized
case alreadyAuthorized
case unknown
}
class func authorizeVideo(completion: ((AuthorizationStatus) -> Void)?) {
AVCaptureDevice.authorize(mediaType: AVMediaType.video, completion: completion)
}
class func authorizeAudio(completion: ((AuthorizationStatus) -> Void)?) {
AVCaptureDevice.authorize(mediaType: AVMediaType.audio, completion: completion)
}
private class func authorize(mediaType: AVMediaType, completion: ((AuthorizationStatus) -> Void)?) {
let status = AVCaptureDevice.authorizationStatus(for: mediaType)
switch status {
case .authorized:
completion?(.alreadyAuthorized)
case .denied:
completion?(.alreadyDenied)
case .restricted:
completion?(.restricted)
case .notDetermined:
AVCaptureDevice.requestAccess(for: mediaType, completionHandler: { (granted) in
DispatchQueue.main.async {
if granted {
completion?(.justAuthorized)
} else {
completion?(.justDenied)
}
}
})
#unknown default:
completion?(.unknown)
}
}
}
And then in order to use it you do
AVCaptureDevice.authorizeVideo(completion: { (status) in
//Your work here
})
As an addition to the answer from #Raptor the following should be mentioned. You may receive the following error starting with iOS 10: This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes.
To fix this, make sure you handle the results from the main thread as follows (Swift 3):
private func showCameraPermissionPopup() {
let cameraMediaType = AVMediaTypeVideo
let cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(forMediaType: cameraMediaType)
switch cameraAuthorizationStatus {
case .denied:
NSLog("cameraAuthorizationStatus=denied")
break
case .authorized:
NSLog("cameraAuthorizationStatus=authorized")
break
case .restricted:
NSLog("cameraAuthorizationStatus=restricted")
break
case .notDetermined:
NSLog("cameraAuthorizationStatus=notDetermined")
// Prompting user for the permission to use the camera.
AVCaptureDevice.requestAccess(forMediaType: cameraMediaType) { granted in
DispatchQueue.main.sync {
if granted {
// do something
} else {
// do something else
}
}
}
}
}
Specify NSCameraUsageDescription key in Info.plist first.
Then check AVAuthorizationStatus if Authorised then present the UIImagePickerController. It will work.
Swift: Using AVFoundation
Add AVFoundation to Target -> Build Phases -> Link Binary with Libraries.
import AVFoundation on ViewController.
On Info.plist, Add the following:
On View Controller:
#IBAction func cameraButtonClicked(sender: AnyObject) {
let authorizationStatus = AVCaptureDevice.authorizationStatusForMediaType(AVMediaTypeVideo)
print(authorizationStatus.rawValue)
if AVCaptureDevice.authorizationStatusForMediaType(AVMediaTypeVideo) == AVAuthorizationStatus.Authorized{
self.openCameraAfterAccessGrantedByUser()
}
else
{
print("No Access")
dispatch_async(dispatch_get_main_queue()) { [unowned self] in
AVCaptureDevice.requestAccessForMediaType(AVMediaTypeVideo, completionHandler: { (granted :Bool) -> Void in
if granted == true
{
// User granted
self.openCameraAfterAccessGrantedByUser()
}
else
{
// User Rejected
alertToEncourageCameraAccessWhenApplicationStarts()
}
});
}
}
//Open camera
func openCameraAfterAccessGrantedByUser()
{
if(UIImagePickerController .isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera)){
self.cameraAndGalleryPicker!.sourceType = UIImagePickerControllerSourceType.Camera
cameraAndGalleryPicker?.delegate = self
cameraAndGalleryPicker?.allowsEditing = false
cameraAndGalleryPicker!.cameraCaptureMode = .Photo
cameraAndGalleryPicker!.modalPresentationStyle = .FullScreen
presentViewController(self.cameraAndGalleryPicker!, animated: true, completion: nil)
}
else
{
}
}
//Show Camera Unavailable Alert
func alertToEncourageCameraAccessWhenApplicationStarts()
{
//Camera not available - Alert
let cameraUnavailableAlertController = UIAlertController (title: "Camera Unavailable", message: "Please check to see if it is disconnected or in use by another application", preferredStyle: .Alert)
let settingsAction = UIAlertAction(title: "Settings", style: .Destructive) { (_) -> Void in
let settingsUrl = NSURL(string:UIApplicationOpenSettingsURLString)
if let url = settingsUrl {
dispatch_async(dispatch_get_main_queue()) {
UIApplication.sharedApplication().openURL(url)
}
}
}
let cancelAction = UIAlertAction(title: "Okay", style: .Default, handler: nil)
cameraUnavailableAlertController .addAction(settingsAction)
cameraUnavailableAlertController .addAction(cancelAction)
self.window?.rootViewController!.presentViewController(cameraUnavailableAlertController , animated: true, completion: nil)
}