UIActivityIndicatorView not showing before the URLSession.shared.dataTask - swift

My UIActivityIndicatorView is not showing when I call it before the API request, and will show after the request has been done,
This is my function that run inside the TouchUpInside of my button
func onLogin () {
let activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView()
activityIndicator.center = view.center
activityIndicator.hidesWhenStopped = true
activityIndicator.style = UIActivityIndicatorView.Style.gray
view.addSubview(activityIndicator)
activityIndicator.startAnimating()
do {
let data = try self.getData()
let loginData = try JSONDecoder().decode(LoginResponse.self, from: data!)
print(loginData)
} catch {
let alert = UIAlertController(title: "Login failed", message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true, completion: nil)
}
}
and my request code was
static func getData() throws -> Data? {
let urlData = URL(string: "www.example.com")
var request : URLRequest = URLRequest(url: urlData!)
request.httpMethod = "POST"
request.httpBody = self.getBodyString.data(using: .utf8)
var data: Data?
var response: URLResponse?
var error: Error?
let semaphore = DispatchSemaphore(value: 0)
URLSession.shared.dataTask(with: request) { d,r,e in
data = d
response = r
error = e
semaphore.signal()
}.resume()
_ = semaphore.wait(timeout: .distantFuture)
if error != nil {
throw error!
}
return data
}
when I remove the do catch with getData() in my onLogin() function the UIActivityIndicatorView was working good.
The response of my API call was request timeout, but I want to see the indicator loading the request.

Try to bring activity indictor to front of UIview and Use it in DispatchQueue.main.async

Related

CCavenue Payment Gateway with WKWebView - iOS (as UiWebview is deprecated since 2020 by apple)

