0
votes

I am calling a method that executes a URLSession but before it does anything, presents a UIAlertController blocking the UI until some sort of response from the request is achieved. Logic tells me that dismissing the UIAlertController in the completion block of that method where it is called on the main thread would be the best option. Am I wrong to assume this? Apparently so, as variably the presented UIAlertController will indeed display, but never dismiss. Help?

Block:

getCostandIV { output in

            let cost = output["ask"] as! NSNumber
            let IV = output["IV"] as! NSNumber

            self.enteredCost = cost.stringValue
            self.enteredIV = IV.stringValue

            DispatchQueue.main.async {

                self.progress.dismiss(animated: true, completion: nil)
                self.tableView.reloadSections(IndexSet(integer: 1), with: UITableView.RowAnimation.none)
                self.canWeSave()

            }

        }

Function:

 func getCostandIV (completionBlock: @escaping (NSMutableDictionary) -> Void) -> Void {

    DispatchQueue.main.async {


        self.progress = UIAlertController(title: "Retrieving ask price and volatility...", message: nil, preferredStyle: UIAlertController.Style.alert)
        self.present(self.progress, animated: true, completion: nil)

    }

    guard let url = URL(string: "https://api.tdameritrade.com/v1/marketdata/chains?apikey=test&symbol=\(symbol)&contractType=\(type)&strike=\(selectedStrike)&fromDate=\(selectedExpiry)&toDate=\(selectedExpiry)") else {
        return
    }

    let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
        guard let dataResponse = data,
            error == nil else {
                //print(error?.localizedDescription ?? "Response Error")

                DispatchQueue.main.async {


                        self.presentedViewController?.dismiss(animated: true, completion: {

                        let alert = UIAlertController(title: "There was an error retrieving ask price and volatility.", message: "Please try again later.", preferredStyle: .alert)
                        alert.addAction(UIAlertAction(title: "OK", style: .default))
                        self.present(alert, animated: true)

                    })

                }

                return }
        do{
            //here dataResponse received from a network request
            let jsonResponse = try JSONSerialization.jsonObject(with:
                dataResponse, options: [])
            //                //print(jsonResponse) //Response result

            guard let jsonDict = jsonResponse as? NSDictionary else {
                return
            }
            //                //print(jsonDict)

            var strikeMap : NSDictionary = [:]

            if self.type == "CALL" {
                strikeMap = jsonDict["callExpDateMap"] as! NSDictionary

            } else {
                strikeMap = jsonDict["putExpDateMap"] as! NSDictionary

            }

            self.strikes.removeAllObjects()

            let inner = strikeMap.object(forKey: strikeMap.allKeys.first ?? "<#default value#>") as! NSDictionary
            let innerAgain = inner.object(forKey: inner.allKeys.first ?? "<#default value#>") as! NSArray
            let dict : NSDictionary = innerAgain[0] as! NSDictionary

            let dict2 = ["ask" : dict["ask"] as! NSNumber, "IV" : dict["volatility"] as! NSNumber] as NSMutableDictionary



            completionBlock(dict2)


        } catch let parsingError {
            print("Error", parsingError)
        }
    }
    task.resume()
}

Edit: Using self.presentedViewController?.dismiss(animated: true, completion: nil) did not fix the issue. In addition, the completion block of the dismiss function for self.progress is not being called.

Edit 2: presentedViewController right before dismiss code in the callback is nil, even though present is called on the alert controller before dismiss?

3

3 Answers

1
votes

use this , for dismiss your alert you should add dismiss method in an async block and for setting timer for that you should tell async block to start being async from now to 5 seconds and after that do some thing :

        alert.addAction(UIAlertAction(title: "ok", style: .default,
                                      handler: nil))
        viewController.present(alert, animated: true, completion: nil)

        // change to desired number of seconds (in this case 5 seconds)
        let when = DispatchTime.now() + 5
        DispatchQueue.main.asyncAfter(deadline: when){
            // your code with delay
            alert.dismiss(animated: true, completion: nil)
        }
0
votes

If you call the getCostandIV method more than once, the second alert won't be presented and self.progress will have the reference of unpresented alert.

Change

self.progress.dismiss(animated: true, completion: nil)

To

self.presentedViewController?.dismiss(animated: true, completion: nil)
0
votes

Your alert would be dismissed only if everything goes well. I suggest you to change your function to something like this:

 func getCostandIV (completionBlock: @escaping (NSMutableDictionary?, Error?) -> Void) -> Void

and make sure that your completionBlock is called when your guard statements fail or an error is thrown. In your current code the alert is only dismissed when it network request fails, but not when something goes wrong when parsing JSON.