I've been stuck on a bug since last Monday, so I'm asking for help now ..
Contacts and Micriohpone request access does not work on iOS 9. I use this piece of code in order to request access to contacts :
let contactsStore = CNContactStore()
func requestAccess(completionHandler: #escaping (Permission) -> ()) {
self.contactsStore.requestAccess(for: .contacts, completionHandler: { (granted, error) in
if granted {
completionHandler(.granted)
} else {
completionHandler(.denied)
}
})
}
This function is called, no problem with that, the problem is it always return .denied and an error set with "Access denied", even though no alert has been shown to the user. The same with microphone.
The key 'Privacy - Contacts Usage Description' is present in my Info.plist
EDIT :
I also know that when the user denied once the usage it is not shown anymore, but the other problem is that there is not "switch" in the settings section of the app. I tried to restore the device (Working on simulator as I don't have a real iOS 9 device), but still the same behaviour.
This code works perfeclty on iOS 10 and iOS 11. But no chance on iOS 9
If you could help me on this issue that would be awesome.
Thanks !
I tried this on 9.3 in the simplest way imaginable, and I did get a prompt:
import UIKit
import Contacts
class ViewController: UIViewController {
let contactsStore = CNContactStore()
override func viewDidAppear(_ animated: Bool) {
DispatchQueue.main.async {
self.requestAccess(completionHandler: { (permission) in
print("The user said \(permission)")
})
}
}
func requestAccess(completionHandler: #escaping (Permission) -> ()) {
self.contactsStore.requestAccess(for: .contacts, completionHandler: { (granted, error) in
if granted {
completionHandler(.granted)
} else {
completionHandler(.denied)
}
})
}
}
enum Permission {
case granted
case denied
}
This works fine. I think the issue is that you already denied it.
The only solutions are:
Change the bundle id, which will make your app act as a different one
Reset your device/simulator (easier if a simulator of course)
Change the privacy setting from Off to On
For end users, I've seen the UI prompt the user to change the setting if they see "denied".
You can do that like this:
self.requestAccess(completionHandler: { (permission) in
print("The user said \(permission)")
if ( permission == .denied ) {
let urlStr = UIApplicationOpenSettingsURLString
if let url = URL(string:urlStr) {
UIApplication.shared.openURL(url)
}
}
})
Related
I am in the process of implementing UMP SDK into my iOS app. I have setup the GDPR and IDFA messages in the Google AdMob dashboard's Privacy and Messaging section. I am having trouble getting the GDPR message to show up. The IDFA and iOS' ATT messages work perfectly.
Below is the code that I am using. I have tested this on both simulator and physical device. Also, I am located in the EU.
static func trackingConsentFlow(completion: #escaping () -> Void) {
let umpParams = UMPRequestParameters()
let debugSettings = UMPDebugSettings()
debugSettings.geography = UMPDebugGeography.EEA
umpParams.debugSettings = debugSettings
umpParams.tagForUnderAgeOfConsent = false
UMPConsentInformation
.sharedInstance
.requestConsentInfoUpdate(with: umpParams,
completionHandler: { error in
if error != nil {
print("MYERROR #1 \(String(describing: error))")
completion()
} else {
let formStatus = UMPConsentInformation.sharedInstance.formStatus
print("FORM STATUS: \(formStatus)")
if formStatus == .available {
loadForm(completion)
} else {
completion()
}
}
})
}
private static func loadForm(_ completion: #escaping () -> Void) {
UMPConsentForm.load(completionHandler: { form, loadError in
if loadError != nil {
print("MYERROR #2 \(String(describing: loadError))")
completion()
} else {
print("CONSENT STATUS: \(UMPConsentInformation.sharedInstance.consentStatus)")
if UMPConsentInformation
.sharedInstance.consentStatus == .required {
guard let rootViewController = UIApplication.shared.currentUIWindow()?.rootViewController else {
return completion()
}
form?.present(from: rootViewController, completionHandler: { dismissError in
if UMPConsentInformation
.sharedInstance.consentStatus == .obtained {
completion()
}
})
}
}
})
}
Just to be clear:
With this code I am able to show the IDFA message, after which the AppTrackingTransparency alert is shown. But I am expecting to also see the GDPR consent form.
For anyone wondering the same thing. The GDPR message was not appearing because I had not finalised my Admob account setup. I had not added my payment method. After adding it (and sending the app for review in Admob) the GDPR message started to appear.
I was using NSFaceIDUsageDescription in my app and it was working. I deleted my app from my device and and re-uploaded (plugging my device into my mac and running from xcode) it and now I don't get the alert that my app would like to use FaceID, how come the alert is not appearing anymore? This is preventing me from using FaceID in my app.
class TouchIDAuth {
let context = LAContext()
func canEvaluatePolicy() -> Bool {
return context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
}
func authenticateUser(completion: #escaping (NSNumber?) -> Void) {
guard canEvaluatePolicy() else {
completion(0)
return
}
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Logging in with Touch ID") { (success, evaluateError) in
if success {
DispatchQueue.main.async {
completion(nil)
}
} else {
let response: NSNumber
switch evaluateError?._code {
case Int(kLAErrorAuthenticationFailed):
response = 2
case Int(kLAErrorUserCancel):
response = 3
case Int(kLAErrorUserFallback):
response = 4
default:
response = 1
}
completion(response)
}
}
}
}
And when I do this:
let touchMe = TouchIDAuth()
print(touchMe.canEvaluatePolicy())
The print returns false.
Is this an issue with my device? Or with NSFaceIDUsageDescription?
When your device exceeds the limit of incorrect attempts it usually returns false.
Try locking your device, then unlocking with face/touch ID and it starts working again in your app.
It should also return an error code why it is failling for evaluateError
Hope this was your case and solves the issue.
I have got a file upload system attached my my website that works perfect on the mobile safari. I can access the iCloud, Photo Libraray, and Camera.
However after I implemented it into the wkWebView in our mobile app the upload system no longer works.
When I load the wkWebView and click on the button it brings up the options of where I want to grab the file from. I select Photo Library and it closes the webview and takes me back to my first viewController.
Here is the webview code:
//Camera
AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo) { response in
if response {
//access granted
} else {
}
}
//Photos
let photos = PHPhotoLibrary.authorizationStatus()
if photos == .notDetermined {
PHPhotoLibrary.requestAuthorization({status in
if status == .authorized{
//...
} else {}
})
}
let requestObj = URLRequest(url: URL(string: "https://example.com/chat.php?use=\(userId)")!)
corepage.load(requestObj);
}
I also have added the information to the info.plist as well. However, this didn't solve my issue of the page reverting back to the main viewController when clicking any of the options of uploading a file.
Thanks Guys!
I have gotten it figured out:
In the viewcontroller that has my webview I added:
override func dismiss(animated flag: Bool, completion: (() -> Void)?) {
if (self.presentedViewController != nil) {
super.dismiss(animated: flag, completion: completion)
}
}
This prevented the dimissal issue.
I would like to know if you can set the code that enables the accessory programmatically? I in this moment use:
import UIKit
import Foundation
import HomeKit
let homeManager = HMHomeManager()
func addAccessory () {
if let home = homeManager.primaryHome {
for room in home.rooms {
if room.name == "Kitchen" {
homeManager.primaryHome?.addAccessory(accessory, completionHandler: { (error) -> Void in
if error != nil {
print("ERROR 1 \(error?.localizedDescription)")
}else {
self.homeManager.primaryHome?.assignAccessory(accessory, toRoom: room, completionHandler: { (error) -> Void in
if error != nil {
print("ERROR 2 \(error?.localizedDescription)")
} else {
print("Accessory Add successfully")
}}}}
}
I would not use the usual view offered by Apple to enter the code.
At this point in time, there is no reference in HomeKit documentation to being able to override the default screens that are displayed when using the HMHome addAccessory method.
I was looking for this myself and have not found anything and have also posed the question on the Apple Developer's HomeKit forum. If I hear differently on the forums, I'll post back here.
When the app tries to access the Camera API's in iOS than an OS level alertview is shown.
The user here has to allow access to camera or disable the access.
My question is how can I get notified of the selection made by the user..?
Say he selected don't allow access than is there any notification raised which I can use in my app..?
Any help is appreciated.
Instead of letting the OS show the alert view when the camera appears, you can check for the current authorization status, and request for the authorization manually. That way, you get a callback when the user accepts/rejects your request.
In swift:
let status = AVCaptureDevice.authorizationStatusForMediaType(AVMediaTypeVideo)
if status == AVAuthorizationStatus.Authorized {
// Show camera
} else if status == AVAuthorizationStatus.NotDetermined {
// Request permission
AVCaptureDevice.requestAccessForMediaType(AVMediaTypeVideo, completionHandler: { (granted) -> Void in
if granted {
// Show camera
}
})
} else {
// User rejected permission. Ask user to switch it on in the Settings app manually
}
If the user has previously rejected the request, calling requestAccessForMediaType will not show the alert and will execute the completion block immediately. In this case, you can choose to show your custom alert and link the user to the settings page. More info on this here.
Taken from Kens answer, I've created this Swift 3 protocol to handle permission access:
import AVFoundation
protocol PermissionHandler {
func handleCameraPermissions(completion: #escaping ((_ error: Error?) -> Void))
}
extension PermissionHandler {
func handleCameraPermissions(completion: #escaping ((_ error: Error?) -> Void)) {
let status = AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeVideo)
switch status {
case .authorized:
completion(nil)
case .restricted:
completion(ClientError.noAccess)
case .notDetermined:
AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo) { granted in
if granted {
completion(nil)
} else {
completion(ClientError.noAccess)
}
}
case .denied:
completion(ClientError.noAccess)
}
}
}
You can then conform to this protocol and call it in your class like so:
handleCameraPermissions() { error in
if let error = error {
//Denied, handle error here
return
}
//Allowed! As you were