Change view once contact permission is granted - swift

At the moment, I am able to successfully ask the user for their permission to access their Contact information. I am handling this through a switch statement like so:
func requestContactPermissions() {
let store = CNContactStore()
var authStatus = CNContactStore.authorizationStatus(for: .contacts)
switch authStatus {
case .restricted:
print("User cannot grant permission, e.g. parental controls in force.")
exit(1)
case .denied:
print("User has explicitly denied permission.")
print("They have to grant it via Preferences app if they change their mind.")
exit(1)
case .notDetermined:
print("You need to request authorization via the API now.")
store.requestAccess(for: .contacts) { success, error in
if let error = error {
print("Not authorized to access contacts. Error = \(String(describing: error))")
exit(1)
}
if success {
print("Access granted")
}
}
case .authorized:
print("You are already authorized.")
#unknown default:
print("unknown case")
}
}
In the .notDetermined case, this is opening the dialog where I can either click no or yes, granting or denying the application access. This is fine and expected.
What I am looking to do, is change the view if the user clicks yes. Right now, I have the requestContactPermissions function within a button like so:
Button(action: {
withAnimation {
// TODO: Screen should not change until access is successfully given.
requestContactPermissions()
// This is where the view change is occurring.
self.loginSignupScreen = .findFriendsResults
}
})
How might I add in the logic to have the view change once the user has granted the application access to their contacts?

add a completion to the requestContactPermissions function something like this (I trimmed the irrelevant parts for the answer):
func requestContactPermissions(completion: #escaping (Bool) -> ()) {
let store = CNContactStore()
var authStatus = CNContactStore.authorizationStatus(for: .contacts)
switch authStatus {
case .notDetermined:
print("You need to request authorization via the API now.")
store.requestAccess(for: .contacts) { success, error in
if let error = error {
print("Not authorized to access contacts. Error = \(String(describing: error))")
exit(1)
//call completion for failure
completion(false)
}
if success {
//call completion for success
completion(true)
print("Access granted")
}
}
}
}
and then you can determine inside the closure whether the user granted permission or not:
Button(action: {
withAnimation {
// TODO: Screen should not change until access is successfully given.
requestContactPermissions { didGrantPermission in
if didGrantPermission {
//this is the part where you know if the user granted permission:
// This is where the view change is occurring.
self.loginSignupScreen = .findFriendsResults
}
}
}
})

Related

User Not Deleted In Firebase

I have an app that allows a user to sign in and sign out. As per App Store rules, I have to allow a user to be able to delete their account. I am using the default function from Firebase to delete account:
func deleteUser() {
let user = Auth.auth().currentUser
user?.delete { error in
if let error = error {
print("Error with deleting user")
} else {
print("User Deleted")
try? Auth.auth().signOut()
self.userisLoggedIn = false
}
}
}
But when I check my firebase auth, the user is still there. I still get the User Deleted print dialouge.
Button to fix the view:
Button {
do {
try deleteUser()
} catch {
print("error with deleting user")
}
} label: {
Text("Delete My Account")
}.buttonStyle(.bordered)

How can I get my app to wait for a permission request to complete?

My updated code is below, added a semaphore, and the app still blows through the AVCaptureDevice.authorizationStatus part and keeps running.
However, if I declare the semaphore with 0, then the first semaphore.wait() is successful, and the program freezes because the userAlert permission box never pops up.
So am having a tough time figuring out what the issue is here.
print ("going in...")
let semaphore = DispatchSemaphore(value: 1 )
DispatchQueue.global(qos: .userInitiated).async {
let mediaAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .audio)
switch mediaAuthorizationStatus {
case .denied:
print (".denied")
case .authorized:
print ("authorized")
case .restricted:
print ("restricted")
case .notDetermined:
print("Need to ask user")
semaphore.wait()
AVCaptureDevice.requestAccess(for: .audio, completionHandler: { (granted: Bool) in
if granted {
semaphore.signal()
} else {
semaphore.signal()
}
})
#unknown default:
print("unknown")
}
print ("\(semaphore.debugDescription)")
}
semaphore.wait()
print ("and we're out")
Misusing DispatchQueue to force an asynchronous task to become synchronous is a very bad practice.
Either use a completion handler
func avAuthorization(completion : #escaping (Bool) -> Void)
{
let mediaType = AVMediaType.audio
let mediaAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: mediaType)
switch mediaAuthorizationStatus {
case .denied, .restricted: completion(false)
case .authorized: completion(true)
case .notDetermined:
AVCaptureDevice.requestAccess(for: .audio) { granted in
completion(granted)
}
}
}
Or in Swift 5.5+ an async function
func avAuthorization() async -> Bool
{
let mediaType = AVMediaType.audio
let mediaAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: mediaType)
switch mediaAuthorizationStatus {
case .denied, .restricted: return false
case .authorized: return true
case .notDetermined: return await AVCaptureDevice.requestAccess(for: .audio)
}
}
I think you are already on the main thread and trying to DispatchQueue.main.sync from the Main thread. Check it like this.
if Thread.isMainThread {
// you are already on the main thread
} else {
DispatchQueue.main.sync {
// do stuff
}
}
without waiting for the iOs permission alert to pop up and complete
Great line, partially describing possible solution (imho).
AFAIK all UIAlertController-related routines are richly flavoured with completions. I'd try to put recording logic trigger inside the completion handler of method that presents the alert itself.

didRegisterForRemoteNotificationsWithDeviceToken not called upon user granting permission to receive push notifications