Post December 2020, apple does not allow UiWebView support. ccavenue, being popular payment gateway in India, still have not updated their sdk from official website.
Here is the the complete code, to be replaced for UiWebView issue.
class CCWebViewController: BaseViewController, WKNavigationDelegate {
var mywebview: WKWebView!
var request: NSMutableURLRequest?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setupWebView()
notification = NotificationCenter.default.addObserver(forName: .UIApplicationWillEnterForeground, object: nil, queue: .main) {
[unowned self] notification in
self.checkResponseUrl()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if !isHere {
isHere = true
self.gettingRsaKey(){
(success, object) -> () in
DispatchQueue.main.sync {
if success {
self.encyptCardDetails(data: object as! Data)
}
else{
self.displayAlert(msg: object as! String)
}
}
}
}
}
override func viewDidAppear(_ animated: Bool) {
LoadingOverlay.shared.showOverlay(view: self.view)
}
private func setupWebView(){
//setup webview
let config = WKWebViewConfiguration()
self.mywebview = WKWebView(frame: CGRect.init(x: self.view.bounds.origin.x, y: self.view.bounds.origin.y, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height), configuration: config)
mywebview.navigationDelegate = self
self.view.addSubview(mywebview)
}
private func gettingRsaKey(completion: #escaping (_ success: Bool, _ object: AnyObject?) -> ()){
DispatchQueue.main.async {
self.rsaKeyDataStr = "access_code=\(self.accessCode)&order_id=\(self.orderId)"
let requestData = self.rsaKeyDataStr.data(using: String.Encoding.utf8)
guard let urlFromString = URL(string: self.rsaKeyUrl) else{
return
}
var urlRequest = URLRequest(url: urlFromString)
urlRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "content-type")
urlRequest.httpMethod = "POST"
urlRequest.httpBody = requestData
let session = URLSession(configuration: URLSessionConfiguration.default)
print("session",session)
session.dataTask(with: urlRequest as URLRequest) {
(data, response, error) -> Void in
if let response = response as? HTTPURLResponse, 200...299 ~= response.statusCode{
guard let responseData = data else{
print("No value for data")
completion(false, "Not proper data for RSA Key" as AnyObject?)
return
}
print("data :: ",responseData)
completion(true, responseData as AnyObject?)
}
else{
completion(false, "Unable to generate RSA Key please check" as AnyObject?) }
}.resume()
}
}
private func encyptCardDetails(data: Data){
guard let rsaKeytemp = String(bytes: data, encoding: String.Encoding.ascii) else{
print("No value for rsaKeyTemp")
return
}
rsaKey = rsaKeytemp
rsaKey = self.rsaKey.trimmingCharacters(in: CharacterSet.newlines)
rsaKey = "-----BEGIN PUBLIC KEY-----\n\(self.rsaKey)\n-----END PUBLIC KEY-----\n"
print("rsaKey :: ",rsaKey)
let myRequestString = "amount=\(amount)&currency=\(currency)"
do{
let encodedData = try RSAUtils.encryptWithRSAPublicKey(str: myRequestString, pubkeyBase64: rsaKey)
var encodedStr = encodedData?.base64EncodedString(options: [])
let validCharSet = CharacterSet(charactersIn: "!*'();:#&=+$,/?%#[]").inverted
encodedStr = encodedStr?.addingPercentEncoding(withAllowedCharacters: validCharSet)
CCWebViewController.statusCode = 0
//Preparing for webview call
if CCWebViewController.statusCode == 0{
CCWebViewController.statusCode = 1
let urlAsString = "https://secure.ccavenue.com/transaction/initTrans"
let encryptedStr = "merchant_id=\(merchantId)&order_id=\(orderId)&redirect_url=\(redirectUrl)&cancel_url=\(cancelUrl)&enc_val=\(encodedStr!)&access_code=\(accessCode)&billing_name=\(billingName)&billing_address=\(billingAddress)&billing_city=\(billingCity)&billing_state=\(billingState)&billing_country=\(billingCountry)&billing_tel=\(billingMobile)&billing_zip=\(pincode)&billing_email=\(billingEmail)&merchant_param1=\(notes)&billing_country=\(billingCountry)&merchant_param2=\(notes)&merchant_param3=\(notes)&merchant_param4=\(notes)&delivery_country=\(billingCountry)&delivery_cust_notes=\(notes)"
let myRequestData = encryptedStr.data(using: String.Encoding.utf8)
request = NSMutableURLRequest(url: URL(string: urlAsString)! as URL, cachePolicy: NSURLRequest.CachePolicy.reloadIgnoringCacheData, timeoutInterval: 30)
request?.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "content-type")
request?.setValue(urlAsString, forHTTPHeaderField: "Referer")
request?.httpMethod = "POST"
request?.httpBody = myRequestData
self.mywebview.load(request! as URLRequest)
}
else{
print("Unable to create requestURL")
displayAlert(msg: "Unable to create requestURL")
}
}
catch let err {
print(err)
}
}
func displayAlert(msg: String){
let alert: UIAlertController = UIAlertController(title: "ERROR", message: msg, preferredStyle: UIAlertControllerStyle.alert)
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default) {
UIAlertAction in
LoadingOverlay.shared.hideOverlayView()
self.dismiss(animated: true, completion: nil)
}
alert.addAction(okAction)
self.present(alert, animated: true, completion: nil)
}
//MARK: WebviewDelegate Methods
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
self.showToast(type: 0, message: "Error", timeToDisplay: 2)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print(String(describing: webView.url))
LoadingOverlay.shared.hideOverlayView()
webView.evaluateJavaScript("document.documentElement.outerHTML", completionHandler: { result, error in
if let datHtml = result as? String {
print(datHtml)
// parse datHtml here
let string = webView.url!.absoluteString
if(string.contains(self.redirectUrl))
{
print(self.mywebview.isLoading)
let html = datHtml
print("html :: ",html)
var transStatus = "Not Known"
if(html.contains("tracking_id"))
{
if(html.contains("Success"))
{
transStatus = "Transaction Successful"
let controller: CCResultViewController = CCResultViewController()
controller.transStatus = transStatus
controller.isSucceed = true
self.present(controller, animated: true, completion: nil)
}
else if(html.contains("Aborted"))
{
transStatus = "Transaction Cancelled"
let controller: CCResultViewController = CCResultViewController()
controller.transStatus = transStatus
controller.isSucceed = false
self.present(controller, animated: true, completion: nil)
}
else
{
transStatus = "Transaction Failed"
let controller: CCResultViewController = CCResultViewController()
controller.transStatus = transStatus
controller.isSucceed = false
self.present(controller, animated: true, completion: nil)
}
}
else{
print("html does not contain any related data")
self.displayAlert(msg: "html does not contain any related data for this transaction.")
}
}
}
} )
}
}

Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread in swift

