Return Image from Function in Swift - swift

I have tried to return an Image or UIImage from the function below but there is an issue that says
"Unexpected non-void return value in void function."
I am retrieving the Image from Firebase Storage and just want to return it...
func retrivePhotos(path: String) -> UIImage {
let storageRef = Storage.storage().reference()
let fileRef = storageRef.child(path)
// Retrieve the data with the path
fileRef.getData(maxSize: 5 * 1024 * 1024) { data, error in
if error == nil && data != nil {
let image = UIImage(data: data!)
return image
}
}
}

The thing to understand is that your call to getData(maxSize:) is a separate asynchronous function. That function does not, and cannot, return the image in its return. It needs to start a network request. It starts that network request and then returns immediately. When the function returns, the network request hasn't even begun sending over the network yet.
The return in the braces following that function call are a return from a closure, which is a block of code that you are passing to the function.
Your function also cannot return an image if it is getting the image using an async function. (unless you rewrite it using async/await, a new language feature that is a little beyond the scope of this question, and likely beyond your current understanding.)
You need to rewrite your function to take a completion handler, just like the getData(maxSize:) function takes a completion handler:
typealias ImageHandler = (UIImage?) -> Void
func retrivePhotos(path: String, completion: ImageHandler) {
let storageRef = Storage.storage().reference()
let fileRef = storageRef.child(path)
// Retrieve the data with the path
fileRef.getData(maxSize: 5 * 1024 * 1024) { data, error in
if error == nil && data != nil {
let image = UIImage(data: data!)
completion(image)
} else {
completion(nil)
}
}
}
(That is over-simplified. You'd probably write it to take a Result instead of an Optional UIImage.)
Think of async functions like this. The main program is you, cooking dinner for your family. You realize you are out of milk, so you send your kid to the store to buy some. You don't hand your kid some money and expect them to hand you back the milk right then and there. You hand them the money, tell them to get you milk, and then go back to the rest of the cooking. At some time in the future, your kid comes back and either gives you the milk (The success case) or tells you there was a problem that prevented them from getting the milk (the error case.)
At the point your kid come back from the store, you run the "kid is back from the store" completion handler. You either go into sauce-making mode with the milk, or an error handler where you decide what to do since you don't have milk.

Related

Firebase Storage getData cannot be awaited [duplicate]

This question already has an answer here:
How to use downloaded URL correctly in AsyncImage?
(1 answer)
Closed 2 months ago.
My function ends before all data can be retrieved from the fire storage and returns empty.
How can I get the data before returning?
Code:
func listItem() -> [imageitem]{
let storage = Storage.storage()
let storageRef = storage.reference().child("images/image")
var array = [imageitem]()
storageRef.listAll { (result, error) in
if let error = error {
print("error")
print(error)
}
print("storagereference")
result?.items.forEach({ StorageReference in
print(StorageReference)
print(StorageReference.name)
StorageReference.getData(maxSize: 5*1024*1024) {data, error in
if error == nil && data != nil{
if let image = UIImage(data: data!){
let element = imageitem(title: StorageReference.name, image: Image(uiImage: image))
array.append(element)
print(array)
}
}
}
})
print("end",array)
}
return array
}
Console:
storagereference
gs://proj-69776.appspot.com/images/image/gorilla.jpg
gorilla.jpg
gs://proj-69776.appspot.com/images/image/test.jpg
test.jpg
end []
[proj.ListView.imageitem(title: "test.jpg", image: SwiftUI.Image(provider: SwiftUI.ImageProviderBox<__C.UIImage>))]
[proj.ListView.imageitem(title: "test.jpg", image: SwiftUI.Image(provider: SwiftUI.ImageProviderBox<__C.UIImage>)), proj.ListView.imageitem(title: "gorilla.jpg", image: SwiftUI.Image(provider: SwiftUI.ImageProviderBox<__C.UIImage>))]
adding an await before the addData gives me an error
Cannot pass function of type '(StorageReference) async -> Void' to parameter expecting synchronous function type
You cannot return data that is loaded asynchronously with a return statement like that, because the return runs well before you ever append any element to the array. If you set breakpoints and run the code in a debugger you can most easily validate that.
Instead, you'll want to pass in a closure/callback or use a dispatch queue to get the value out of your function into the calling context.
For examples of how to do this, see:
Return image from asynchronous call
Unable to assign value within a function in Firebase API callback block
When I try to access my array outside of this function it appears to be empty.I t is something to do with an asynchronous call, any suggestion?
Accessing Array Outside Closure in Swift 3
How to make synchronous operation with asynchronous callback?
How to upload the profile image URL to firebase database

Code improvement - how to serve use pictures in Vapor?

I have a working directory that contains every user's picture and I am trying to implement a call that returns data containing the user's picture, defined in this structure:
struct ImageData: Content {
var picture: Data // UIImage data
}
I tried to implement a solution also partially using what I found in the book 'Server Side Swift with Vapor' (version 3) in chapter 26 but that's different for me because I am not using Leaf and I need to return the data directly.
I came up with this function to return the user picture, which does its job but I am trying to improve it.
func getProfilePictureHandler(_ req: Request) throws -> EventLoopFuture<ImageData> {
return User.find(req.parameters.get("userID"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { user in
// To do: throw error (flatMapThrowing?)
let filename = user.profilePicture!
let path = req.application.directory.workingDirectory
+ imageFolder
+ filename
// Improvement: Do I need this?
var data = Data()
return req.fileio.readFile(at: path) { buffer -> EventLoopFuture<Void> in
let additionalData = Data(buffer: buffer)
data.append(contentsOf: additionalData)
return req.eventLoop.makeSucceededVoidFuture()
}.map {
return ImageData(picture: data)
}
}
}
First:
How to implement this using flatMapThrowing? If I replace flatMap with flatMapThrowing I get this error: "Cannot convert return expression of type 'EventLoopFuture' to return type 'ImageData'". Which doesn't make sense to me considering that flatMap allows returning a future and not a value.
I didn't find any solution other than using a Data variable and appending chunks of data as more data is read. I am not sure that this is thread-safe, FIFO and I don't consider it an elegant solution. Does anybody know any better way of doing it?
The short answer is that as soon as you have the file path, Vapor can handle it all for you:
func getProfilePictureHandler(_ req: Request) throws -> EventLoopFuture<Response> {
return User.find(req.parameters.get("userID"), on: req.db)
.unwrap(or: Abort(.notFound))
.tryflatMap { user in
// To do: throw error (flatMapThrowing?)
guard let filename = user.profilePicture else {
throw Abort(.notFound)
}
let path = req.application.directory.workingDirectory
+ imageFolder
+ filename
return req.fileio.streamFile(at: path)
}
}
You can use tryFlatMap to have a flatMap that can throw and you want to return a Response. Manually messing around with Data is not usually a good idea.
However, the better answers are use async/await and the FileMiddleware as two tools to clean up your code and remove the handler altogether

What happens to a pointer referenced by a closure that is effectively let go?

The following is pseudo-code to help demonstrate my question:
var dataSourceArray = [CustomClassObject]()
databaseListener { (data) in
dataSourceArray.removeAll()
let id = data.id
let imageURL = data.imageURL
let listing = CustomClassObject(id: id, image: nil)
remoteFileStorage.getImage(url: imageURL) { (image) in
listing.image = image
}
dataSourceArray.append(listing)
tableView.reloadData()
}
databaseListener is a realtime database listener that can return updates rapidly. In its completion handler is an async call that downloads an image. If databaseListener returns new data before remoteFileStorage has a chance to return from the last run, what happens to the listing pointer in the remoteFileStorage closure? The listing pointer no longer has a home in dataSourceArray (it was just purged) so can the remoteFileStorage handler safely access it anyway? My assumption is that the listing pointer is still pointing to a valid address in memory since the remoteFileStorage closure has a reference to it, but I am not certain?

Swift, URLSession, viewDidLoad, tableViewController

I've never really gotten the nuances of async operations so time and again, I get stymied. And I just can't figure it out.
I'm trying to do some very simple web scraping.
My local volleyball association has a page (verbose HTML, not responsive, not mobile-friendly, yaddah, yaddah, yaddah) which shows the refs assigned to each game of the season. I'm trying to write a silly little app which will scrape that page (no API, no direct access to db, etc.) and display the data in a grouped table. The first group will show today's matches (time, home team, away team). The second group will show tomorrow's matches. Third group shows the entire season's matches.
Using code I found elsewhere, my viewDidLoad loads the page, scrapes the data and parses it into an array. Once I've parsed the data, I have three arrays: today, tomorrow, and matches, all are [Match].
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: urlString)!
let request = NSMutableURLRequest(url: url)
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
if let error = error {
print (error)
} else {
if let unwrappedData = data {
// scrape, scrape, parse, parse
matchRow = ...
self.matches.append(matchRow)
if matchRow.date == todaysDate {
self.today.append(matchRow)
} else if matchRow.date == tomorrowsDate {
self.tomorrow.append(matchRow)
}
}
}
}
task.resume()
}
As I'm sure is no surprise to anyone who understands async operations, my table is empty. I've checked and I see the the data is there and properly parsed, etc. But I can't for the life of me figure out how get the data in my table. The way I have it now, the data is not ready when numberOfSections or numberOfRowsInSection is called.
I've found the Ray Wenderlich tutorial on URLSession and I also have a Udemy course (Rob Percival) that builds an app to get the weather using web scraping, but in both those instances, the app starts and waits for user input before going out to the web to get the data. I want my app to get the data immediately upon launch, without user interaction. But I just can't figure out what changes I need to make so that those examples work with my program.
Help, please.
You can simply reload the tableviews once the data arrays are getting populated from the URLSession completion block. Have you tried that. Sample snippet may be like the one follows.
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
if let error = error {
print (error)
} else {
if let unwrappedData = data {
// scrape, scrape, parse, parse
matchRow = ...
self.matches.append(matchRow)
if matchRow.date == todaysDate {
self.today.append(matchRow)
} else if matchRow.date == tomorrowsDate {
self.tomorrow.append(matchRow)
}
}
DispatchQueue.main.async { [weak self] in
self?.todayTableView.reloadData()
self?.tomorrowTableView.reloadData()
}
}
}

