PKAddPaymentPassViewControllerDelegate methods implementations - swift

We have to add debit/credit cards from App to Apple Wallet
let config = PKAddPaymentPassRequestConfiguration.init(encryptionScheme: PKEncryptionScheme.ECC_V2)
config?.cardholderName = "John"
config?.primaryAccountSuffix = "9999" //last 4 or 5digits of card
config?.localizedDescription = "This will add the card to Apple Pay";
config?.primaryAccountIdentifier = "test";
config?.paymentNetwork = PKPaymentNetwork(rawValue: "VISA");
guard let addPaymentPassVC = PKAddPaymentPassViewController.init(requestConfiguration: config!, delegate: self) else { return }
self.present(addPaymentPassVC, animated: true, completion: nil)
extension ViewController: PKAddPaymentPassViewControllerDelegate {
func addPaymentPassViewController(_ controller: PKAddPaymentPassViewController,
generateRequestWithCertificateChain certificates: [Data],
nonce: Data,
nonceSignature: Data,
completionHandler handler: #escaping (PKAddPaymentPassRequest) -> Void) {
}
func addPaymentPassViewController(_ controller: PKAddPaymentPassViewController,
didFinishAdding pass: PKPaymentPass?,
error: Error?) {
print("didFinishAdding")
}
}
What should be implementation for PKAddPaymentPassViewControllerDelegate methods?

