Search code examples
swiftapipostrequestviewcontroller

How to pass String data from API POST request to another ViewController


I am absolutely beginner in Swift and programming at all.

I don't know how to pass a response from my function that calls POST request to the text label on the second view controller. I think that it may be something wrong with the main thread but I don't know how to solve it. I really try to search for the solution but nothing works for me.

Here is my function:

 func getBookTitle() -> String {
        
        var bookTitle = ""
            
        
        if let bookNameURL = URL(string: urlString) {
            
            
            do {
                let htmlString = try String(contentsOf: bookNameURL, encoding: .utf8)
                let htmlContent = htmlString
                
                do {
                    let doc = try SwiftSoup.parse(htmlContent)
                    
                    do {
                        let bookNumber = try doc.select("a.dlink").attr("onclick")
                        let bookNumberTrim = bookNumber.trimmingCharacters(in: CharacterSet.decimalDigits.inverted).components(separatedBy: ",")
                        
                        do {
                            
                            let bookid = bookNumberTrim[0]
                            let passageid = bookNumberTrim[1]
                            
                            
                            print(bookid)
                            print(passageid)
                            
                            
                            
                            //Get Book Title
                            
                            let url:URL = URL(string: "\(urlString)/getname")!
                            let session = URLSession.shared
                            
                            let request = NSMutableURLRequest(url: url)
                            request.httpMethod = "POST"
                            request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
                            
                            let paramString = "bookid=\(bookid)&passageid=\(passageid)"
                            
                            request.httpBody = paramString.data(using: String.Encoding.utf8)
                            
                            

                            let task = session.dataTask(with: request as URLRequest) { (data, response, error) in
                                
                                
                                guard let data = data, let _:URLResponse = response, error == nil else {
                                    print("error")
                                    return
                                }
                                
                                let dataString = String(data: data, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue))
                                
                                
                                do {
                                    
                                    let doc: Document = try SwiftSoup.parse(dataString ?? "")
                                    
                                    do {
                                        
                                        let bTitle = try doc.select("div").first()
                                        bookTitle = try bTitle!.text()
                                        
                                        print(bookTitle)
                                        
                                    }
                                } catch {
                                    print(error)
                                }
                                
                            }
                            task.resume()
                            
                            
                        }
                        
                    } catch {
                        print(error)
                        
                    }
                }
                
            } catch let error {
                print("Error \(error)")
            }
            
            
        } else {
            print("Something wrong")
        }
        
        return bookTitle
        
    }

This is how I am trying to pass data to another VC:

@IBAction func infoButtonPressed(_ sender: UIButton) {
    
    performSegue(withIdentifier: "goToInfo", sender: sender)
}



override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    
    if segue.identifier == "goToInfo" {
        let destinationVC = segue.destination as! BookInfoVC
        
        destinationVC.bookName = networkService.getBookTitle()

    }
    
}

And second VC:

class BookInfoVC: UIViewController {
    
    
    @IBOutlet weak var bookTitle: UILabel!
    
    var bookName: String!
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        bookTitle.text = bookName
        
    }
    
}

The response that comes from the function is one String line.

Many thanks!


UPD: I've tried to implement completion handler in my function and now it looks like this:

func getBookTitle(completion: @escaping (String) -> ()) {
        
        if let bookNameURL = URL(string: urlString) {
            
            do {
                let htmlString = try String(contentsOf: bookNameURL, encoding: .utf8)
                let htmlContent = htmlString
                
                do {
                    let doc = try SwiftSoup.parse(htmlContent)
                    
                    
                    do {
                        let bookNumber = try doc.select("a.dlink").attr("onclick")
                        let bookNumberTrim = bookNumber.trimmingCharacters(in: CharacterSet.decimalDigits.inverted).components(separatedBy: ",")
                        
                        
                        do {
                            let bookid = bookNumberTrim[0]
                            let passageid = bookNumberTrim[1]
                        
                            
                            
                            //MARK: - Fetch book title
                            
                            let params = BookTitle(bookid: bookid, passageid: passageid)
                            
                            
                            AF.request("\(self.urlString)/getname", method: .post, parameters: params).validate(contentType: ["application/x-www-form-urlencoded"]).response { (response) in
                                
                                
                                
                                if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
                                    
                                    
                                    do {
                                        let html: String = utf8Text
                                        let doc: Document = try SwiftSoup.parse(html)

                                        do {

                                            let parseTitle = try doc.select("div").first()
                                            let bookTitle = try parseTitle!.text()
                                            completion(bookTitle)

                                        } catch {
                                            print(error)
                                        }
                                        
                                        
                                    } catch {
                                        print(error)
                                    }
  
                                }
                            
                            }
                        }
                        
                    } catch {
                        print(error)
                    }
                    
                }
                
            } catch let error {
                print("Error \(error)")
            }
                        
            
        } else {
            print("Something wrong")
        }
        
    }

And I call it this:

@IBAction func infoButtonPressed(_ sender: UIButton) {
    
    self.performSegue(withIdentifier: "goToInfo", sender: self)
}


override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    

    if segue.identifier == "goToInfo" {
        let destinationVC = segue.destination as! BookInfoVC
        
        
        networkService.getBookTitle { bookTitle in
            destinationVC.bookName = bookTitle
            print(bookTitle)

        }

    }

I've tried to paste DispatchQueue.main.async everywhere, but it still doesn't update the text label on the second VC. But in the main view controller, it's working fine.


Solution

  • Short answer: You can't. You are making an async request. The function will return before the results are available. You need to rewrite your function to take a completion handler. You would then invoke the completion handler when the result is available. This is widely discussed on this board.

    Edit:

    Your updated getBookTitle(completion:) function looks good, but now you have a sequencing problem.

    Your prepare(for segue:) method in your first view controller uses your networkService.getBookTitle(completion:) function, and in the completion handler it sets destinationVC.bookName.

    However, remember that networkService.getBookTitle(completion:) runs asynchronously. It returns immediately, waits for the network result, and then invokes the completion handler.

    Your destination view controller, (of type BookInfoVC) tries to install bookName into its UI in its viewDidLoad method. viewDidLoad() gets called immediately after the first view controller's prepare(for segue:) fires. At the time viewDidLoad is called, bookName is very likely still nil.

    You need to add a didSet method on your BookInfoVC's bookName property so it responds to a change to the book name. That might look like this:

    var bookName: String! {
       didSet: {
         bookTitle.text = bookName
       }
    }
    

    If you do that, when getBookTitle()'s completion handler fires, it will change destinationVC.bookName. That will cause the BookInfoVC's bookName didSet code to fire, installing the updated bookName into the UI.