Swift 3 Completion Handler on Google Places Lookup. Due to delay how do I know when Im "done"?

Sorry, newbie here and Ive read extensively about completion handlers, dispatch queues and groups but I just can't get my head around this.
My app loads an array of Google Place IDs and then wants to query Google to get full details on each place. The problem is, due to async processing the Google Lookup Place returns immediately and the callback happens much further down the line so whats the "proper way" to know when the last bit of data has come in for my inquiries because the function ends almost immedately ?
Code is attached. Thanks in advance.
func testFunc() {
let googlePlaceIDs = ["ChIJ5fTXDP8MK4cRjIKzek6L6NM", "ChIJ9Wd6mGYGK4cRiWd0_bkohHg", "ChIJaeXT08ASK4cRkCGpGgzYpu8", "ChIJkRkS4BapK4cRXCT8-SJxNDI", "ChIJ3wDV_2zX5IkRtd0hg2i1LhE", "ChIJb4wUsI5w44kRnERe7ywQaJA"]
let placesClient = GMSPlacesClient()
for placeID in googlePlaceIDs {
placesClient.lookUpPlaceID(placeID, callback: { (place, error) in
if let error = error {
print("lookup place id query error: \(error.localizedDescription)")
return
}
guard let place = place else {
print("No place details for \(placeID)")
return
}
print("Place Name = \(place.name)")
})
}
print("Done")
}