I think that first, you should check PassKit documentation from Apple to understand correctly how it works(https://developer.apple.com/wallet/). Then maybe this website could works for you: https://github.com/tschoffelen/php-pkpass
But you could not do anything until you speak directly with apple to give you their permission.

Related

CKShare - Failed to modify some records error - CloudKit

I'm trying to share a record with other users in CloudKit but I keep getting an error. When I tap one of the items/records on the table I'm presented with the UICloudSharingController and I can see the iMessage app icon, but when I tap on it I get an error and the UICloudSharingController disappears, the funny thing is that even after the error I can still continue using the app.
Here is what I have.
Code
var items = [CKRecord]()
var itemName: String?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let item = items[indexPath.row]
let share = CKShare(rootRecord: item)
if let itemName = item.object(forKey: "name") as? String {
self.itemName = item.object(forKey: "name") as? String
share[CKShareTitleKey] = "Sharing \(itemName)" as CKRecordValue?
} else {
share[CKShareTitleKey] = "" as CKRecordValue?
self.itemName = "item"
}
share[CKShareTypeKey] = "bundle.Identifier.Here" as CKRecordValue
prepareToShare(share: share, record: item)
}
private func prepareToShare(share: CKShare, record: CKRecord){
let sharingViewController = UICloudSharingController(preparationHandler: {(UICloudSharingController, handler: #escaping (CKShare?, CKContainer?, Error?) -> Void) in
let modRecordsList = CKModifyRecordsOperation(recordsToSave: [record, share], recordIDsToDelete: nil)
modRecordsList.modifyRecordsCompletionBlock = {
(record, recordID, error) in
handler(share, CKContainer.default(), error)
}
CKContainer.default().privateCloudDatabase.add(modRecordsList)
})
sharingViewController.delegate = self
sharingViewController.availablePermissions = [.allowPrivate]
self.navigationController?.present(sharingViewController, animated:true, completion:nil)
}
// Delegate Methods:
func cloudSharingControllerDidSaveShare(_ csc: UICloudSharingController) {
print("saved successfully")
}
func cloudSharingController(_ csc: UICloudSharingController, failedToSaveShareWithError error: Error) {
print("failed to save: \(error.localizedDescription)")// the error is generated in this method
}
func itemThumbnailData(for csc: UICloudSharingController) -> Data? {
return nil //You can set a hero image in your share sheet. Nil uses the default.
}
func itemTitle(for csc: UICloudSharingController) -> String? {
return self.itemName
}
ERROR
Failed to modify some records
Here is what I see...
Any idea what could be wrong?
EDIT:
By the way, the error is generated in the cloudSharingController failedToSaveShareWithError method.
Looks like you're trying to share in the default zone which isn't allowed. From the docs here
Sharing is only supported in zones with the
CKRecordZoneCapabilitySharing capability. The default zone does not
support sharing.
So you should set up a custom zone in your private database, and save your share and records there.
Possibly it is from the way you're trying to instantiate the UICloudSharingController? I cribbed my directly from the docs and it works:
let cloudSharingController = UICloudSharingController { [weak self] (controller, completion: #escaping (CKShare?, CKContainer?, Error?) -> Void) in
guard let `self` = self else {
return
}
self.share(rootRecord: rootRecord, completion: completion)
}
If that's not the problem it's something with either one or both of the records themselves. If you upload the record without trying to share it, does it work?
EDIT TO ADD:
What is the CKShareTypeKey? I don't use that in my app. Also I set my system fields differently:
share?[CKShare.SystemFieldKey.title] = "Something"
Try to add this to your info.plist
<key>CKSharingSupported</key>
<true/>

AWS Mobile Hub states user is not signed-in after custom UI User Pool sign-in/authentication

I'm currently using AWS Mobile Hub for an iOS application that utilizes Cognito and Cloud Logic.
I decided to replace the default AuthUIViewController because I didn't like how it looked. I used this sample project to help me implement sign up through User Pools: https://github.com/awslabs/aws-sdk-ios-samples/tree/master/CognitoYourUserPools-Sample/Swift .
Here is my implementation:
Starting in my AppDelegate, I set the UserPool I want to sign into to a commonly accessible constant variable. One idea I have to why AWSMobileClient doesn't think my user is signed in is because it defines its own service configuration/pool, but I'm not sure:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
AWSDDLog.add(AWSDDTTYLogger.sharedInstance)
AWSDDLog.sharedInstance.logLevel = .verbose
// setup service configuration
let serviceConfiguration = AWSServiceConfiguration(region: Constants.AWS.CognitoIdentityUserPoolRegion, credentialsProvider: nil)
// create pool configuration
let poolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: Constants.AWS.CognitoIdentityUserPoolAppClientId,
clientSecret: Constants.AWS.CognitoIdentityUserPoolAppClientSecret,
poolId: Constants.AWS.CognitoIdentityUserPoolId)
// initialize user pool client
AWSCognitoIdentityUserPool.register(with: serviceConfiguration, userPoolConfiguration: poolConfiguration, forKey: AWSCognitoUserPoolsSignInProviderKey)
// fetch the user pool client we initialized in above step
Constants.AWS.pool = AWSCognitoIdentityUserPool(forKey: AWSCognitoUserPoolsSignInProviderKey)
return AWSMobileClient.sharedInstance().interceptApplication(
application, didFinishLaunchingWithOptions:
launchOptions)
}
After the AppDelegate is finished, the application goes to its root view controller named InitialViewController. Here, I allow the user to click a facebook sign in or regular (user pool) sign in.
class InitialViewController:UIViewController {
#objc func regLogin() {
//Set a shared constants variable "user" to the current user
if (Constants.AWS.user == nil) {
Constants.AWS.user = Constants.AWS.pool?.currentUser()
}
Constants.AWS.pool?.delegate = self
//This function calls the delegate function startPasswordAuthentication() in the extension below to initiate login
Constants.AWS.user?.getDetails().continueOnSuccessWith { (task) -> AnyObject? in
DispatchQueue.main.async(execute: {
//called after details for user are successfully retrieved after login
print(AWSSignInManager.sharedInstance().isLoggedIn)// false
print(AWSSignInManager.init().isLoggedIn)// false
print(AWSCognitoUserPoolsSignInProvider.init().isLoggedIn())// false
print(Constants.AWS.user?.isSignedIn) // true
AppDelegate.del().signIn()
})
return nil
}
}
}
extension InitialViewController: AWSCognitoIdentityInteractiveAuthenticationDelegate {
func startPasswordAuthentication() -> AWSCognitoIdentityPasswordAuthentication {
self.present(loginVC, animated: true, completion: nil)
return self.loginVC
}
}
As you can see, the functions do their job and the user is successfully logged in according to (Constants.AWS.user?.isSignedIn) as well as the fact that I am successfully able to retrieve the user details. However, when I ask AWSSignInManager or the UserPoolsSignInProvider whether my user is logged in, it returns false. This is a problem because without AWSMobileHub seeing my user as logged in, I cannot access my Cloud Logic functions etc.
Can someone please help me shed light on how I can notify MobileHub and the sign in manager that my user is logged into the user pool so that my application can work right?
Thank You!
Jonathan's answer is a good starting point for this, but it requires to include AWSAuthUI to your project.
A better solution is to implement directly the functions in AWSUserPoolsUIOperations.m. In particular, the function triggered when pressing the sign in button, should look like this:
#IBAction func signInPressed(_ sender: AnyObject) {
if (self.usernameTextField.text != nil && self.passwordTextField.text != nil) {
self.userName = self.usernameTextField.text!
self.password = self.passwordTextField.text!
AWSCognitoUserPoolsSignInProvider.sharedInstance().setInteractiveAuthDelegate(self)
AWSSignInManager.sharedInstance().login(
signInProviderKey: AWSCognitoUserPoolsSignInProvider.sharedInstance().identityProviderName,
completionHandler: { (provider: Any?, error: Error?) in
print(AWSSignInManager.sharedInstance().isLoggedIn)
})
} else {
let alertController = UIAlertController(title: "Missing information",
message: "Please enter a valid user name and password",
preferredStyle: .alert)
let retryAction = UIAlertAction(title: "Retry", style: .default, handler: nil)
alertController.addAction(retryAction)
}
}
Then include the following functions as an extension of your SignIn view controller:
public func handleUserPoolSignInFlowStart() {
let authDetails = AWSCognitoIdentityPasswordAuthenticationDetails(username: self.userName!, password: self.password!)
self.passwordAuthenticationCompletion?.set(result: authDetails)
}
public func startPasswordAuthentication() -> AWSCognitoIdentityPasswordAuthentication {
return self
}
public func getDetails(_ authenticationInput: AWSCognitoIdentityPasswordAuthenticationInput, passwordAuthenticationCompletionSource: AWSTaskCompletionSource<AWSCognitoIdentityPasswordAuthenticationDetails>) {
self.passwordAuthenticationCompletion = passwordAuthenticationCompletionSource
}
public func didCompleteStepWithError(_ error: Error?) {
DispatchQueue.main.async {
if let error = error as NSError? {
let alertController = UIAlertController(title: error.userInfo["__type"] as? String,
message: error.userInfo["message"] as? String,
preferredStyle: .alert)
let retryAction = UIAlertAction(title: "Retry", style: .default, handler: nil)
alertController.addAction(retryAction)
self.present(alertController, animated: true, completion: nil)
} else {
self.usernameTextField.text = nil
self.dismiss(animated: true, completion: nil)
}
}
}
After digging more through the AWS code, I found my answer in the pod AWSAuthUI in AWSSignInViewController.m (The view controller used in the default authentication process for mobile hub/cognito).
The code is:
- (void)handleUserPoolSignIn {
Class awsUserPoolsUIOperations = NSClassFromString(USERPOOLS_UI_OPERATIONS);
AWSUserPoolsUIOperations *userPoolsOperations = [[awsUserPoolsUIOperations alloc] initWithAuthUIConfiguration:self.config];
[userPoolsOperations loginWithUserName:[self.tableDelegate getValueForCell:self.userNameRow forTableView:self.tableView]
password:[self.tableDelegate getValueForCell:self.passwordRow forTableView:self.tableView]
navigationController:self.navigationController
completionHandler:self.completionHandler];
}
and getting to just the parts that matter... in Swift!!
userPoolsOperations.login(withUserName: "foo", password: "bar", navigationController: self.navigationController!, completionHandler: { (provider: Any?, error: Error?) in
print(AWSSignInManager.sharedInstance().isLoggedIn) // true
print(AWSSignInManager.init().isLoggedIn) // false
print(AWSCognitoUserPoolsSignInProvider.init().isLoggedIn()) // false
print(Constants.AWS.user?.isSignedIn) // nil
})
}
lesson learned: Reading through AWS's code is helpful even though it sucks

