Completion handler and if statement within - swift

I wrote a function that I guess it should return a boolean , I'm using completion handler and switch and if statements within
this is my code :
class func login(username : String , password : String, _ completion: #escaping (Bool) -> ()) {
let url = "http://127.0.0.1:3000/login/"+username+"/"+password
Alamofire.request(url).responseJSON{response in
switch response.result
{
case .failure:
print(response)
completion(false)
case .success:
if let dict = response.value as? NSDictionary {
let dict = response.value as? NSDictionary
let user = dict!["users"] as? NSArray
if user!.count > 0 {
print(user!.count)
completion(true)
}
else {
print(user!.count)
completion(true)
}
}
}
}
}
And I want to use it like this :
#IBAction func LoginBtn(_ sender: Any) {
API.login(username: textUsername.text!, password: textPassword.text!) {
success in
if success{
// if the function return true print(hello)
//else print (type again)
print("Welcome")
}
else{
print("NO")
}
}
}

First of all don't use NSArray and NSDictionary in Swift, use native types.
Second of all never use .count > 0 to check for empty array or empty string. There is an optimized property isEmpty
Your code is very cumbersome and there are a few redundant parts in it.
The logic is to return true if the users array is valid and not empty otherwise return false.
So the success branch can be reduced to
case .success:
if let dict = response.value as? [String:Any], let users = dict!["users"] as? [[String:Any]], !users.isEmpty {
completion(true)
} else {
completion(false)
}

Related

How can I get data inside of completionHandler for Alamofire.responseJSon

I'm currently making app for Destiny 2 API.
and I'm struggling with this issue
this is my whole function code that makes hash data to item name
I want to return that name variable which is guarded last, but that completion handler
runs after function is end.
I searched to internet, but almost everyone just used this data inside of it, not
function return.
that I only need is " Parse json and get returned data I want "
is there any idea please?
item parameter means " item hash to get name of it "
lang parameter means " language to return " if this is "en" then, it should return English.
here's the data I pared too " https://pastebin.com/1tV6fx9F "
func hashToName(item: String, lang: String = "ko") (" want to return String "-> String ){
let url = String(format: "https://www.bungie.net/platform/Destiny2/Manifest/DestinyInventoryItemDefinition/\(item)")
let param: [String: String] = ["lc": "\(lang)"]
let head: HTTPHeaders = ["x-api-key": "b21e4d2d33234b82be6e56893554974b"]
let doNetwork = AF.request(url, method:.get, parameters: param, encoder: URLEncodedFormParameterEncoder.default, headers: head)
doNetwork.responseJSON {
response in
switch response.result {
case .success(let site):
guard let dict = site as? NSDictionary else { return }
guard let res = dict["Response"] as? NSDictionary else { return }
guard let prop = res["displayProperties"] as? NSDictionary else { return }
guard let name: String = prop["name"] as? String else { return }
print(name) // data I want to return
case .failure(let err):
print(err.localizedDescription)
}
}
}
func hashToName(item: String, lang: String = "ko", returnString: #escaping (String)->()) {
let url = String(format: "https://www.bungie.net/platform/Destiny2/Manifest/DestinyInventoryItemDefinition/\(item)")
let param: [String: String] = ["lc": "\(lang)"]
let head: HTTPHeaders = ["x-api-key": "b21e4d2d33234b82be6e56893554974b"]
let doNetwork = AF.request(url, method:.get, parameters: param, encoder: URLEncodedFormParameterEncoder.default, headers: head)
doNetwork.responseJSON {
response in
switch response.result {
case .success(let site):
guard let dict = site as? NSDictionary else { return }
guard let res = dict["Response"] as? NSDictionary else { return }
guard let prop = res["displayProperties"] as? NSDictionary else { return }
guard let name: String = prop["name"] as? String else { return }
returnString(name)
print(name) // data I want to return
case .failure(let err):
print(err.localizedDescription)
}
}
}
//How to use
hashToName(item: "Your string") { str in
print(str)
}

Return response as object in swift

