How to create delegate/callback inside framework in Swift? [duplicate] - ios

This question already has answers here:
How could I create a function with a completion handler in Swift?
(9 answers)
Closed 3 years ago.
I have been implementing login framework to use in app development. I have created login framework functionality and it works fine.
Now i'm trying to do callback of return response of whether it success or failed from app to framework wise versa framework to app.
here is my framework code:
public func loginApiCall(username: String?, password: String?) {
let parameters = [
"username": username,
"password": password
]
print(parameters)
let url = "apiUrl/authentication/"
Alamofire.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: [:]).responseJSON {
response in
switch (response.result) {
case .success:
print(response)
break
case .failure:
print(Error.self)
}
}
}
Here is my sampleapp where i have using myframework:
override func viewDidLoad() {
super.viewDidLoad()
//framework called here...
let apiCall = APICall.init()
apiCall.loginApiCall(username: "demo", password: "demo")
}//viewdidload

you can achieve using callback
// framework code
public func loginApiCall(username: String?, password: String?, callback : #escaping ((Bool) -> Void)) {
let parameters = [
"username": username,
"password": password
]
print(parameters)
let url = "apiUrl/authentication/"
Alamofire.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: [:]).responseJSON {
response in
switch (response.result) {
case .success:
callback(true)
case .failure:
callback(false)
}
}
}
// viewDidLoad code
override func viewDidLoad() {
super.viewDidLoad()
//framework called here...
let apiCall = APICall.init()
apiCall.loginApiCall(username: "demo", password: "demo") { (status) in
print("code")
}
}
//NOTE : create completionHandler as per your requirement, it is a demo completionHandler

first you have to define completion handler, after then you can use it inside of your function parameter. I suggest you to check out completions to achieve that easily.
typealias DownloadComplete = () -> ()
public func loginApiCall(username: String?, password: String?, completed: #escaping DownloadComplete))
{
let parameters = [
"username": username,
"password": password
]
print(parameters)
let url = "apiUrl/authentication/"
Alamofire.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: [:]).responseJSON {
response in
switch (response.result) {
case .success:
completed(). // important part
case .failure:
}
}

Related

How can i separate my networking code from my ViewController

I'm using Alamofire for make a post request at the moment. I first built it in the ViewController and worked. But then i'm trying to separate it by building it in another class. I use a singleton and still works when i call in the ViewController. The only problem is after the status coming back a success i am trying to instantiate the another ViewController when the user logs in. I don't want to do it in the networking class, i would like to do it inside my ViewController. How can i achieve this?
enum Status: Int {
case Success = 200
case Failure = 401
}
enum HTTPMethod: String {
case Post = "POST"
case Get = "GET"
}
enum APIKey: String {
case value = "somevalueeeeeeeee"
case key = "somekeyyyyyyyyyyyyyy"
}
import Foundation
import Alamofire
import UIKit
class NetworkManager {
static let shared = NetworkManager()
func makePostRequest(url: URL, params: Parameters, headers: HTTPHeaders, credential: URLCredential) {
Alamofire.request(url, method: .post, parameters: params, headers: headers).authenticate(usingCredential: credential)
.responseJSON { response in
print(response)
let status = response.response?.statusCode
switch status {
case Status.Success.rawValue:
print("I would like to instantiate my viewcontroller here")
case Status.Failure.rawValue:
print("Bad Request")
default:
break
}
}
}
}
import UIKit
import Alamofire
class LoginViewController: UIViewController {
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var passwordTextfield: UITextField!
private func login() {
guard let userName = emailTextField.text else { return }
guard let passWord = passwordTextfield.text else { return }
let params: Parameters = [
"username": userName,
"password": passWord
]
guard let url = URL(string: URLS.url.rawValue) else { return }
let headers = [APIKey.key.rawValue: APIKey.value.rawValue]
let credential = URLCredential(user: userName, password: passWord, persistence: .forSession)
NetworkManager.shared.makePostRequest(url: url, params: params, headers: headers, credential: credential)
}
#IBAction func onLoginTapped(_ sender: UIButton) {
login()
}
}
Add completion block to your function :
func makePostRequest(url: URL, params: Parameters, headers: HTTPHeaders, credential: URLCredential, completion:#escaping(Bool)->()) {
Alamofire.request(url, method: .post, parameters: params, headers: headers).authenticate(usingCredential: credential)
.responseJSON { response in
print(response)
let status = response.response?.statusCode
switch status {
case Status.Success.rawValue:
print("I would like to instantiate my viewcontroller here")
completion(true)
break
case Status.Failure.rawValue:
print("Bad Request")
completion(false)
break
default:
break
}
}
}
And call it like this from ViewController:
NetworkManager.shared.makePostRequest(url: URL, params: Parameters, headers: HTTPHeaders, credential: "") { (success) in
if success {
} else {
}
}
You can pass the completion handler to your controller from Network manager class.
func makePostRequest(url: URL, params: Parameters, headers: HTTPHeaders, credential: URLCredential, completion:#escaping (Result<Any>)->()) {
Alamofire.request(url, method: .post, parameters: params, headers: headers).authenticate(usingCredential: credential)
.responseJSON { response in
print(response)
completion(response.result)
let status = response.response?.statusCode
switch status {
case Status.Success.rawValue:
print("I would like to instantiate my viewcontroller here")
completion(response.result)
break
case Status.Failure.rawValue:
print("Bad Request")
completion(response.result)
break
default:
break
}
}
}
And now You can call this function as like below
NetworkManager.shared.makePostRequest(url: url, params: , headers: param, credential: credential, completion: { (response) in
if result.isSuccess, let json = result.value {
// Use json variable to handle json
} else {
self.showAlert(R.string.keys.somethingWentWrong())
}
})