Testing text of an animated label that appears and disappears

I am struggling to test the appearance of a label(toastLabel) which I have that animates briefly into view when someone enters the wrong email.
private func registerNewUser(email: String, password: String, confirmationPassword: String) {
if password == confirmationPassword {
firebaseData.createUser(email: email, password: password, completion: { (error, _ ) in
if let error = error {
self.showToast(in: self.view, with: error.localizedDescription)
} else {
self.showToast(in: self.view, with: "Registered succesfully")
self.signInUser(email: email, password: password)
}
})
} else {
//raise password mismatch error
print("password mismatch error")
}
}
func showToast(in toastSuperView: UIView, with text: String) {
let toastLabel = ToastLabel()
toastLabel.text = text
toastSuperView.addSubview(toastLabel)
layoutToastLabel(toastLabel)
animateToastLabel(toastLabel)
}
private func layoutToastLabel(_ toastLabel: ToastLabel) {
toastLabel.centerYToSuperview()
toastLabel.pinToSuperview(edges: [.left, .right])
}
private func animateToastLabel(_ toastLabel: ToastLabel) {
UIView.animate(withDuration: 2.5, delay: 0, options: .curveEaseOut, animations: {
toastLabel.alpha = 0.0
}, completion: { _ in
toastLabel.removeFromSuperview()
})
}
I just want to test that the error text received back from firebase appears after the user enters an email that has already been taken.
func testRegisteringWithUsedEmailDisplaysFirebaseError() {
let email = registeredEmail
let password = "password"
welcomeScreenHelper.register(email: email,
password: password,
confirmationPassword: password,
completion: {
let firebaseErrorMessage = "The email address is already in use by another account."
XCTAssert(self.app.staticTexts[firebaseErrorMessage].exists)
})
}
func register(email: String, password: String, confirmationPassword: String, completion: (() -> Void)? = nil) {
let emailTextField = app.textFields[AccesID.emailTextField]
let passwordTextField = app.secureTextFields[AccesID.passwordTextField]
let confirmPasswordTextField = app.secureTextFields[AccesID.confirmPasswordTextField]
let registerButton = app.buttons[AccesID.registerButton]
emailTextField.tap()
emailTextField.typeText(email)
passwordTextField.tap()
passwordTextField.typeText(password)
registerButton.tap()
confirmPasswordTextField.tap()
confirmPasswordTextField.typeText(confirmationPassword)
registerButton.tap()
completion?()
}
when I use other tools such as expectation and XCTWaiter the tests still don't pass despite the text and label definitely appearing. I have never had to do a test like this so I'm not sure where I may be going wrong, whether I have to do something different to test an animated view or something.
Update1:
So I can see after a bit more playing that when i tap the registerButton the toast appears as it should but the test doesn't continue until it has disappeared again. I find this odd as it's not strictly attached to the registerButton being its own view.
update2:
I have update my test as follows:
func testRegisteringWithUsedEmailDisplaysFirebaseError() {
welcomeScreenHelper.register(email: registeredEmail,
password: password,
confirmationPassword: password,
completion: {
let firebaseErrorMessage = "The email address is already in use by another account."
let text = self.app.staticTexts[firebaseErrorMessage]
let exists = NSPredicate(format: "exists == true")
self.expectation(for: exists, evaluatedWith: text, handler: nil)
self.waitForExpectations(timeout: 10, handler: nil)
XCTAssert(self.app.staticTexts[firebaseErrorMessage].exists)
})
}
with the addition of:
override func setUp() {
app.launch()
UIView.setAnimationsEnabled(false)
super.setUp()
}
override func tearDown() {
if let email = createdUserEmail {
firebaseHelper.removeUser(with: email)
}
UIView.setAnimationsEnabled(true)
super.tearDown()
}
But so far no luck. I can still see that in func register once the register button is tapped the toast shows and the next line isn't called until the toastLabel has finished animating.
There are several things you need to solve in such kind of test:
If the code you are testing is using DispatchQueue.async you should use XCTestCase.expectation
If the code you are testing has UIView.animate in it (I see there is one in your example) do UIView.setAnimationsEnabled(false) before the test and enable it back after the test finishes so expectation won't wait for animation to complete. You can do it in XCTestCase.setUp and XCTestCase.tearDown methods.
If the code you are testing has dependencies like services that are doing async calls (I assume firebaseData is) you should either inject their mocks/stubs that will behave synchronously or use XCTestCase.expectation and pray for API/network be OK while the tests are run.
So using XCTestCase.expectation + UIView.setAnimationsEnabled(false) should work for you. Just XCTestCase.expectation with high enough timeout should work too.
EDIT 1:
Correct way to use expectation:
func test() {
let exp = expectation(description: "completion called")
someAsyncMethodWithCompletion() {
exp.fulfill()
}
waitForExpectations(timeout: 1) { _ in }
// assert here
}
So your test method should be:
func testRegisteringWithUsedEmailDisplaysFirebaseError() {
let exp = expectation(description: "completion called")
welcomeScreenHelper.register(email: registeredEmail,
password: password,
confirmationPassword: password,
completion: { exp.fulfill() })
self.waitForExpectations(timeout: 10, handler: nil)
let firebaseErrorMessage = "The email address is already in use by another account."
XCTAssert(self.app.staticTexts[firebaseErrorMessage].exists)
}