I have a function that connects to an API to retrieve data. The API takes two parameters accessCode (provided by user in a text box) and then UDID (UDID of their device). I can parse the data from within the function, but only locally. I need to store the values that are returned but am unsure on how to return them properly. Essentially I need this to return the json object as a dictionary (I think...) so it can be parsed outside of the async task. I've read through the swift documentation and that's where I found out how to do the requests, but I can't find a way to store the returned values in memory for access outside of the function.
func getResponse(accessCode:String, UDID:String, _ completion: #escaping (NSDictionary) -> ()) {
let urlPath = "https://apihosthere.com/api/validate?accessCode=" + accessCode + "&UDID=" + UDID
guard let url = URL(string: urlPath) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary {
let results = jsonResult as? NSDictionary
print(results)
completion(results!)
}
} catch {
//Catch Error here...
}
}
task.resume()
}
First of all don't use NSDictionary in Swift, use native [String:Any] and declare the type as optional to return nil if an error occurs.
And never use .mutableContainers in Swift, the option is useless.
func getResponse(accessCode:String, UDID:String, completion: #escaping ([String:Any]?) -> Void)) {
let urlPath = "https://apihosthere.com/api/validate?accessCode=" + accessCode + "&UDID=" + UDID
guard let url = URL(string: urlPath) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error else {
print(error)
completion(nil)
return
}
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data!) as? [String:Any] {
print(jsonResult)
completion(jsonResult)
} else {
completion(nil)
}
} catch {
print(error)
completion(nil)
}
}
task.resume()
}
Your mistake is that you don't consider the closure, you have to execute the entire code inside the completion handler
#IBAction func StartWizard(_ sender: UIButton) {
//Store entered access code
let accessCode = AccessCodeField.text!
//Call API to validate Access Code
getResponse(accessCode:accessCode, UDID:myDeviceUDID) { [weak self] result in
if let accessCodeFound = result?["Found"] as? Bool {
print("Value of Found during function:")
//If access code is valid, go to License view
print(accessCodeFound)
if accessCodeFound {
//Load License View
DispatchQueue.main.async {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let licenseController = storyboard.instantiateViewController(identifier: "LicenseViewPanel")
self?.show(licenseController, sender: self)
}
}
}
}
}
Your completion closure should handle the obtained data. You would call the function like this:
getResponse(accessCode: "code", UDID: "udid", completion: { result in
// Do whatever you need to do with the dictionary result
}
Also, I'd recommend you to change your NSDictionary with a swift Dictionary.
This is what the API returns as a response
{
AccessCode = 00000000;
Client = "0000 - My Company Name";
EmailAddress = "brandon#brandonthomas.me";
FirstName = Brandon;
Found = 1;
LastName = Thomas;
Status = A;
UDIDregistered = 1;
}
And this is what calls the function. I am calling at after clicking a button after an access code is being entered in a text field.
#IBAction func StartWizard(_ sender: UIButton) {
//Store entered access code
let accessCode = AccessCodeField.text!
var accessCodeFound: Bool? = nil
//Call API to validate Access Code
getResponse(accessCode:accessCode, UDID:myDeviceUDID) { result in
accessCodeFound = result["Found"] as! Bool
print("Value of Found during function:")
print(accessCodeFound)
//accessCodeFound = true
}
//If access code is valid, go to License view
print("Value of Found after function:")
print(accessCodeFound)
//accessCodeFound = nil ???
//it seems the value is getting reset after the function completes
if accessCodeFound == true{
//Load License View
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let licenseController = storyboard.instantiateViewController(identifier: "LicenseViewPanel")
self.show(licenseController, sender: Any?.self)
}
}

flatMap Not returning onCompleted