Performing tasks sequentially in Swift [duplicate]

This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 4 years ago.
I'm trying to verify a login and return a boolean value from my function accordingly, but my return statement keeps getting executed before the web service function is completed, even if I use an async method. I am using both Alamofire and SwiftyJSON.
I'm attaching my code below. Any help would be appreciated!
Thanks.
func checkUs (name: String, password: String) -> Bool
{
bool authen = false
DispatchQueue.global(qos: .userInitiated).async {
let jsonDic : [String: String] = ["email": name, "pass": password]
Alamofire.request("enter URL here", method: .post, parameters: jsonDic, encoding: JSONEncoding.default, headers: nil).responseJSON { (response) in
switch(response.result) {
case .success(let sentJSON):
let gotJSON = JSON (sentJSON)
print (gotJSON[0]["status"].boolValue)
authen = gotJSON[0]["status"].boolValue
case .failure(let err):
print(err)
}
print ("First ", authen)
}
}
print ("Second", authen)
return authen
//return true
Log Output:
Second false
true
First true
You need completion , also Alamfire runs asynchronously no need for global queue
func checkUs (name: String, password: String,completion: #escaping (_ status: Bool,_ err:Error?) -> Void) {
bool authen = false
let jsonDic : [String: String] = ["email": name, "pass": password]
Alamofire.request("enter URL here", method: .post, parameters: jsonDic, encoding: JSONEncoding.default, headers: nil).responseJSON { (response) in
switch(response.result) {
case .success(let sentJSON):
let gotJSON = JSON (sentJSON)
print (gotJSON[0]["status"].boolValue)
authen = gotJSON[0]["status"].boolValue
completion(authen,nil)
case .failure(let err):
print(err)
completion(authen,err)
}
print ("First ", authen)
}
print ("Second", authen)
}
//
call it like this
self.checkUs(name: "endedName", password: "sendedPassword") { (status, error) in
if let err = error {
}
else
{
}
}

Call same Web Service multiple times from different View Controllers(not at the same time) in ios

I was just wondering what would be the best way to call the same Web Service from Different View Controllers(at different time). What architecture or design should I follow? I don't want to write the same code in each View Controller.
In case of using Alamofire library I can suggest to use
class NetworkManager {
static let shared = NetworkManager()
static let alamofireManager: SessionManager = {
let sessionConfiguration = URLSessionConfiguration.default
sessionConfiguration.timeoutIntervalForRequest = TimeInterval(_TIMEOUT)
sessionConfiguration.timeoutIntervalForResource = TimeInterval(_TIMEOUT)
return Alamofire.SessionManager(configuration: sessionConfiguration)
}()
func performRequest(url: String,
method: HTTPMethod = .get,
parameters: [String: Any] = [String: Any](),
encoding: ParameterEncoding = URLEncoding.default,
contentType: String? = nil,
headers: HTTPHeaders = [String: String](),
success: #escaping(Data, Int) -> (),
failure: #escaping(CustomError) -> ()) {
debugPrint("NetworkManager is calling endpoint: \(url)")
NetworkManager.alamofireManager.request(url, method: method, parameters: parameters, encoding: encoding, headers: headers).validate().response { response in
guard let status = response.response?.statusCode, let data = response.data else {
if let error = response.error {
debugPrint("Error when calling endpoint \(url)")
failure(.unknownError(message: error.localizedDescription))
}
return
}
debugPrint("HTTP Status received: \(status)")
success(data, status)
}
} else {
failure(.noNetworkConnection)
}
}
Please feel free to modify failure handler with your custom error or whatever you like.
Of course then you need to serialise the response.
Create a base class for Alamofire request like that:
import Alamofire
/// Struct for create AlamofireRequestModal
struct AlamofireRequestModal {
/// Struct constant for Alamofire.HTTPMethod
var method: Alamofire.HTTPMethod
/// Struct constant for path
var path: String
/// Struct constant for parameters
var parameters: [String: AnyObject]?
/// Struct constant for encoding:ParameterEncoding
var encoding: ParameterEncoding
/// Struct constant for headers
var headers: [String: String]?
///method to get init
init() {
method = .post
path = ""
parameters = nil
encoding = JSONEncoding() as ParameterEncoding
}
}
///BaseService to call the api's
class BaseService: NSObject {
/// network variable for Reachability
let network = Reachability.init(hostname: "https://www.google.com")
/**
This is method for call WebService into Alamofire
- parameter alamoReq: this is AlamofireRequestModal type request
- parameter success: success response
- parameter failure: failer object
*/
func callWebServiceAlamofire(_ alamoReq: AlamofireRequestModal, success:#escaping ((_ responseObject: AnyObject?) -> Void), failure:#escaping ((_ error: NSError?) -> Void)) {
guard (network?.isReachable)! else {
debugPrint("\n No Network Connection")
return
}
let request = Alamofire.request(alamoReq.path, method: alamoReq.method, parameters: alamoReq.parameters, encoding: alamoReq.encoding, headers: alamoReq.headers)
// Call response handler method of alamofire
request.validate(statusCode: 200..<600).responseJSON(completionHandler: { response in
let statusCode = response.response?.statusCode
if let allHeaderField = response.response {
allHeaderField.setHeaders()
}
switch response.result {
case .success(let data):
if statusCode == 200 {
success(data as AnyObject)
} else {
failure(NSError.init(domain: "www.wen.com", code: 101010, userInfo: ["message": "Something went wrong. Please trt again."]))
}
case .failure(let error):
failure(error as NSError?)
}
})
}
}
Then create a service class according to use, like here I am creating Profile service class for login and registration and profile type all api's are added in this class so you can create multiple serice class according to use:
import Alamofire
///Profile service to call the profile api's
class ProfileService: BaseService {
/**
This is request to BaseService to get Login
- parameter email: User email id
- parameter password: User password to login
- parameter success: success response
- parameter failure: failure response
*/
func doLogin(email: String, password: String, success: #escaping ((_ response: AnyObject?) -> Void), failure: #escaping ((_ error: NSError?) -> Void)) {
var request = AlamofireRequestModal()
request.path = "www.yourpath.com"
request.parameters = ["email": email as AnyObject,
"password": password as AnyObject
]
callWebServiceAlamofire(request, success: success, failure: failure)
}
}
Now, You can call this doLogin method of Profile service from anywhere like Or you can create more layer like Model class and call this service from model class or You can call directly like this:
ProfileService().doLogin(email: "Email", password: "Password", success: { (response) in
// Code here for handle success response
}) { (error) in
// Code here for handle error
}
I would write a network manager class, that takes the web service parameters if any as arguments.
Here is a crude example of the architecture
class YourNetworkManager {
public func callSpecificWebService(argument : Type?, [optional closure to handle results]) {
// Generate the actual URL to be called here. Usually by
// appending a suffix to some constant base url
// The web service call mechanism goes here.
// This could either use the NSURLSession API
// or some third party library such as Alamofire
// Process the generic response conditions from the web service
// here. Pass on the specific parts to the calling method.
}
}
As I mentioned, this is a crude example. The more modularised you can make things, the better it will be.
Never pass your Views and/or ViewControllers to you NetworkManager class.
Suppose you have a NetworkManager class like this.
open class NetworkHelper {
class var sharedManager: NetworkHelper {
struct Static{
static let instance: NetworkHelper = NetworkHelper()
}
return Static.instance
}
func request(_ method: HTTPMethod, _ URLString: String, parameters: [String : AnyObject]? = [:], headers: [String : String]? = [:], completion:#escaping (Any?) -> Void, failure: #escaping (Error?) -> Void) {
let URL = "BASE_PATH" + "URLString"
Alamofire.request(URL, method: method, parameters: parameters, encoding: JSONEncoding.default, headers: headers)
.responseJSON { response in
switch response.result {
case .success:
completion(response.result.value!)
case .failure(let error):
failure(error)
guard error.localizedDescription == JSON_COULDNOT_SERIALISED else {
return
}
}
}
}
}
Now create a BaseViewController which inherit from UIViewController and write your API call with necessary parameters.
For example in an API call you only need userID as param all else is static.
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
func makeThisApiCallWithUserID(userId: Int) {
NetworkHelper.sharedManager.request(.get, "api/v1/yourApiAddress", parameters: ["userId":userId as AnyObject],headers: ["Authorization":"Bearer agshd81tebsf8724j"], completion: { (response) in
}) { (error) in
}
}
}
No you should inherit those ViewController in which you want the same API call and you do not want to write the code again.
class FirstChildViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.makeThisApiCallWithUserID(userId: 123)
}
}
class SecondChildViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.makeThisApiCallWithUserID(userId: 13)
}
}
class ThirdChildViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.makeThisApiCallWithUserID(userId: 3)
}
}
See I haven't write API code in FirstChildViewController, SecondChildViewController, ThirdChildViewController but still they can make the same API call with different parameters.
Do you use Alamofire?, if yes then I have good method for it, written in NetworkHelper Class.
import Foundation
import Alamofire
open class NetworkHelper {
class var sharedManager: NetworkHelper {
struct Static{
static let instance: NetworkHelper = NetworkHelper()
}
return Static.instance
}
func request(_ method: HTTPMethod
, _ URLString: String
, parameters: [String : AnyObject]? = [:]
, headers: [String : String]? = [:]
, onView: UIView?, vc: UIViewController, completion:#escaping (Any?) -> Void
, failure: #escaping (Error?) -> Void) {
let URL = BASE_PATH + URLString
Alamofire.request(URL, method: method, parameters: parameters, encoding: JSONEncoding.default, headers: headers)
.responseJSON { response in
switch response.result {
case .success:
completion(response.result.value!)
case .failure(let error):
failure(error)
guard error.localizedDescription == JSON_COULDNOT_SERIALISED else {
return
}
}
}
}
}

