i build a register form for my app and i need to send the user a verifiation code by sms in order to complete the registration proccess.
i tried to use MFMessageComposeViewController but its open the dialog sms on the device so the user can see the code.
i also checked the web for 3party of sending sms but there is a problem with the country code. i know its posible becuse whatsapp do it to confirm the user.
what it the right way to do it?
this is the topic the i tried:
Sending SMS in iOS with Swift
The best way to achieve this is by creating some views for allowing the user to enter the phone number with the country code which can be used by a server to send a request for initiating the OTP verification. To achieve this you need to:
Create View Controllers.
Upload Phone Number and Country code to the server.
Validate the requests by verifying the OTP.
As mentioned by Dan, you can use Digits in Fabric for that purpose, and create custom views for a great UX.
On the other hand, you can also use a service called as SendOTP from MSG91 - you can use it for internal testing and development ideas as they provide you with 5,000 free OTP SMS. The service has a complete set of APIs which you can implement on the backend as well on the app front. Also, they provide a framework so that you don't need to create the views, but only presentViewController and call delegate methods for knowing what happened during the verification process - such as Cancelled or Verified or Failed.
Swift implementation of the same looks like this:
class OTPFrame: UIViewController, sendOTPAuthenticationViewControllerDelegate {
func loadOTPFramework() {
SendOTP.sharedManager().startWithApiId("yourAppID")
let frameworkPath: NSString = NSBundle.mainBundle().privateFrameworksPath!
let frameworkBundlePath: NSString = frameworkPath.stringByAppendingPathComponent("SendOTPFramework.framework")
let frameworkBundle : NSBundle
= NSBundle(path: frameworkBundlePath as String)!
let authenticationViewController: AuthenticationViewController = AuthenticationViewController(nibName: "AuthenticationViewController", bundle: frameworkBundle)
authenticationViewController.delegate = self self.presentViewController(authenticationViewController, animated: true, completion: nil)
}
func authenticationisSuccessfulForMobileNumber(mobNo: String!, withCountryCode countryCode: String!) {
print("Success")
}
func canceledAuthentication() {
print("Cancelled")
}
func authenticationisFailedForMobileNumber(mobNo: String!, withCountryCode countryCode: String!) {
print("Failed")
}
}
Disclaimer: I, in no way, endorse the services mentioned above - you are free to choose whatever you like.
Thank You!
I would give digits a try! It's part of the Twitter Fabric package and it's very simple to use. The user enters their phone number and Fabric takes care of validating the number.
Related
I am trying to figure out how to perform e2e test via firebase test lab for iOS that allow to check passwordless authentication flow, which essentially should do following
Enters email within my app
Firebase sends auth link to such email
Somehow I need to be logged into such email somewhere in firebases test device, I assume either in mail app, or gmail?
I need to know when new email arrives and open it
Once I opened an email I need to click on auth link
This should bring me back into the app and authenticate
My biggest issue at the moment is figuring out steps that happen outside my app i.e. how can I prepare for this test and log in under my email address (is it better to log into gmail in safari for example or somehow add this acc to apples mail app?).
Testing email
In my experience, testing your own code to see if an email was sent is not straightforward beyond checking if the method call you expect to send the email has happened.
Add on top of that using Firebase, which does not expose its underlying email send code, and that looks like a challenge to me.
In terms of testing, I suggest you assert that your method calls to send email happened or that the relevant code path was reached. In Firebase web, this looks like:
firebase.auth().sendSignInLinkToEmail(email, actionCodeSettings)
.then(function() {
// The link was successfully sent. Inform the user.
// Save the email locally so you don't need to ask the user for it again
// if they open the link on the same device.
window.localStorage.setItem('emailForSignIn', email);
// TODO save email to something accessible in your iOS tests
// TODO In your tests, confirm that email was saved after it was sent
})
.catch(function(error) {
// Some error occurred, you can inspect the code: error.code
});
See: https://firebase.google.com/docs/auth/web/email-link-auth#send_an_authentication_link_to_the_users_email_address
Another option:
You could setup a test user with an email address on a mail server that you manage, and check for incoming mail for that test user with your own custom mail reading code.
I would use Firebase Admin tools for this: https://firebase.google.com/docs/auth/admin/manage-users#create_a_user
I think you should first take a look at firebase docs for iOS on how to create dynamic links that you can use for email auth.
https://firebase.google.com/docs/auth/ios/email-link-auth
https://firebase.google.com/docs/auth/ios/passing-state-in-email-actions#configuring_firebase_dynamic_links
After you're done with those two check out the following code:
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
// [END old_delegate]
if handlePasswordlessSignIn(withURL: url) {
return true
}
}
func handlePasswordlessSignIn(withURL url: URL) -> Bool {
let link = url.absoluteString
// [START is_signin_link]
if Auth.auth().isSignIn(withEmailLink: link) {
// [END is_signin_link]
UserDefaults.standard.set(link, forKey: "Link")
(window?.rootViewController as? UINavigationController)?.popToRootViewController(animated: false)
window?.rootViewController?.children[0].performSegue(withIdentifier: "passwordless", sender: nil)
return true
}
return false
}
This is just an example on how you can handle the deep link in your app after the user taps the link. The delegate method
func application(_ application: UIApplication, open url: URL,
sourceApplication: String?, annotation: Any) -> Bool
in AppDelegate is used for all deep links into an app. You could set up for example your own scheme that your app conforms to. And you can send url type links with your custom scheme into your app from the browser for example.
To do this just Open Xcode, go to Project Settings -> Info, and add inside ‘The URL Types” section a new URL scheme. Add something of the sort of com.myApp in order for it to be as unizue as possible. Then you can just type into a browser com.myApp://main and handle that in the appDelegate.
Edit: It says so in their docs that you can present a prompt inside the app for the user to input the email. Where the user opens his email from isn't really your concern as long as your dynamic link is set up properly.
I've integrated code from twilio voice quickstart https://github.com/twilio/voice-quickstart-swift and got my app working up to some extent.
I'm facing problem with "Voice Bot". I'm not sure what this is and how I can implement it in different way. This is for outgoing call. After call is completed, Voice Bot is triggered but it doesn't show on the top of my VC where I'm handling all outgoing call related stuff, it remains in the background and making any subsequent call fails. When I go back from VC to other VC then screen flickers and it seems Voice Bot shows sometimes and then flow completes.
I'm not sure what this Voice Bot is and how we can avoid it or redirect to be handled in a VC? All I can guess is that it's from Apple's Call kit if i'm not wrong.
Any help on how to make it work?
Update on comment: I've used cocoapods to install Twilio.
pod 'TwilioVoice', '~> 2.0'
Here is the code:
#IBAction func placeCall(_ sender: UIButton) {
if (self.call != nil && self.call?.state == .connected) {
performEndCallAction(uuid: self.call!.uuid)
self.toggleUIState(isEnabled: false, showCallControl: false)
} else {
let uuid = UUID()
let handle = "Voice Bot" // <==== ????
performStartCallAction(uuid: uuid, handle: handle)
}
}
It's the name which appears on callKit. You can change it to be the name of your app or caller name. Apart from this name it has no significance whatsoever. If you are using client-to-client call only then change it to be your app's name. If you are using your app for phone call as well then change it to be caller's name.
I am exploring SiriKit and I want to use it for sending data to web server. Basically, I want to send some data whenever user says "Hey Siri, send data using SomeApplication". Here 'SomeApplication' is the name of my app. I googled a bit, but I came to know that I can only use the intents given by SiriKit, I can't create my own. Please help me if there is some way to do this.
That’s true that you can’t create your own intents but there’s a trick that you can play. You can use the Messaging intent and instead of sending text message, you can make your service call from there.
In the IntentHandler.swift file, modify the code of resolveRecipients method to create a custom contact. This would result in showing a messaging UI but would skip the step of asking for recipient. Instead, Siri would directly ask for the message that you would receive in resolveContent method. Get it from there and call your web service. As simple as that.
Here's the code
func resolveRecipients(for intent: INSendMessageIntent, with completion: #escaping ([INPersonResolutionResult]) -> Void) {
let resolutionResults = [INPersonResolutionResult.success(with: INPerson.init(personHandle: INPersonHandle.init(value: "Raw", type: .unknown), nameComponents: PersonNameComponents.init(), displayName: "Raw", image: INImage.init(), contactIdentifier: "Raw", customIdentifier: "Raw"))]
completion(resolutionResults)
}
For detailed example, you can checkout this Git Repo for SiriKitDemo
I'm trying to update my app to work correctly with the new features of GameCenter in iOS10.
I create a new GKGameSession on device1, get a share URL, and all that works fine. I send the share URL out via a share sheet to device 2.
Device2 clicks the link, the device briefly displays 'Retrieving...' and then launches my app. Great! But, now what? Is there context information available for this URL that I can somehow access? Otherwise I have no way how to respond when the app is launched.
Previously you'd get a callback to something adhering to the GKLocalPlayerListener protocol, to the method player:didAcceptInvite:, and you could join the match that way. But with these iCloud-based messages, the player might not be even logged into GameCenter, right? This part seems to have been glossed over in the WWDC presentation.
Also, as of today (12/28/2016) there is no Apple documentation on these new methods.
Since the GKGameSessionEventListener callback session:didAddPlayer: only fires if the game is already running, to be sure you can process this callback every time requires a work around. I've tested this and it works.
When you send out an iMessage or email invite to the game, don't include the Game Session Invite URL directly in the message. Instead use a registered URL that will open your app when opened on a device on which your app is installed. Check here to see how:
Complete Tutorial on iOS Custom URL Schemes
But add a percent escaped encoding of the game invite URL as a parameter to this URL thusly (I'm assuming the registration of a url e.g. newGameRequest but it will be best to make this quite unique, or even better - though it requires more setup, try Universal Link Support as this will allow you to direct users who don't have your app installed to a webpage with a download link)
let openOverWordForPlayerChallenge = "newGameRequest://?token="
gameState.gameSession?.getShareURL { (url, error) in
guard error == nil else { return }
// No opponent so we need to issue an invite
let encodedChallengeURL = url!.absoluteString.addingPercentEncoding(withAllowedCharacters:.urlHostAllowed)
let nestedURLString = openOverWordForPlayerChallenge + encodedChallengeURL!
let nestedURL = URL(string: nestedURLString)!
}
send the URL in a message or email or WhatsApp or whatever. Then in your app delegate, add the following:
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
var success = false
if let queryString = url.query {
if let urlStringToken = queryString.removingPercentEncoding {
let token = "token="
let startIndex = urlStringToken.startIndex
let stringRange = startIndex..<urlStringToken.index(startIndex, offsetBy: token.characters.count)
let urlString = urlStringToken.replacingOccurrences(of: token, with: "", options: .literal, range: stringRange)
if let url = URL(string: urlString) {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
success = true
}
}
}
}
return success
}
Now you can be sure the session:didAddPlayer: will be called. What's the betting this workarround is good for about 2 weeks, and they fix this in the next release of iOS showcased at WWDC 2017 ! Update: this problem hasn't been fixed - so the workaround above remains good!
I agree, the lack of documentation is frustrating. From what I can see, we have to:
add <GKGameSessionEventListener> protocol in the class' header
Then session:didAddPlayer: fires on the joining player's device after accepting an invite link.
update:
Unfortunately, I'm not surprised to hear your results. I hadn't tried all of those scenarios, but GKTurnBasedMatch had similar shortcomings. The way I got around it there was: I added a list of player statuses to match data (invited, active, quit, etc). I gave the player a view of "pending invitations." When they opened that view, I would load all of their matches and display the entries where the player was in invited state. With GKGameSession, that should work too.
Or, it might be easier if you could maintain a local list of sessions that you are aware of. Whenever the game becomes active, pull the entire list of sessions from the server and look for a new entry. The new entry would have to be the match the player just accepted by clicking the share URL.
Hopefully the title is self-explanatory. I'm trying to do something like this:
checkIfUserIsSubscribedToProduct(productID, transactionID: "some-unique-transaction-string", completion: { error, status in
if error == nil {
if status == .Subscribed {
// do something fun
}
}
}
does anything like the hypothetical code I've provided exist? I feel like I'm taking crazy pills
Edit
In similar questions I keep seeing a generic answer of "oh you gotta validate the receipt" but no explanation on how, or even what a receipt is. Could someone provide me with how to "validate the receipt"? I tried this tutorial but didn't seem to work.
Edit - For Bounty
Please address the following situation: A user subscribes to my auto-renewable subscription and gets more digital content because of it - cool, implemented. But how do I check whether that subscription is still valid (i.e. they did not cancel their subscription) each time they open the app? What is the simplest solution to check this? Is there something like the hypothetical code I provided in my question? Please walk me through this and provide any further details on the subject that may be helpful.
I know everyone was very concerned about me and how I was doing on this - fear not, solved my problem. Main problem was that I tried Apple's example code from the documentation, but it wasn't working so I gave up on it. Then I came back to it and implemented it with Alamofire and it works great. Here's the code solution:
Swift 3:
let receiptURL = Bundle.main.appStoreReceiptURL
let receipt = NSData(contentsOf: receiptURL!)
let requestContents: [String: Any] = [
"receipt-data": receipt!.base64EncodedString(options: []),
"password": "your iTunes Connect shared secret"
]
let appleServer = receiptURL?.lastPathComponent == "sandboxReceipt" ? "sandbox" : "buy"
let stringURL = "https://\(appleServer).itunes.apple.com/verifyReceipt"
print("Loading user receipt: \(stringURL)...")
Alamofire.request(stringURL, method: .post, parameters: requestContents, encoding: JSONEncoding.default)
.responseJSON { response in
if let value = response.result.value as? NSDictionary {
print(value)
} else {
print("Receiving receipt from App Store failed: \(response.result)")
}
}
As some comments pointed out there's a couple flaws with these answers.
Calling /verifyReceipt from the client isn't secure.
Comparing expiration dates against the device clock can be spoofed by changing the time (always a fun hack to try after cancelling a free trial :) )
There are some other tutorials of how to set up a server to handle the receipt verification, but this is only part of the problem. Making a network request to unpack and validate a receipt on every app launch can lead to issues, so there should be some caching too to keep things running smoothly.
The RevenueCat SDK provides a good out-of-the box solution for this.
A couple reasons why I like this approach:
Validates receipt server side (without requiring me to set up a server)
Checks for an "active" subscription with a server timestamp so can't be spoofed by changing the device clock
Caches the result so it's super fast and works offline
There's some more details in this question: https://stackoverflow.com/a/55404121/3166209
What it works down to is a simple function that you can call as often as needed and will return synchronously in most cases (since it's cached).
subscriptionStatus { (subscribed) in
if subscribed {
// Show that great pro content
}
}
What are you trying to achieve in particular? Do you want to check for a specific Apple ID?
I highly doubt that this is possible through the SDK. Referring to Is it possible to get the user's apple ID through the SDK? you can see that you can't even ask for the ID directly but rather services attached to it.
What would work is caching all transactions on your own server and search its database locally but that would require the app to ask for the user's Apple ID so the app could update the subscription state whenever it launches as it can check for IAP of the ID associated with the device.
However, the user could just type whatever he wanted - and it's unlikely to get this through Apple's app review process.
I am using MKSoreKit https://github.com/MugunthKumar/MKStoreKit for auto-renew subscriptions.but it is in objective c you can check the library code for solution.I am using it in my code and it is working fine.
using below method you can easily check subscription status..
if([MKStoreManager isProductPurchased:productIdentifier]) {
//unlock it
}
It gets the apple id from device and I think that is user specific