I have given DispatchQueue.main.async {} where it necessary but when i give break point from dataTask here it says
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread.'
Cannot be called with asCopy = NO on non-main thread.
class EventsViewController: UIViewController {
#IBOutlet weak var backBtn: UIButton!
var eventsListArray = [AnyObject]()
var eventType: String?
var eventList : EventsModel? = nil
#IBOutlet weak var eventsTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
getAllEventsList()
}
func getAllEventsList() {
DispatchQueue.main.async {
let deviceId: String = (UIDevice.current.identifierForVendor?.uuidString)!
let personalId: String = UserDefaults.standard.string(forKey: "regUserID") ?? ""//KeychainWrapper.standard.string(forKey: "USERID") ?? ""
let headers = ["deviceid": deviceId,"userType": "personal","key": personalId]
DispatchQueue.main.async {
let string = Constants.GLOBAL_URL + "/get/allevents"
var urlComponents = URLComponents(string: string)
let eventStatus = self.eventType
let requestEventType = URLQueryItem(name: "eventstatus", value: eventStatus)
urlComponents?.queryItems = [requestEventType]
let urlStr = urlComponents?.url
let request = NSMutableURLRequest(url: urlStr!, cachePolicy: .useProtocolCachePolicy,timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers as! [String : String]
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if error == nil {
let httpResponse = response as? HTTPURLResponse
if httpResponse!.statusCode == 200 {
do {
let jsonObject = try JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as! [String :AnyObject]
print("publish event \(jsonObject)")
self.eventList = EventsModel.init(fromDictionary: jsonObject)
DispatchQueue.main.async {
if self.eventList?.events.count != 0 {
DispatchQueue.main.async {
self.eventsTableView.reloadData()
}
}
else {
DispatchQueue.main.async {
Constants.showAlertView(alertViewTitle: "", Message: "No Events \(self.eventType)", on: self)
self.eventList?.events.removeAll()
self.eventsTableView.reloadData()
}
}
}
} catch { print(error.localizedDescription) }
} else {
Constants.showAlertView(alertViewTitle: "", Message: "Something went wrong, Please try again", on: self)
}
}
})
dataTask.resume()
}
}
}
}
You've probably missed a few spots where you're trying to present an alert when errors are thrown. Why don't you just enter the main queue right after the data request is complete.
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
DispatchQueue.main.async {
if error == nil {
//...
}
}
})

Load page into webview swift 2 from java class

