Multipeer Connectivity: [GCKSession] Not in connected state - ios

I am using the Multipeer Connectivity framework to connect multiple users in my app. When I try to invite users via the MCBrowserViewController, it works. However, when I try to send data I get the following error:
2022-10-29 13:13:30.159840+0200 Split[72183:5177674] [MCNearbyServiceAdvertiser] We are trying to send data to peer [iPhone,0D8E401A] and we are not connected.
2022-10-29 13:13:38.313487+0200 Split[72183:5177663] [GCKSession] Not in connected state, so giving up for participant [0D8E401A] on channel [0].
This is my code to send data:
func sendPayData(totalUser: String) {
if mcSession.connectedPeers.count > 0 {
do {
try mcSession.send(totalUser.data(using: .ascii)!, toPeers: mcSession.connectedPeers, with: .reliable)
} catch let error as NSError {
let ac = UIAlertController(title: "Send error", message: error.localizedDescription, preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
present(ac, animated: true)
}
}
}
One solution at the Apple Developer Forum was to set the encryptionPreference to .none in the MCSession. This did not help me either. Some also say that they can send data despite this error. Not for me, unfortunately, as it looks.
I found a few questions about this error on Reddit, but they weren't answered there. I would be very grateful if someone could help me.

Related

How to open the default mail app on iOS 14 without a compose view?

I want to open the default Mail application chosen by the user on iOS 14 - but without showing a compose view.
After signing up for an account, the user should confirm their email address, so I want to direct the user there.
There seem to be two known approaches based on the other questions I found:
UIApplication.shared.open(URL(string: "mailto://")!)
and
UIApplication.shared.open(URL(string: "message://")!)
The problem with the first option is that it will open an empty compose mail view in the app that comes up asking the user to type in a new email. That's not what I want. It would confuse users and they make think they have to send us an email. Putting in some text through parameters of the mailto URL syntax where I basically prepopulate the mail compose view with some text instructing to discard that new email draft and asking to check their email instead would work as a workaround but is not very nice.
The problem with the second option is that always opens the Mail.app, even if that is not the default mail app, and presumably it will ask the user to install the Mail.app if they deleted it from their phones because they have chosen e.g. Protonmail as their default mail app instead. Also not a very nice option for anyone who does not use Mail.app mainly.
So neither of the two approaches that have been proposed by other people solve my issue very nicely.
What is the best way to approach this?
Is there maybe some app to query iOS for the default mail app so at least I can try and launch that app if I know that app's custom URL scheme (e.g. googlegmail://)?
I ended up half-solving it by asking the user with an alert view about their preference, because I did not find a way to query iOS about it directly.
So first am showing an alert view like this:
func askUserForTheirPreference(in presentingViewController: UIViewController) {
let alertController = UIAlertController(title: nil, message: "pleaseConfirmWithApp", preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: "Apple Mail", style: .default) { action in
self.open(presentingViewController, .applemail)
})
alertController.addAction(UIAlertAction(title: "Google Mail", style: .default) { action in
self.open(presentingViewController, .googlemail)
})
alertController.addAction(UIAlertAction(title: "Microsoft Outlook", style: .default) { action in
self.open(presentingViewController, .outlook)
})
alertController.addAction(UIAlertAction(title: "Protonmail", style: .default) { action in
self.open(presentingViewController, .protonmail)
})
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel) { action in
os_log("Cancelling", log: Self.log, type: .debug)
})
presentingViewController.present(alertController, animated: true)
}
Then, I am responding to the user's choice like this:
func open(_ presentingViewController: UIViewController, _ appType: AppType) {
switch appType {
case .applemail: UIApplication.shared.open(URL(string: "message:")!, completionHandler: { handleAppOpenCompletion(presentingViewController, $0) })
case .googlemail: UIApplication.shared.open(URL(string: "googlegmail:")!, completionHandler: { handleAppOpenCompletion(presentingViewController, $0) })
case .outlook: UIApplication.shared.open(URL(string: "ms-outlook:")!, completionHandler: { handleAppOpenCompletion(presentingViewController, $0) })
case .protonmail: UIApplication.shared.open(URL(string: "protonmail:")!, completionHandler: { handleAppOpenCompletion(presentingViewController, $0) })
}
}
private func handleAppOpenCompletion(_ presentingViewController: UIViewController, _ isSuccess: Bool) {
guard isSuccess else {
let alertController = UIAlertController(title: nil, message: "thisAppIsNotInstalled", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: .cancel))
presentingViewController.present(alertController, animated: true)
return
}
}
enum AppType {
case applemail, googlemail, outlook, protonmail
}
A clear limitation of this approach is of course that I am limiting the user to very specific apps (in this case Google Mail, iOS "default" Mail, Microsoft Outlook and ProtonMail).
So this approach does not really scale well.
But at least, you can cover a few favorite ones and go from there based on your users' feedback.
The main reason for jumping through these hoops of asking the first is that, at least at the moment, it seems impossible to get that information from iOS directly.
I also could not find a URL scheme that would always open the chosen default Mail app without showing the compose new email view.
I believe it can be done with a button with link: href=“message://“
Visual example from Revolut app:

How do I create a loop checking app connectivity