I've setup didRegisterForRemoteNotificationsWithDeviceToken in AppDelegate like so:
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let token = deviceToken.map { String(format:"%02.2hhx", $0) }.joined()
print("didRegisterForRemoteNotificationsWithDeviceToken got called - Token is: \(token)")
// delegate might get called even before an authtoken has been set for the user. Return in such cases:
guard UserDefaults.standard.string(forKey: "authtoken") != nil else {return}
// otherwise continue:
if (token != UserDefaults.standard.string(forKey: "apnsToken")) {
self.apiService.setAPNSToken(apnsToken: token, completion: {result in
switch result {
case .success(let resultString):
DispatchQueue.main.async {
UserDefaults.standard.set(token, forKey: "apnsToken")
print(resultString, " Token is: \(token)")
}
case .failure(let error):
print("An error occured \(error.localizedDescription)")
}
})
} else {
print("User is registered for Push Notifications. Token did not change and is: \(token)")
}
}
I'm asking for user permission to sent push Notifications in one of my view controllers like so:
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
DispatchQueue.main.async {
if (granted) {
UserDefaults.standard.set(true, forKey: "pushNotificationsEnabled")
}
print("permission granted?: \(granted)")
}
}
All pretty standard I suppose. My confusion / problem is this:
didRegisterForRemoteNotificationsWithDeviceToken does not get called upon user interaction - i.e. I would expect it to be called as soon as the user taps on "allow push notifications" so that the apns token would be stored on my back end. But it doesn't.
When I close the app down and start it again, didRegisterForRemoteNotificationsWithDeviceToken gets called and the token gets stored on the back end.
What do I need to do in order to access the token and store it in the backend upon user having tapped on "allow push notifications"?
When your user confirms that they want notifications you should call UIApplication.shared.registerForRemoteNotifications(). It doesn't seem like you are doing this.
You could do something like this
UNUserNotificationCenter.current()
.requestAuthorization(options: [.alert, .sound, .badge]) {
[weak self] granted, error in
print("Permission granted: \(granted)")
guard granted else { return }
self?.getNotificationSettings()
}
Then getNotificationSettings checks that it has been authorised before registering for remote notifications.
func getNotificationSettings() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
print("Notification settings: \(settings)")
guard settings.authorizationStatus == .authorized else { return }
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
For more on how to handle push notifications check out this Ray Wenderlich tutorial.

Cannot delete EKCalendar, get Error Domain=EKErrorDomain Code=72

I would like to delete an EKCalendar. This works fine on iOS devices and simulators, however trying to get this working on Catalina is problematic. Whatever I try I get this:
Error Domain=EKErrorDomain Code=72 "Can't delete a calendar if doing
so would leave the account with no calendars which are eligible to be
the default scheduling calendar."
UserInfo={NSLocalizedDescription=Can't delete a calendar if doing so
would leave the account with no calendars which are eligible to be the
default scheduling calendar.}
Any pointers? I have been chasing this for weeks! Thanks!
I have been granted permission for both Calendars and Reminders:
import UIKit
import EventKit
class ViewController: UIViewController {
let eventStore = EKEventStore()
func deleteCal (eventStoreToUse: EKEventStore) {
let calsArray = eventStoreToUse.calendars(for: .event)
for cals in calsArray {
print (cals.title)
if cals.title == "Gaps2" || cals.title == "Done" {
do { try _ = eventStoreToUse.removeCalendar(cals, commit: true)
} catch {
print ("Error \(error)")
}
} else {
print ("Did not delete \(cals.title)")
}
}
}
func askAccess() {
switch EKEventStore.authorizationStatus(for: .event) {
case .authorized:
print ("Calendars Access Granted")
case .denied:
print("Access denied")
case .notDetermined:
eventStore.requestAccess(to: .event, completion:
{[weak self] (granted: Bool, error: Error?) -> Void in
if granted {
print("Granted")
self?.deleteCal(eventStoreToUse: (self?.eventStore)!)
} else {
print("Access denied")
}
})
default:
print("Case default")
}
switch EKEventStore.authorizationStatus(for: .reminder) {
case .authorized:
print ("Reminders Access Granted")
case .denied:
print("Access denied")
case .notDetermined:
eventStore.requestAccess(to: .event, completion:
{[weak self] (granted: Bool, error: Error?) -> Void in
if granted {
print("Granted")
self?.deleteCal(eventStoreToUse: (self?.eventStore)!)
} else {
print("Access denied")
}
})
default:
print("Case default")
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
askAccess()
deleteCal(eventStoreToUse: eventStore)
}
}

Get Facebook user details with swift and parse

i need to get the Facebook user information details when i login a new user through parse. at present i am logging in the new user below. can't seem to get the user details though. I've seen some code written on objective - c. most of the functions don't work anymore
The Facebook iOS sdks i am running is v4.3.0.
#IBAction func facebookButton(sender: AnyObject) {
PFFacebookUtils.logInInBackgroundWithReadPermissions(permissions) {
(user: PFUser?, error: NSError?) -> Void in
if let user = user {
if user.isNew {
println("User signed up and logged in through Facebook!")
} else {
println("User logged in through Facebook!")
}
} else {
println("Uh oh. The user cancelled the Facebook login.")
}
}
}
To get the user details you have to send a FBSDKGraphRequest after the login request.
This can be done inside the if let user = user {...} block.
// Create request for user's Facebook data
let request = FBSDKGraphRequest(graphPath:"me", parameters:nil)
// Send request to Facebook
request.startWithCompletionHandler {
(connection, result, error) in
if error != nil {
// Some error checking here
}
else if let userData = result as? [String:AnyObject] {
// Access user data
let username = userData["name"] as? String
// ....
}
}
For the new Facebook API and version of Swift 3.1 you can do something like this:
if((FBSDKAccessToken.current()) != nil) {
FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "id,name,first_name,last_name,email"]).start(completionHandler: {(connection, result, error) -> Void in
if(error != nil) {
print("Some error occurred.");
} else {
print(result!)
}
})
}