How to call a method with completion handle in swift 3+

I am very new to swift. I have a method like this.
public func prepareUrl (appendString:String,bindedParams:String,isAuthorized:Bool,isGet:Bool,jsonBody:[String:String],completion:#escaping(String)->Void)
{
let baseUrl=Bundle.main.value(forKey: "GlobalURL")
let urlString=baseUrl as! String+appendString as String+bindedParams as String
Alamofire.request(urlString, method: .get, parameters: nil, encoding: JSONEncoding.default)
.downloadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in
print("Progress: \(progress.fractionCompleted)")
}
.validate { request, response, data in
// Custom evaluation closure now includes data (allows you to parse data to dig out error messages if necessary)
return .success
}
.responseJSON { response in
debugPrint(response)
}
}
I have no idea how to call this method since it's having a completion handler part too. How can I call this method. Please help me.
Thanks
To call method like this:
self.prepareUrl(appendString: "www.some.com/api/likeLogin", bindedParams: "name=lee", isAuthorized: false, isGet: true, jsonBody: ["key":"value"]) { (returnString) in
if returnString == "someValue" {
//do something
}
else{
}
}
And in the method, you should call the completion to return value, like:
.responseJSON { response in
completion("aReturnString")
}
Although the method name is prepareUrl, it actually requests the WebApi, so it's better to rename it to request.
Try this :
NOTE :This answer is for example . You need to change as per your needs
func getResponse(url: String, method : HTTPMethod, parameter : [String : AnyObject], Alert : Bool, callback: #escaping responseHandler) -> Void{
Alamofire.request(API_PREFIX + url, method: method, parameters: parameter).validate().responseJSON { response in
switch response.result {
case .success:
if let result = response.result.value {
let JSON = result as! [String : AnyObject]
print("\(JSON as AnyObject)")
callback(JSON as AnyObject, true)
}
case .failure(let error):
print(error)
callback({} as AnyObject, false)
}
}
}
Calling of method using closure
self.getResponse(url: "", method: .post, parameter: ["Email" : "" as AnyObject, "Password" : "" as AnyObject], Alert: true) { (responseObject, success) in
if success{
}
}