Swift 3: fix issue Type "className" does not conform to protocol 'UIDocumentPickerDelegate'

I'm migrating my code from Swift 2 to Swift 3 but my code throws this error: Type "className" does not conform to protocol 'UIDocumentPickerDelegate'. I've migrate many parts of my document as you can see but error stills,
extension className: UIDocumentMenuDelegate {
//Opening the Document Menu
func documentMenu(_ documentMenu: UIDocumentMenuViewController, didPickDocumentPicker documentPicker: UIDocumentPickerViewController) {
documentPicker.delegate = self
print ("documentMenu")
self.present(documentPicker, animated: true, completion: nil)
}
func documentMenuWasCancelled(_ documentMenu: UIDocumentMenuViewController) {
}
}
//Using UIDocumentPickerDelegate, error persists here.
extension className: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) {
if(controller.documentPickerMode == UIDocumentPickerMode.import){
print ("success")
}
}
}
class className: BaseViewController{
...Tons of code here...
#IBAction func importKeyButtonPressed(_ sender: UIButton) {
let importMenu = UIDocumentMenuViewController(documentTypes: ["public.data","public.text","public.content"], in: UIDocumentPickerMode.import)
var documentPicker = UIDocumentPickerViewController(documentTypes: ["public.txt"], in: UIDocumentPickerMode.import)
documentPicker.delegate = self
documentPicker.modalPresentationStyle = UIModalPresentationStyle.fullScreen
self.present(documentPicker, animated: true, completion: nil)
}
How can I solve it?, I've use all methods that procotol requires. Thanks
I revised your information on this link that has a similar problem.
Cannot conform to STPAddCardViewControllerDelegate since Xcode 8 GM on Swift 3
The problem is the swift compiler than canĀ“t recognize automatically some information. So in this case:
didPickDocumentAt url: URL
Then i follow the problem to this link:
https://bugs.swift.org/browse/SR-2596
In this link the information is that Swift 3 mistake data types, so i research some more and get to this page. The mistaken type in this case is "URL".
Then the solution is in the same page. I write it bellow:
weak var delegate : UIDocumentPickerDelegate?
#available(iOS 8.0, *)
public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: Foundation.URL ){
//
print("something")
}
To that matter i reproduce the error in my computer, it is caused when diferent imported libraries has the same definition for data types, in this case URL type, which Swift 3 doesnt recognize automatically neither tells correctly the error. So it has to be defined directly.