I have created below function with chaining of multiple observables however whatever I do it does not seem to call completed ? it only return the following:
(facebookSignInAndFetchData()) -> subscribed
(facebookSignInAndFetchData()) -> Event next(())
even though when I debug the observables individually they all return completed
here is my chaining function
func facebookSignInAndFetchData() {
observerFacebook.flatMap { (provider: FacebookProvider) in
return provider.login()
}.flatMap { token in
return self.loginViewModel.rx_authenticate(token: token)
}.flatMap {
return self.loginViewModel.fetchProfileData()
}.debug().subscribe(onError: { error in
//Guard unknown ErrorType
guard let err = error as? AuthError else {
//Unknown error message
self.alertHelper.presentAlert(L10n.unknown)
return
}
//error message handling
switch err {
case .notLoggedIn:
print("not logged in")
break
default:
self.alertHelper.presentAlert(err.description)
}
}, onCompleted: {
self.goToInitialController()
}).addDisposableTo(self.disposeBag)
}
rx_authenticate
func rx_authenticate(token: String) -> Observable<Void> {
return Observable.create({ observer in
let credentials = SyncCredentials.facebook(token: token)
SyncUser.logIn(with: credentials, server: URL(string: Globals.serverURL)!, onCompletion: { user, error in
//Error while authenticating
guard error == nil else {
print("error while authenticating: \(error!)")
observer.onError(AuthError.unknown)
return
}
//Error while parsing user
guard let responseUser = user else {
print("error while authenticating: \(error!)")
observer.onError(AuthError.unknown)
return
}
//Authenticated
setDefaultRealmConfiguration(with: responseUser)
//next
observer.onNext()
//completed
observer.onCompleted()
})
return Disposables.create()
})
}
fetchProfileData
func fetchProfileData() -> Observable<Void> {
return Observable.create({ observer in
//Fetch facebookData
let params = ["fields" : "name, picture.width(480)"]
let graphRequest = GraphRequest(graphPath: "me", parameters: params)
graphRequest.start {
(urlResponse, requestResult) in
switch requestResult {
case .failed(_):
//Network error
observer.onError(AuthError.noConnection)
break
case .success(let graphResponse):
if let responseDictionary = graphResponse.dictionaryValue {
guard let identity = SyncUser.current?.identity else {
//User not logged in
observer.onError(AuthError.noUserIdentity)
return
}
//Name
let name = responseDictionary["name"] as! String
//Image dictionary
let pictureDic = responseDictionary["picture"] as! [String: Any]
let dataDic = pictureDic["data"] as! [String: Any]
let imageHeight = dataDic["height"] as! Int
let imageWidth = dataDic["width"] as! Int
let url = dataDic["url"] as! String
//Create Person object
let loggedUser = Person()
loggedUser.id = identity
loggedUser.name = name
//Create photo object
let photo = Photo()
photo.height = imageHeight
photo.width = imageWidth
photo.url = url
//Append photo object to person object
loggedUser.profileImage = photo
//Save userData
let realm = try! Realm()
try! realm.write {
realm.add(loggedUser, update: true)
}
//next
observer.onNext()
//completed
observer.onCompleted()
} else {
//Could not retrieve responseData
observer.onError(AuthError.noResponse)
}
}
}
return Disposables.create()
})
}
observerFacebook
//FacebookProvider
private lazy var observerFacebook: Observable<FacebookProvider>! = {
self.facebookButton.rx.tap.map {
return FacebookProvider(parentController: self)
}
}()
The chain starts with calling observerFacebook, which returns an observable that will emit values everytime facebookButton is tapped.
This observable will only complete when facebookButton gets released, most probably when the view controller holding it is removed from screen.
The rest of the chain will map or flatMap, but never force completion as another tap will trigger the whole chain again.
The easy way to solve this would be to add a call to take(1) on facebookButton.rx.tap, so that the function would be defined like so:
private lazy var observerFacebook: Observable<FacebookProvider>! = {
self.facebookButton.rx.tap
.take(1)
.map {
return FacebookProvider(parentController: self)
}
}()
Now, observerFacebook will complete after the first tap and you should see a call to onCompleted.
Note that you'll need to resubscribe to the chain on errors if you want to perform it again when another tap comes in.

Why does the value of a local variable is not being initialised in try block.