I am developing an App for the iPhone using Xwebview which enables me to download a page then interact with the javascript on the downloaded page.
All works, but if the internet connection drops, a default local page is loaded, informing the user there is no internet connection. The page displays a retry button that, when pressed checks, the internet connection: if the connection is made the app tries to connect again to the external page and load the page into the webview.
I cannot get this to work: the code downloads the page (I can see this in my session data) but I can't get that page to load back into the webview.
override func viewDidLoad() {
super.viewDidLoad()
login()
}
func login()
{
// *********** Get stored hashkey **************
let hashcode = getHashcode()
// ********** Check network connection *********
let netConnection = Connection.isConnectedToNetwork()
print("net connection: ", netConnection)
if netConnection == true
{
if hashcode != "00000"
{
print("local key found", hashcode)
// We dont have local key
let webview = WKWebView(frame: view.frame, configuration: WKWebViewConfiguration())
//webview.loadRequest(NSURLRequest(URL: NSURL(string: "about:blank")!))
view.addSubview(webview)
webview.loadPlugin(jsapi(), namespace: "jsapi")
let url:NSURL = NSURL(string: serverLocation + onlineLoginApi)!
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData
let paramString = "/?username=username&password=password"
request.HTTPBody = paramString.dataUsingEncoding(NSUTF8StringEncoding)
let task = session.downloadTaskWithRequest(request) {
(
let location, let response, let error) in
guard let _:NSURL = location, let _:NSURLResponse = response where error == nil else {
print("error")
return
}
let urlContents = try! NSString(contentsOfURL: location!, encoding: NSUTF8StringEncoding)
guard let _:NSString = urlContents else {
print("error")
return
}
print(urlContents)
}
task.resume()
// you must tell webview to load response
webview.loadRequest(request)
}
else{
print("local key found", hashcode)
// ********* Found local key go to site pass key over ************
let webview = WKWebView(frame: view.frame, configuration: WKWebViewConfiguration())
view.addSubview(webview)
webview.loadPlugin(jsapi(), namespace: "jsapi")
let req = NSMutableURLRequest(URL: NSURL(string:serverLocation + onlineLoginApi + "?hashcode=\(hashcode)")!)
req.HTTPMethod = "POST"
req.HTTPBody = "/?hashcode=\(hashcode)".dataUsingEncoding(NSUTF8StringEncoding)
NSURLSession.sharedSession().dataTaskWithRequest(req)
{ data, response, error in
if error != nil
{
//Your HTTP request failed.
print(error!.localizedDescription)
} else {
//Your HTTP request succeeded
print(String(data: data!, encoding: NSUTF8StringEncoding))
}
}.resume()
webview.loadRequest(req)
}
}
else{
// No connection to internet
let webview = WKWebView(frame: view.frame, configuration: WKWebViewConfiguration())
view.addSubview(webview)
webview.loadPlugin(jsapi(), namespace: "jsapi")
let root = NSBundle.mainBundle().resourceURL!
let url = root.URLByAppendingPathComponent("/www/error-no-connection.html")
webview.loadFileURL(url, allowingReadAccessToURL: root)
print("No internet connection")
}
}
class jsapi: NSObject {
// Reconnect button on interface
func retryConnection()
{
print("Reconnect clicked")
dispatch_async(dispatch_get_main_queue())
{
let netConnections = Connection.isConnectedToNetwork()
if netConnections == true {
let netalert = UIAlertView(title: "Internet on line", message: nil, delegate: nil, cancelButtonTitle: "OK")
netalert.show()
let url = self.serverLocation + self.onlineLoginApi
let hashcode = ViewController().getHashcode()
if(hashcode != "00000") {
let url = url + "?hashcode=\(hashcode)"
print("url: ", url)
}
ViewController().loadPagelive(url)
}
else{
let netalert = UIAlertView(title: "Internet off line", message: nil, delegate: nil, cancelButtonTitle: "OK")
netalert.show()
}
}
print("retryConnect end")
}
}
You try to perform the loadPagelive(url) on a new instance of your ViewController, not on the current one shown on the screen, that's why you don't see any update.
You should create a delegate or a completion block in order to execute code on you ViewController instance loaded on the screen: every time you do ViewController(), a new object is created.
You can try using the delegate pattern, which is simple to achieve. I will try to focus on the important part and create something that can be used with your existing code:
class ViewController: UIViewController {
let jsapi = jsapi() // You can use only 1 instance
override func viewDidLoad() {
super.viewDidLoad()
// Set your ViewController as a delegate, so the jsapi can update it
jsapi.viewController = self
login()
}
func loadPagelive(_ url: URL) {
// Load page, probably you already have it
}
}
class jsapi: NSObject {
weak var viewController: ViewController?
func retryConnection() {
// We check if the delegate is set, otherwise it won't work
guard viewController = viewController else {
print("Error: delegate not available")
}
[... your code ...]
// We call the original (and hopefully unique) instance of ViewController
viewController.loadPagelive(url)
}
}

How to dismiss a UIAlert with no buttons or interaction in Swift?

I am using a UIAlert to display the string "Loading..." while my iOS application is interacting with a database. Is there any way to pragmatically dismiss it when the action is complete?
code:
let myUrl = NSURL(string: "http://www.test.org/ios.html")
let request = NSMutableURLRequest(URL: myUrl!)
request.HTTPMethod = "POST"
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
if error != nil {
print("Error: \(error)")
}
dispatch_async(dispatch_get_main_queue()) {
self.testLabel.text = "\(responseString!)"
// dismiss sendLoading() UIAlert
}
}
}
task.resume()
self.sendLoading()
sendLoading func:
func sendLoading() {
let alertController = UIAlertController(title: "Loading...", message:
"", preferredStyle: UIAlertControllerStyle.Alert)
self.presentViewController(alertController, animated: true, completion: nil)
}
Thank you
Make your alertController as instance variable and when you need to dismiss it just call
self.dismissViewController(alertController, animated:true, completion: nil)
Edit - Adding code.
In your case code would be like -
let alertController : UIAlertController ?
let myUrl = NSURL(string: "http://www.test.org/ios.html")
let request = NSMutableURLRequest(URL: myUrl!)
request.HTTPMethod = "POST"
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
if error != nil {
print("Error: \(error)")
}
dispatch_async(dispatch_get_main_queue()) {
self.testLabel.text = "\(responseString!)"
// dismiss sendLoading() UIAlert
self.dismissViewController(alertController!, animated:true, completion: nil)
}
}
}
task.resume()
self.sendLoading()
sendLoading func:
func sendLoading() {
alertController = UIAlertController(title: "Loading...", message:
"", preferredStyle: UIAlertControllerStyle.Alert)
self.presentViewController(alertController, animated: true, completion: nil)
}
The UIAlertController have the function dismissViewControllerAnimated(flag: Bool, completion: (() -> Void)?) that according to Apple:
Dismisses the view controller that was presented modally by the view controller.
Then what you need to do is to keep a reference to the UIAlertController as a property in your UIViewController and then dismiss it as you like, something like this:
// instance of the UIAlertController to dismiss later
var alertController: UIAlertController!
func sendLoading() {
self.alertController = UIAlertController(title: "Loading...", message:
"", preferredStyle: UIAlertControllerStyle.Alert)
self.presentViewController(alertController, animated: true, completion: nil)
}
let myUrl = NSURL(string: "http://www.test.org/ios.html")
let request = NSMutableURLRequest(URL: myUrl!)
request.HTTPMethod = "POST"
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
if error != nil {
print("Error: \(error)")
}
dispatch_async(dispatch_get_main_queue()) {
self.testLabel.text = "\(responseString!)"
// dismiss sendLoading() UIAlert
self.alertController.dismissViewControllerAnimated(true, completion: nil)
}
}
}
task.resume()
self.sendLoading()
I hope this help you.