Waiting for Callback Function to Finish : Swift

I want to change the view when the user logs in from the LoginController. When access = true, change controller. However, I cant do it within the user.login because it gives me an error saying I have to process this on the main thread. Now I know that there are solutions for this, but I have been searching for a week and ran around in circles. I have gotten close enough to produce what I have below.
I have 3 pieces of information that I would like to share:
LoginController:
var access = false
user.login(
{(response: Bool) -> () in
access = response
print(access)
},username: emailField.text!, password: passwordField.text!)
if(access){
print("YAY")
let controller = storyboard?.instantiateViewControllerWithIdentifier("NewsFeed") as! NewsFeed
presentViewController(controller, animated: true, completion: nil)
}else{
print("NAY")
}
LoginClass:
func login(completionHandler : (response: Bool) -> (), username: String, password: String){
//Set the calback with the calback in the function parameter
let parameters : [String: AnyObject] = ["tag": "login", "email": username, "password": password]
manager.postDataToServer(
{(response: NSDictionary) -> () in
if(response["success"] as! Int == 1){
// Log user in
}else{
// User not able to login
}
completionHandler(response: false)
}
}, page: "login", params: parameters)
}
APIManager:
func postDataToServer(completionHandler: (response: NSDictionary) -> (), page: String, params: [String: AnyObject]){
// Gets the information and returns the User
// Works completely fine
}
ANSWER : Please go down to look at the Updated Answer
Please look at #Rob's answer. However you may get an error message saying Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[UIKeyboardTaskQueue waitUntilAllTasksAreFinished] may only be called from the main thread.'. It is because you cannot change views unless its on the main thread. Simply wrap it in
NSOperationQueue.mainQueue().addOperationWithBlock {
//Change View
}
Updated
I realized my mistake was that I didn't endEditing in one of my fields before processing the information. I fixed it by doing the following, passwordField.endEditing and also emailField.endEditing (just to be safe)
Instead of waiting for the response, just move the code you want to perform inside the closure:
user.login( { response in
if response {
print("YAY")
let controller = self.storyboard?.instantiateViewControllerWithIdentifier("NewsFeed") as! NewsFeed
self.presentViewController(controller, animated: true, completion: nil)
} else {
print("NAY")
}
}, username: emailField.text!, password: passwordField.text!)
Even better, I'd change the order of those parameters, so that the closure was the last parameter (and you can then use "trailing closure" syntax):
func loginWithUsername(username: String, password: String, completionHandler : (response: Bool) -> ()) {
// your login code here
}
And then:
user.loginWithUsername(emailField.text!, password: passwordField.text!) { response in
if response {
print("YAY")
let controller = self.storyboard?.instantiateViewControllerWithIdentifier("NewsFeed") as! NewsFeed
self.presentViewController(controller, animated: true, completion: nil)
} else {
print("NAY")
}
}