I have a local variable of Bool type initialised to FALSE. Am trying to set the value of this in the try block depending on certain condition. The condition is true, the value becomes true. When checked for the values inside the block and outside, they are different. Inside it is true and outside it takes the initialised value ie; false.
func Print() -> Bool{
print(userName)
print(password)
var statusValue:Bool = False
let request: Request = Request()
let body: NSMutableDictionary = NSMutableDictionary()
do{
try request.post(url: url, body: body, completionHandler: { data, response, error in
if error != nil{
print(error?.localizedDescription)
return
}
let statuscode = response as? HTTPURLResponse
print("error:",error)
print("status code:",statuscode?.statusCode)
if statuscode?.statusCode == 200 {
print("Success")
statusValue = True
}
else{
print("Fail")
}
})
}
catch{
print("Not me error")
}
print("Printing status value outside:",statusValue)
return statusValue
}
The value for "statusValue" prints different. Inside it sets to True, outside it prints False.
Returning a value from a function works when your code is synchronous but in your case you're making a network request (asynchronous call) so the result will be available after some milliseconds or seconds then returning a value doesn't make any sense.
Instead you could "return" your result using closures like so:
func Print( completion: #escaping (_ statusValue: Bool) -> Void ) -> Void {
var statusValue:Bool = False
let request: Request = Request()
let body: NSMutableDictionary = NSMutableDictionary()
do{
try request.post(url: url, body: body, completionHandler: { data, response, error in
if error != nil{
print(error?.localizedDescription)
completion(false)
return
}
let statuscode = response as? HTTPURLResponse
if statuscode?.statusCode == 200 {
print("Success")
statusValue = True
completion(statusValue)
}
else{
completion(false)
}
})
}
catch{
completion(false)
}
}
When you call the function:
Print(completion: { statusValue in
print(statusValue)
})

Sorting JSON when using Alamofire and SwiftyJSON

Morning all,
I've been following along the examples in the excellent iOS Apps With REST APIs book and as a result using Alamofire and SwiftyJSON. One thing I don't see mentioned in the book is how you would sort the incoming json objects into a specific order, eg. date. The code I've pasted below works fine for pulling in the json objects however as far as I can tell, they're automatically ordered by created_by. I'd like to sort by a different order, lets say my class was called Vegetable and had a name attribute so that I could sort by something like:
.sort { $0.name < $1.name }
I'll start with the Vegetable class in Vegetable.swift
class Vegetable: ResponseJSONObjectSerializable {
var id: Int?
var name : String?
var date: NSDate?
}
Inside my JSONSerializer file I have the following, I'm not sure I'd wish to change the order directly in here as I'd prefer some more flexibility with each call.
public func responseArray<T: ResponseJSONObjectSerializable>(completionHandler: Response<[T], NSError> -> Void) -> Self {
let serializer = ResponseSerializer<[T], NSError> { request, response, data, error in
guard error == nil else {
return .Failure(error!)
}
guard let responseData = data else {
let failureReason = "Array could not be serialized because input data was nil."
let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let result = JSONResponseSerializer.serializeResponse(request, response, responseData, error)
switch result {
case .Success(let value):
let json = SwiftyJSON.JSON(value)
var objects: [T] = []
for (_, item) in json {
if let object = T(json: item) {
objects.append(object)
}
}
return .Success(objects)
case .Failure(let error):
return .Failure(error)
}
}
return response(responseSerializer: serializer, completionHandler: completionHandler)
}
Then, in my APIManager I have the following function
func getAllVegetables(completionHandler: (Result<[Vegetable], NSError>) -> Void) {
Alamofire.request(VegetableRouter.GetVegetables())
.responseArray { (response:Response<[Vegetable], NSError>) in
completionHandler(response.result)
}
}
Finally, populate my tableview I have:
func loadVegetables() {
self.isLoading = true
VegetablesAPIManager.sharedInstance.getAllVegetables() {
result in
self.isLoading = false
if self.refreshControl.refreshing {
self.refreshControl.endRefreshing()
}
guard result.error == nil else {
print(result.error)
// TODO: Display Error
return
}
if let fetchedVegetables = result.value {
self.vegetables = fetchedVegetables
for vegetable in fetchedVegetables {
// Nothing here at the moment
}
}
self.tableView.reloadData()
}
}
I appreciate any help I can get with this, Thanks!
Since you have a NSDate property, you can sort with the compare method of NSDate.
let sorted = result.value.sort { $0.date.compare($1.date) == .OrderedAscending }