I'm trying to create a loop on my app to check Connectivity. So, when the app loads for the first time, the user must be connected to the internet, otherwise he cannot proceed on using the app.
However, I'm trying to create a function that while the user has no internet, the UIAlerView persists on the screen until find an internet connection, then it should be dismissed and another method be lunched. What would be the best way for me to do that?
let alert = UIAlertController(title: "Primeiro Uso", message: "Voce precisa estar conectado na internet", preferredStyle: UIAlertControllerStyle.alert)
// add the actions (buttons)
alert.addAction(UIAlertAction(title: "Estou Conectado", style: .default, handler: { action in
if Connectivity.isConnectedToInternet != true {
//How do I create a loop here until find a connection?
//I'd like to persist the UIAlerView until find a connection
}
//Once the user is connected, this code below should be
//lunched
getApiData { (cerveja) in
arrCerveja = cerveja
//Backup
do{
let data = try JSONEncoder().encode(arrCerveja)
UserDefaults.standard.set(data, forKey: "backupSaved")
//
self.tableView.reloadData()
}catch{print(error)
}
}}))
alert.addAction(UIAlertAction(title: "Cancelar", style: UIAlertActionStyle.cancel, handler: nil))
// show the alert
self.present(alert, animated: true, completion: nil)
}
you can use this answer
https://stackoverflow.com/a/3597085/2621857
in the appDelegate level of you application.
i think creating loop for checking internet, somehow will pressure the processor to memory leak so i suggest not using a kind of loop to check the internet.
i hope this will work for you .

Swift ios touch id login flow

In my app a user can authenticate/login to my backend using email/password. Now I am thinking of implementing touch ID as well.
But I am confused about the login flow using a touch ID.
Using the code bellow I can easy authenticate a user:
func authenticateUser() {
let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
let reason = "Identify yourself!"
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) {
[unowned self] success, authenticationError in
DispatchQueue.main.async {
if success {
self.runSecretCode()
} else {
let ac = UIAlertController(title: "Authentication failed", message: "Sorry!", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
self.present(ac, animated: true)
}
}
}
} else {
let ac = UIAlertController(title: "Touch ID not available", message: "Your device is not configured for Touch ID.", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
present(ac, animated: true)
}
}
But I dont know which user it is.
So, when a user creates an account, should I store something like the device ID(if that exists) in my DB then when the users uses touch ID I can check what device ID it is then log the user in?
As I understand it, touch ID only acts as a gatekeeper to the native app iteself, not the API.
No matter what, your user would have to authenticate with the API at least once to get some kind of secret token(JWT, etc) which would then be persisted through closing/opening of the app. Touch ID would have to be successful before your app would start making calls to the API, but the API will only validate against the secret.
This way you can persist the user's login without saving credentials, and you have touch ID on the app to keep an unwanted user from picking up an unlocked phone and just opening the banking app.
This is how I understand it, but if I'm wrong, someone please correct me.

Saving Contact Address to Unified Contact results in (CNErrorDomain error 500)

There is an odd error in my application that I can't find any workarounds/fixes for. For some reason, I'm able to save an address to a contact that isn't unified with a social profile (Facebook, Twitter, etc). However, when I try to add an address for my contact that is unified with Facebook or Twitter I get a weird save error:
The operation couldn’t be completed. (CNErrorDomain error 500.)
Here is some of the code that I'm using:
if mutableContact.isKeyAvailable(CNContactPostalAddressesKey) {
var postalAddresses = [CNLabeledValue<CNPostalAddress>]()
for address in self.contactAddresses {
let postalAddress: CNLabeledValue<CNPostalAddress> = CNLabeledValue(label: CNLabelOther, value: address)
postalAddresses.append(postalAddress)
}
mutableContact.postalAddresses = postalAddresses
}
let saveRequest = CNSaveRequest()
if isNewContact {
saveRequest.add(mutableContact, toContainerWithIdentifier: nil)
} else {
saveRequest.update(mutableContact)
}
do {
try contactStore.execute(saveRequest)
} catch let error as NSError {
print(error.localizedDescription)
let alertController = UIAlertController(title: "Failed to save/update contact!", message: "Unfortunatly, the app couldn't add or make modifications to your contact. Please try again or use the Contacts app to preform changes.", preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Okay", style: .cancel) {
action in
self.dismiss(animated: true, completion: nil)
}
alertController.addAction(cancelAction)
self.present(alertController, animated: true, completion: nil)
}
Ok, so I have gotten a response from Apple and this behavior is intended. Developers should detect this policy violation and then offer to create a new contact and then link the two contacts.
I understand why Apple do it, as the social account does not store all the same data as (say) an iCloud contact, so you need to keep those values somewhere else, (and you can't convert the contact to iCloud as the social account will lose its data.)
I don't think you can CHOOSE to link contacts programmatically, though, can you?
As I understand it iOS will decide whether two contacts are the same- probably when more than one value matches (?) You can detect whether two given contacts are linked together.

How to check if user is signed in into iCloud before saving data

Before saving data into CloudKit, how do I check that user has an account and is signed in?
You need to use accountStatusWithCompletionHandler from the CKContainer class and check the accountStatus returned.
This is described in the CloudKit Quick Start documentation.
Here is the Swift version of the example:
CKContainer.defaultContainer().accountStatusWithCompletionHandler { accountStatus, error in
if accountStatus == .NoAccount {
let alert = UIAlertController(title: "Sign in to iCloud", message: "Sign in to your iCloud account to write records. On the Home screen, launch Settings, tap iCloud, and enter your Apple ID. Turn iCloud Drive on. If you don't have an iCloud account, tap Create a new Apple ID.", preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
} else {
// Code if user has account here...
}
}

Resources