Cannot convert return expression of type 'Void' to return type '(String, String, String)'

Viewed 3614

I'm new to Swift and I am trying to return the values of the variables created in this function. I have been facing this issue for quite some time now and was hoping anyone can shed some light on this and help me out. I have tried changing the return type of the function and that did not work. Any help would be greatly appreciated

Here's the code:

func grabData1() -> (String, String, String)  {
       
        db.collection("News").document("Article 1").getDocument { (document, error) in
            if error == nil {
                if document != nil && document!.exists{
                    self.headline1 = document?.get("Headline").map(String.init(describing:)) ?? nil
                    var article = document?.get("Article").map(String.init(describing:)) ?? nil
                    var image1URL = URL(string: document?.get("Image") as! String)
                    return (headline1, article, image1URL)
                    
                }
            } 
        }
        
    } 
3 Answers

You are not getting any result back because you are trying to return the values that are inside a closure, so your function finish before you have something to return. You need to return your values using a completion handler. It means you will return the values only when you call it.

func grabData1(completionHandler:@escaping(String, String, String)->())  {
   
    db.collection("News").document("Article 1").getDocument { (document, error) in
        if error == nil {
            if document != nil && document!.exists{
                self.headline1 = document?.get("Headline").map(String.init(describing:)) ?? nil
                var article = document?.get("Article").map(String.init(describing:)) ?? nil
                var image1URL = URL(string: document?.get("Image") as! String)
                completionHandler (headline1, article, image1URL)
            }
        } 
    }
    
} 

Then you call your function like this:

grabData1() { headline, article, imageURL in 
    print(headline)
    print(article)
    print(imageURL)
}

Of course you can improve it using a data model but this is the answer for your question.

1- You need to create a model insterad of using a tuple

struct Item {
    let headline,article,imageURL:String
}

2- You need a completion as firebase call is asynchronous

func grabData1(completion:@escaping(Item -> ())) {
   
    db.collection("News").document("Article 1").getDocument { (document, error) in
        if error == nil {
            if document != nil && document!.exists{
                self.headline1 = document?.get("Headline").map(String.init(describing:)) ?? nil
                var article = document?.get("Article").map(String.init(describing:)) ?? nil
                var image1URL = URL(string: document?.get("Image") as! String)
                completion(Item(headline1:self.headline, article:article, imageURL:image1URL))
                
            }
        } 
    }
    
} 

Call

grabData1 { item in
   print(item)
}

You are not actually returning anything in the function grabData1. What you are actually doing is calling the function getDocument which takes a function as an argument (also called a closure in Swift). After you have called that function, your method returns with a return type of void. This is because the document is being retrieved asynchronously. When the document is there, the closure that you passed can be called with the document (or an error that occurred).

So if the signature of that function is

getDocument(onDocumentRetrieved: (DocumentType, ErrorType) -> Void)

then in Swift you may write

getDocument { document, error in ... }

instead of

getDocument(onDocumentRetrieved: { document, error in ... })
Related