Swift Block Syntax with Objective-C Function [Venmo-iOS-SDK] - ios

I'm currently trying to use the Venmo-iOS-SDK for an application I am working on. The SDK is in objective-C, while I'm trying to use it with a swift app.
I'm having trouble translating the syntax of a completion obj-c block to swift. I found sample code implementing a function I want to use.
- (IBAction)logInButtonAction:(id)sender {
[[Venmo sharedInstance] requestPermissions:#[VENPermissionMakePayments,
VENPermissionAccessProfile]
withCompletionHandler:^(BOOL success, NSError *error) {
if (success) {
NSLog("Success")
} else {
NSLog("Failure")
}
}];
}
I've tried doing this
#IBAction func loginButtonAction(sender: AnyObject){
Venmo.sharedInstance().requestPermissions([VENPermissionMakePayments, VENPermissionAccessPhone], withCompletionHandler: { (success: Bool, error: NSErrorPointer) -> Void in
if success{
println("Yes")
}else{
println("No")
}
})
}
But get the error
"Cannot invoke 'requestsPermissions with an argument list of type
'([String], withCompletionHandler: (Bool, NSError) -> Void)'
Is this a problem with how i'm translating the block? Or something else. Looking at the Venmo-SDK the obj-C functions are defined like this
- (void)requestPermissions:(NSArray *)permissions withCompletionHandler:(VENOAuthCompletionHandler)handler;
and
- (void)requestPermissions:(NSArray *)permissions withCompletionHandler:(VENOAuthCompletionHandler)handler;

You can write it like this (note the lack of types on the completion handler params):
#IBAction func loginButtonAction(sender: AnyObject) {
Venmo.sharedInstance().requestPermissions([VENPermissionMakePayments, VENPermissionAccessPhone], withCompletionHandler: { (success, error) -> Void in
// code here
})
}
A bit more concise with Swift 2 syntax would be omitting the -> Void and explicit withCompletionHandler: parameter:
#IBAction func loginButtonAction(sender: AnyObject) {
Venmo.sharedInstance().requestPermissions([VENPermissionMakePayments, VENPermissionAccessPhone]) { (success, error) in
// code here
}
}
You'll also want to make sure you change your println statements to print.

Related

Completion handler in function