UIAlertView in closures callback function in Swift

I need to login through api when user click login button. The return results might failure or success. I want to use alterview to notice the user.
I defined one global variable in class:
var responseString:String = "";
In button click event:
#IBAction func login(sender: AnyObject) {
sendPostLoginRequest {
results in
println("here:\(results)")
self.responseString = results
}
var myAlert = UIAlertController(title:"Alert", message:self.responseString, preferredStyle: UIAlertControllerStyle.Alert);
let okAction = UIAlertAction(title:"Ok", style:UIAlertActionStyle.Default, handler:nil);
myAlert.addAction(okAction);
self.presentViewController(myAlert, animated:true, completion:nil);
}
The self.responseString is empty, even through i can get it in sendPostLoginRequest function:
func sendPostLoginRequest(completionHandler:(result:String)->()){
let name = usernamefield.text
let password = passwordfield.text
let request = NSMutableURLRequest(URL: NSURL(string: "http://www.xxx.xxx/api/user/login")!)
request.HTTPMethod = "POST"
let postString = "username=\(name)&password=\(password)"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
let jsonStr = NSString(data: data, encoding: NSUTF8StringEncoding)
}
let json = JSON(data: data)
println("results: \(json) ")
self.responseString = json[0].string!
//json[0].string! can be success or failure
completionHandler(result:self.responseString)
}
task.resume()
}
The way I see it, I think when you click the button, the alertView is shown without the responseString or nil. The problem is you are anticipating the block will get called later on and hence the data for responseString will be set later in time but the code below will continue to execute.
The long way to do is:
//observe this variable and when something changes we will alert it
var responseString:String {
didSet{
//do some checking or other work
presentAlert(responseString)
}
}
#IBAction func login(sender: AnyObject) {
sendPostLoginRequest {
results in
println("here:\(results)")
self.responseString = results
}
}
func presentAlert(msg:String){
var myAlert = UIAlertController(title:"Alert", message:msg, preferredStyle: UIAlertControllerStyle.Alert);
let okAction = UIAlertAction(title:"Ok", style:UIAlertActionStyle.Default, handler:nil);
myAlert.addAction(okAction);
self.presentViewController(myAlert, animated:true, completion:nil);
}
The short way to do is:
#IBAction func login(sender: AnyObject) {
sendPostLoginRequest {
results in
println("here:\(results)")
self.responseString = results
dispatch_async(dispatch_get_main_queue(), { () -> Void in
//update ui from main thread always
self.presentAlert(results) //may pass responseString too
})
}
}
func presentAlert(msg:String){
var myAlert = UIAlertController(title:"Alert", message:msg, preferredStyle: UIAlertControllerStyle.Alert);
let okAction = UIAlertAction(title:"Ok", style:UIAlertActionStyle.Default, handler:nil);
myAlert.addAction(okAction);
self.presentViewController(myAlert, animated:true, completion:nil);
}
Had you put the code below the closure inside it would have worked but you would be running UI things from other threads which are bad so use Dispatch Async to work for long-running operations on a separate thread.
Let me know if that helps and hope it does. Cheers!