POST request with data in body with Alamofire 4

how is it possible to send a POST request with a data in the HTTP body with Alamofire 4? I used custom encoding at swift 2.3 it was working good. I converted my code swift 3 and I tried to paramater encoding but not working. This code :
public struct MyCustomEncoding : ParameterEncoding {
private let data: Data
init(data: Data) {
self.data = data
}
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
do {
urlRequest.httpBody = data
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
} catch {
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}
return urlRequest
}
and Alamofire request :
let enco : ParameterEncoding = MyCustomEncoding(data: ajsonData)
Alamofire.request(urlString, method: .post , parameters: [:], encoding: enco , headers: headers).validate()
.responseJSON { response in
switch response.result {
case .success:
print(response)
break
case .failure(let error):
print(error)
}
}
You need to send request like below in swift 3
let urlString = "https://httpbin.org/get"
Alamofire.request(urlString, method: .post, parameters: ["foo": "bar"],encoding: JSONEncoding.default, headers: nil).responseJSON {
response in
switch response.result {
case .success:
print(response)
break
case .failure(let error):
print(error)
}
}
Swift 5 with Alamofire 5:
AF.request(URL.init(string: url)!, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseJSON { (response) in
print(response.result)
switch response.result {
case .success(_):
if let json = response.value
{
successHandler((json as! [String:AnyObject]))
}
break
case .failure(let error):
failureHandler([error as Error])
break
}
}
Alamofire using post method
import UIKit
import Alamofire
class ViewController: UIViewController {
let parameters = [
"username": "foo",
"password": "123456"
]
let url = "https://httpbin.org/post"
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: [:]).responseJSON {
response in
switch (response.result) {
case .success:
print(response)
break
case .failure:
print(Error.self)
}
}
}
This will work better in Swift 4.
let url = "yourlink.php". // This will be your link
let parameters: Parameters = ["User_type": type, "User_name": name, "User_email": email, "User_contact": contact, "User_password": password, "from_referral": referral] //This will be your parameter
Alamofire.request(url, method: .post, parameters: parameters).responseJSON { response in
print(response)
}
Please find the code below
**
pod 'Alamofire', '~> 5.4'
**
**
pod 'ObjectMapper', '~> 4.2'
**
**
pod 'SwiftyJSON'
**
pod 'TPKeyboardAvoiding'
Use Model
import ObjectMapper
class LoginModel : Mappable{
var status : String?
var data : [DataModel]?
var message : String?
required init?(map: Map) {
}
func mapping(map: Map) {
status <- map["status"]
data <- map["data"]
message <- map["message"]
}
}
class DataModel : Mappable{
var access_token : String?
var isvideo : String?
required init?(map: Map) {
}
func mapping(map: Map) {
access_token <- map["access_token"]
isvideo <- map["isvideo"]
}
}
Call API
HTTPNetwork().getHTTPData("", parameters: LoginParameter, completion: {(successresponse) -> Void in
if let res = successresponse{
print("sucess token \(res["message"].string!)")
if let myuser = Mapper<DataModel>().map(JSONString: res["data"].rawString()!){
print("access_token \(myuser.access_token)")
}
}
}, error: {(errorresponse)-> Void in
if let res = errorresponse{
print("Error response token \(res)")
}
})
public func getHTTPData(_ request: String, parameters : Parameters?, completion: #escaping ( JSON?) -> Void, error: #escaping ( JSON?) -> Void){
AF.request(URL.init(string: "url")!, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: ["Content-Type":"application/json"]).responseJSON { (response) in
print(response.result)
switch response.result{
case .success:
if let json = response.value as? [String : Any]{
if let output:JSON = JSON(response.value!){
if json["isSuccess"] as? Int == 1{
completion(output)
}else{
error(output)
}
}
}else{
completion(nil)
}
case .failure:
completion(nil)
}
}
}
Alamofire for GET and POST method using Alamofire
1.Create a file named "GlobalMethod" for multiple use
import Alamofire
class GlobalMethod: NSObject {
static let objGlobalMethod = GlobalMethod()
func ServiceMethod(url:String, method:String, controller:UIViewController, parameters:Parameters, completion: #escaping (_ result: DataResponse<Any>) -> Void) {
var headers = Alamofire.SessionManager.defaultHTTPHeaders
headers["HeaderKey"] = "HeaderKey"
if method == "POST" {
methodType = .post
param = parameters
}
else {
methodType = .get
}
Alamofire.request(url, method: methodType, parameters: param, encoding: JSONEncoding.default, headers:headers
).responseJSON
{ response in
completion(response)
}
}
}
In the View Controller call "ServiceMethod" created in GlobalMethod by sending values to call API Service
let urlPath = "URL STRING"
let methodType = "GET" or "POST" //as you want
let params:[String:String] = ["Key":"Value"]
GlobalMethod.objGlobalMethod.ServiceMethod(url:urlPath, method:methodType, controller:self, parameters:params)
{
response in
if response.result.value == nil {
print("No response")
return
}
else {
let responseData = response.result.value as! NSDictionary
print(responseData)
}
}

Resources