I have a function that looks like this, and I have tried to add a completionHandler in the code below:
func getValueFromAPI(completionHandler: (_ result: Bool) -> Void){
apii.getVehicle(id!).done {
(vehicle: Vehicle) -> Void in
print("ggg.state: \(vehicle.state!)")
print("ggg.state: \(vehicle.displayName!)")
apii.getAllData(vehicle).done { (extendedVehicle: VehicleExtended) in
let entryBattery = (extendedVehicle.chargeState?.batteryLevel)!
let entryCarState = (extendedVehicle.state)!
print("entryBattery: \(entryBattery)")
print("entryCarState: \(entryCarState)")
completionHandler(true)
}.catch { (error) in
print("ERROOOOR: \(error)")
}
}.catch { error in
print("errorr: \(error)")
}
}
I have already tried to add a complete handler, but I get the following error on these lines:
Line: apii.getVehicle(id!).done {
Error: Escaping closure captures non-escaping parameter 'completionHandler'
Line: apii.getAllData(vehicle).done { (extendedVehicle: VehicleExtended) in
Error: Escaping closure captures non-escaping parameter 'completionHandler'
What am I doing wrong here, and how can I fix this?
I am using Swift 5.
You need to declare your completionHandler to be an escaping closure. E.g.:
func getValueFromAPI(completionHandler: #escaping (Bool) -> Void) {
...
}
Note the #escaping qualifier.

Is it okay to segue, without calling a completion handler, if not needed?

Say I have a function that has a completion handler, then calls another function, with a completion handler like this:
func register(withCompletion complete: #escaping (() -> Void)) {
self.sendRegisterRequest(withCompletion: { (error: Error?) -> () in
if error != nil {
self.performSegue(withIdentifier: "ErrorVCSegue", sender: nil)
}
else {
complete()
}
})
}
In the event of an error, it will segue away with calling complete().
Am I ok to segue away like this, without calling complete()? I do not need to return from this function as I'm now wanting to go to another View Controller.
Thanks.
This is a bad idea. A completion handler should be called no matter what. The caller is waiting for a response. It wants to know when it is done. That's the whole point of having a completion handler.
In your case (like many other cases), it would be much better if the completion handler accepted a boolean parameter (and/or an error parameter). This way the completion handler provides some basic information about the success or failure of the method.
Try like this
override func viewDidLoad() {
super.viewDidLoad()
register { (error) in
if error == nil {
// do what you want in success case
} else {
self.performSegue(withIdentifier: "ErrorVCSegue", sender: nil)
}
}
}
func register(withCompletion complete: #escaping ((_ error: Error?) -> Void)) {
self.sendRegisterRequest(withCompletion: { (error: Error?) -> () in
complete(Error)
})
}
Thanks:)

Expected Type Before / After —>, Expected Declaration in Swift 3

So, I created a typealias to store a completion handler, to later pass into a function called submitTokenToBackend using Stripe's iOS library. Here is my code:
// MARK: - Create Completion Handlers
typealias CompletionHandler = (_ token: AnyObject?, _ error: NSError?) -> Void
// MARK: - Submit Token To Backend
func submitTokenToBackend(completionHandler: CompletionHandler) {
}
// MARK: - STPPaymentCardTextFieldDelegate
func paymentCardTextFieldDidChange(_ textField: STPPaymentCardTextField) {
print("Card number: \(textField.cardParams.number) Exp Month: \(textField.cardParams.expMonth) Exp Year: \(textField.cardParams.expYear) CVC: \(textField.cardParams.cvc)")
self.buyButton.isEnabled = textField.isValid
}
// MARK: Initialize Card Params
let cardParams = STPCardParams()
func cardParamsFunc() {
cardParams.number = "4242424242424242"
cardParams.expMonth = 10
cardParams.expYear = 2018
cardParams.cvc = "123"
STPAPIClient.shared().createToken(withCard: cardParams){ (token, error) in
if let error = error {
print(error.localizedDescription)
} else if let token = token {
// HERE'S WHERE I'M GETTING ERRORS
self.submitTokenToBackend(completionHandler: CompletionHandler) -> Void {
if let error = error {
print(error.localizedDescription)
} else {
print("Show receipt page")
}
}
}
}
}
I am getting these weird errors, now, in Swift 3 concerning my completion handler not having expected types. Not an isolated incident, either. Any thoughts?
Almost all thing you need is described in Rob Napier's answer.
I'll try to show you a little more concrete code...
You can define the completion handler and pass it to submitTokenToBackend(completionHandler:) like this:
let theCompletionHandler: CompletionHandler = {token, error in
if let error = error {
print(error.localizedDescription)
} else {
print("Show receipt page")
}
}
self.submitTokenToBackend(completionHandler: theCompletionHandler)
With removing intermediate let-constant, you can write it in this way:
self.submitTokenToBackend(completionHandler: {token, error in
if let error = error {
print(error.localizedDescription)
} else {
print("Show receipt page")
}
})
Using the trailing closure feature of Swift, the above code can be shortened to:
self.submitTokenToBackend {token, error in
if let error = error {
print(error.localizedDescription)
} else {
print("Show receipt page")
}
}
Your code is far from any of above three.
self.submitTokenToBackend(completionHandler: CompletionHandler) -> Void {
This is a declaration, not a method call. You can't pass a typealias as a parameter. And -> Void does not make sense here at all. You almost certainly meant
self.submitTokenToBackend {
If you have further questions on this, however, you need to provide code we can compile (see mcve) and list the exact errors. "Weird errors" is not particularly helpful for debugging.
Im not sure, but... Where is 'CompletionHandler' implementation? As I understand, you just declarate some like block in Objective - C ('typedef void (^completionHandler)(id token);'), but don't use it.

Swift closure: cannot invoke a function with its argument list

I am writing a Swift function using closure. A should-be-compilable code sample is like this,
import Foundation
typealias PKSynchronizeProgressBlock = (Double) -> Void
typealias PKSynchronizeCompletionBlock = (Bool, NSError?) -> Void
class X {
func synchronizeAppDataWithProgress(
progress: PKSynchronizeProgressBlock?,
completion: PKSynchronizeCompletionBlock?) {
dispatch_async(dispatch_get_main_queue(), {
// Do a lot of downloading, and during the process
// {
// If progress is updated
if (progress != nil) {
progress!(Double(0))
}
//
// If something goes wrong
if (completion != nil) {
completion!(false, nil)
}
// }
dispatch_async(dispatch_get_main_queue(), {
if (completion != nil) {
completion!(true, nil)
}
})
})
}
func foo() {
self.synchronizeAppDataWithProgress({ (progress: Double) -> Void in
self.launchProgressBar.progress = progress
}, completion: { (success: Bool, error: NSError?) -> Void in
if success {
self.launchProgressBar.progress = 1.0
}
else {
print("Failed to synchronize app data with error %#", error!)
}
})
}
}
However, this code does not compile. Xcode says that
cannot invoke 'synchronizeAppDataWithProgress' with an argument list
'(progress: (Double) -> Void, completion: (Bool, NSError?) -> Void)'
What should I do? Did I make any stupid mistake in my code?
Update:
Thanks to #Mario Zannone. I fixed the first two mistakes in my code above. That was: (1) I inserted a redundant progress: in the function call. I have removed that. (2) I updated UI in a thread other than main thread.
But the code still does not work if I don't comment out the following single line in the foo(),
self.launchProgressBar.progress = progress
Do you have any clue why?
Xcode can be picky sometimes with the way arguments are listed inside closures. I've found it best to leave the type inferred. Also be sure to use capture lists to avoid strong reference cycles in your closures.
Using the Alamofire dependency, I've rewritten your code above and it compiles.
import Alamofire
typealias ProgressBlock = (Double) -> Void
typealias CompletionBlock = (Bool, ErrorType?) -> Void
class ExampleDataSource {
func fetchData(progress: ProgressBlock?, completion: CompletionBlock?) {
// Here we use the Alamofire Dependency for progress reporting simplicity.
Alamofire.request(.GET, "https://www.myexampledomain.com")
.progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
// bytesRead, totalBytesRead, and totalBytesExpectedToRead are Int64
// so we must perform unit conversion
let progressPercentage = Double(totalBytesRead) / Double(totalBytesExpectedToRead)
// here we optionally call the ProgressBlock 'progress' and pass it the progressPercentage
progress?(progressPercentage)
}
.response { request, response, data, error in
// here we usually parse the data, but for simplicity we'll
// simply check to see if it exists.
let completionFlag = (data != nil)
// note that NSError? is interchangable with ErrorType?
completion?(completionFlag, error)
}
}
func performTasks() {
// first we'll set up our closures...
let progressBlock: ProgressBlock = { progress in
// here we update the UI or whatever
// the nice thing about the Alamofire dependency is
// that it's thread-safe :]
}
let completionBlock: CompletionBlock = { success, error in
// here we do whatever we need to do when the
// network operation finishes, or handle the
// errors appropriately
}
// then we'll pass them into our fetchData method
fetchData(progressBlock, completion: completionBlock)
}
}

Global handling of 401 Responses with RestKit and Swift

I am currently working on an iOS App that is developed using Swift. For the REST calls, I am using the RestKit framework.
The next stage of my project is to start using authentication against the services. An issue that I have come up against is handling 401 (Not Authenticated) responses from the service. In all of these circumstances, I would like to display a login page. I want to avoid implementing the error handling of this multiple times.
I followed the tutorial at http://blog.higgsboson.tk/2013/09/03/global-request-management-with-restkit/. However, this is in Objective-C and I would like to do things slightly differently.
As such, I want to build a class that extends RKObjectRequestOperation as in the tutorial but using Swift. I have come up with an issue as I am receiving the error
Overriding method with selector 'setCompletionBlockWithSuccess:failure:' has incompatible type '((RKObjectRequestOperation, RKMappingResult) -> Void (RKObjectRequestOperation, NSError) -> Void) -> Void'
I am a bit stuck on this and so was hoping someone could help. The code for the method that is failing is below.
class CustomRequestOperation : RKObjectRequestOperation {
func setCompletionBlockWithSuccess(success: (operation: RKObjectRequestOperation, mappingResult: RKMappingResult) -> Void, failure: (operation: RKObjectRequestOperation, error: NSError) -> Void) -> Void {
}
}
Can anyone point out what is wrong with my method signature?
You're overriding the method so you can get Xcode to add the signature for you if you start typing the method name and hie escape.
It should be
func setCompletionBlockWithSuccess(success: (operation: RKObjectRequestOperation, mappingResult: RKMappingResult) -> Void, failure: (operation: RKObjectRequestOperation, error: NSError) -> Void) {
(you are adding a return spec that doesn't exist in the superclass method)
Here is the full class in swift...
class CustomRKObjectRequestOperation : RKObjectRequestOperation
{
override func setCompletionBlockWithSuccess(success: ((RKObjectRequestOperation!, RKMappingResult!) -> Void)!, failure: ((RKObjectRequestOperation!, NSError!) -> Void)!) {
super.setCompletionBlockWithSuccess({ (operation, RKMappingResult mappingResult) -> Void in
if ((success) != nil) {
success(operation, mappingResult);
}
}, failure: { (RKObjectRequestOperation operation, NSError error) -> Void in
NSNotificationCenter.defaultCenter().postNotificationName("connectionFailure",object:operation)
if ((failure) != nil) {
failure(operation, error);
}
})
}
}
App Delegate
Register notification
NSNotificationCenter.defaultCenter().addObserver(self, selector:"connectionFailedWithOperation:",name:"connectionFailure", object:nil)
func connectionFailedWithOperation(notification: NSNotification ){
let operation = notification.object as! RKObjectRequestOperation?;
if ((operation) != nil) {
let statusCode = operation!.HTTPRequestOperation.response.statusCode;
if (statusCode == 401) {
// log user out
}
}
}

Resources