I have a picker view that has images as it's scrollable items. I need to pull those images from my database so I'm getting the Unexptected non-void return value in void function error. Here is my code:
func pickerView(_ pickerView: AKPickerView, imageForItem item: Int) -> UIImage {
let imgRef = FIRStorage.storage().reference().child("profile_images").child(pets[item])
imgRef.data(withMaxSize: 1 * 1024 * 1024) { (data, error) -> Void in
// Create a UIImage, add it to the array
let pic = UIImage(data: data!)
return pic
}
}
So I understand why this doesn't work but I'm having a hard time finding out what's the best way to get around this. One solution I can think of is to just set the images to some generic photo until the callback happens, then update the picker view's image to the retrieved image. However, I don't know how to access individual picker view items in order to update its image.
If anybody with some experience can give me advice on how I can achieve my goal of setting these items to the data from an asynchronous call I'd greatly appreciate it!
Your function here is an asynchronous function. You have to make use of callbacks in this case. You can rewrite the function in the following way to achieve desired results.
func pickerView(_ pickerView:AKPickerView, imageForeItem item:Int, completion:(_ resultImg: UIImage)->Void) {
let imgRef = FIRStorage.storage().reference().child("profile_images").child(pets[item])
imgRef.data(withMaxSize: 1 * 1024 * 1024) { (data, error) -> Void in
// Create a UIImage, add it to the array
if let pic:UIImage = UIImage(data: data!) {
completion(pic)
}
}
}
This can be called as follows:
self.pickerView(pickerView, imageForeItem: 0) { (image) in
DispatchQueue.main.async {
// set resulting image to cell here
}
}
Feel free to suggest edits to make this better :)
Related
I'm working my way through the Stanford CS193P Course on iTunesU. Stuck on this one part - There's one section where the instructor has the following comment:
When a drop happens, you'll have to collect both the aspect ratio (from
the UIImage) and the URL before you can add an item. You could do this
straightforwardly with a couple of local variables that are captured by
the closures used to load up the drag and drop data
I assume this to mean that I have to update my model item in the performDrop method of the UICollectionViewDropDelegate protocol. Therefore I will have to call loadObject for both the NSURL item and the UIImage item. However, since the completion handlers for loadObject are off the main thread, I don't have access to the same local variable for both. Therefore if I first load the UIImage, get the aspect ratio, and save it to a local variable in the performDrop method, that variable is still empty when I call loadObject on the URL, which is when I create the model based on the URL and the aspect ratio. I tried nesting the loadObject calls, but the 2nd call was not firing. My code is below:
let placeholderContext = coordinator.drop(item.dragItem, to:
UICollectionViewDropPlaceholder(insertionIndexPath: destinationIndexPath, reuseIdentifier: placeholderCellReuseIdentifier))
var aspectRatio: [CGFloat] = []
item.dragItem.itemProvider.loadObject(ofClass: UIImage.self) { (provider, error) in
if var image = provider as? UIImage {
aspectRatio.append(image.size.width / image.size.height)
}
DispatchQueue.main.async {
item.dragItem.itemProvider.loadObject(ofClass: NSURL.self, completionHandler: { (provider, error) in
print(provider)
if let error = error {
print(error.localizedDescription)
}
print("load urls in completion")
})
}
}
print("main thread: \(aspectRatio)")
item.dragItem.itemProvider.loadObject(ofClass: NSURL.self) { (provider, error) in
print("loaded urls")
DispatchQueue.main.async {
if let url = provider as? URL {
placeholderContext.commitInsertion(dataSourceUpdates: { (insertionIndexPath) in
self.galleryItems.items.insert(GalleryItem(imageURL: url.imageURL, aspectRatio: 1.0), at: insertionIndexPath.item)
})
} else {
placeholderContext.deletePlaceholder()
}
}
}
Am I misguided here or is there something simple I am missing? How can I get the aspect ratio from loading the UIImage first, and then use that in the URL load object completion handler, in the way as is described in the homework?
Given a piece of code like this:
func downloadImage() {
// if image is not downloaded yet, get it
// 1
if (post?.image.value == nil) {
// 2
post?.imageFile!.getDataInBackgroundWithBlock { (data: NSData?, error: NSError?) -> Void in
if let data = data {
let image = UIImage(data: data, scale:1.0)!
// 3
self.post!.image.value = image
}
}
}
}
What is the difference if I turned post from ? to !
Also, how come when I try and do ! I get a:
EXC_BAD_INSTRUCTION, but when I use ? I do not get the error but the screen I am trying to load does not load till a refresh?
Ideas?
dispatch_async(dispatch_get_main_queue()) { \ update UI code }
I have a pretty elaborate problem and I think someone with extensive async knowledge may be able to help me.
I have a collectionView that is populated with "Picture" objects. These objects are created from a custom class and then again, these objects are populated with data fetched from Parse (from PFObject).
First, query Parse
func queryParseForPictures() {
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, err: NSError?) -> Void in
if err == nil {
print("Success!")
for object in objects! {
let picture = Picture(hashtag: "", views: 0, image: UIImage(named: "default")!)
picture.updatePictureWithParse(object)
self.pictures.insert(picture, atIndex: 0)
}
dispatch_async(dispatch_get_main_queue()) { [unowned self] in
self.filtered = self.pictures
self.sortByViews()
self.collectionView.reloadData()
}
}
}
}
Now I also get a PFFile inside the PFObject, but seeing as turning that PFFile into NSData is also an async call (sync would block the whole thing..), I can't figure out how to load it properly. The function "picture.updatePictureWithParse(PFObject)" updates everything else except for the UIImage, because the other values are basic Strings etc. If I would also get the NSData from PFFile within this function, the "collectionView.reloadData()" would fire off before the pictures have been loaded and I will end up with a bunch of pictures without images. Unless I force reload after or whatever. So, I store the PFFile in the object for future use within the updatePictureWithParse. Here's the super simple function from inside the Picture class:
func updateViewsInParse() {
let query = PFQuery(className: Constants.ParsePictureClassName)
query.getObjectInBackgroundWithId(parseObjectID) { (object: PFObject?, err: NSError?) -> Void in
if err == nil {
if let object = object as PFObject? {
object.incrementKey("views")
object.saveInBackground()
}
} else {
print(err?.description)
}
}
}
To get the images in semi-decently I have implemented the loading of the images within the cellForItemAtIndexPath, but this is horrible. It's fine for the first 10 or whatever, but as I scroll down the view it lags a lot as it has to fetch the next cells from Parse. See my implementation below:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(Constants.PictureCellIdentifier, forIndexPath: indexPath) as! PictureCell
cell.picture = filtered[indexPath.item]
// see if image already loaded
if !cell.picture.loaded {
cell.loadImage()
}
cell.hashtagLabel.text = "#\(cell.picture.hashtag)"
cell.viewsLabel.text = "\(cell.picture.views) views"
cell.image.image = cell.picture.image
return cell
}
And the actual fetch is inside the cell:
func loadImage() {
if let imageFile = picture.imageData as PFFile? {
image.alpha = 0
imageFile.getDataInBackgroundWithBlock { [unowned self] (imageData: NSData?, err: NSError?) -> Void in
if err == nil {
self.picture.loaded = true
if let imageData = imageData {
let image = UIImage(data: imageData)
self.picture.image = image
dispatch_async(dispatch_get_main_queue()) {
UIView.animateWithDuration(0.35) {
self.image.image = self.picture.image
self.image.alpha = 1
self.layoutIfNeeded()
}
}
}
}
}
}
}
I hope you get a feel of my problem. Having the image fetch inside the cell dequeue thing is pretty gross. Also, if these few snippets doesn't give the full picture, see this github link for the project:
https://github.com/tedcurrent/Anonimg
Thanks all!
/T
Probably a bit late but when loading PFImageView's from the database in a UICollectionView I found this method to be much more efficient, although I'm not entirely sure why. I hope it helps. Use in your cellForItemAtIndexPath in place of your cell.loadImage() function.
if let value = filtered[indexPath.row]["imageColumn"] as? PFFile {
if value.isDataAvailable {
cell.cellImage.file = value //assign the file to the imageView file property
cell.cellImage.loadInBackground() //loads and does the PFFile to PFImageView conversion for you
}
}
I've subclassed PFUser in my iOS app and I'm using this function to grab the profile picture. profilePicture is the #NSManaged PFFile and profilePictureImage is a UIImage.
This works great except for the fact that getData() and fetchIfNeeded() are potential long running operations on the main thread.
Can anyone think of a good way to implement this method so the scary parts run on a background thread?
Thanks!
func image() -> UIImage!
{
if !(self.profilePictureImage != nil)
{
if self.profilePicture != nil
{
self.fetchIfNeeded()
if let data = self.profilePicture!.getData() {
self.profilePictureImage = UIImage(data: data)
return self.profilePictureImage
}
}else {
return UIImage(named: "no_photo")!
}
}
return self.profilePictureImage
}
Change the method so that rather than returning an image it takes a closure which is called when the image is available and passes it as a parameter. This may be called immediately or after some delay if the image needs to be downloaded.
Just do it as you say, run the task in the background using: fetchIfNeededInBackgroundWithBlock. Also, your image function should look something like this:
func imageInBackgroundWithBlock(block: ((UIImage?, NSError?) -> Void)?) {
var image: UIImage?
self.fetchIfNeededInBackgroundWithBlock({ (user, error) -> Void in
if error == nil {
// load the picture here
image = ...
} else {
println(error!.userInfo)
image = UIImage(named: "no_photo")!
}
// return after fetching the user and the image
block?(image, error)
})
}
I want to call TableViewData Sources method for Seeting up Ui after it has been fethced from parse . With this i am able to fetch
func loadImages() {
var query = PFQuery(className: "TestClass")
query.orderByDescending("objectId")
query.findObjectsInBackgroundWithBlock ({(objects:[AnyObject]!, error: NSError!) in
if(error == nil){
self.getImageData(objects as [PFObject])
}
else{
println("Error in retrieving \(error)")
}
})//findObjectsInBackgroundWithblock - end
}
func getImageData(objects: [PFObject]) {
for object in objects {
let thumbNail = object["image"] as PFFile
println(thumbNail)
thumbNail.getDataInBackgroundWithBlock({
(imageData: NSData!, error: NSError!) -> Void in
if (error == nil) {
var imageDic = NSMutableArray()
self.image1 = UIImage(data:imageData)
//image object implementation
self.imageResources.append(self.image1!)
println(self.image1)
println(self.imageResources.count)
}
}, progressBlock: {(percentDone: CInt )-> Void in
})//getDataInBackgroundWithBlock - end
}//for - end
self.tableView.reloadData()
But not able to populate these fetched data to tableview like this
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
println("in table view")
println(self.imageResources.count)
return imageResources.count+1;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:CustomTableViewCell = tableView.dequeueReusableCellWithIdentifier("customCell") as CustomTableViewCell
var (title, image) = items[indexPath.row]
cell.loadItem(title: title, image: image)
println("message : going upto this line")
println(self.imageResources.count)
var (image1) = imageResources[indexPath.row]
cell.loadItem1(image1: image1)
return cell
}
Then on loaditem i am trying to show up the images and i have writen my own array to populate to the image array but i am geeting a zero value when populating so not able to set it up
Any Help is much appreciated!
You have several problems, all related to concurrency - your load is occurring in the background and in parallel.
The first problem is the use of self.image1 as a temporary variable in the loading process - this variable may be accessed concurrently by multiple threads. You should use a local variable for this purpose.
Second, you are appending to self.imageResources from multiple threads, but Swift arrays are not thread safe.
Third, you need to call reload on your tableview after you have finished loading all of the data, which isn't happening now because you call it while the background operations are still taking place.
Finally, your getImageData function is executing on a background queue, and you must perform UI operations (such as reloading a table) on the main queue.
The simplest option is to change get thumbnail loading to synchronous calls - This means that your thumbnails will load sequentially and may take a bit longer that the multiple parallel tasks but it is easier to manage -
func getImageData(objects: [PFObject]) {
for object in objects {
let thumbNail = object["image"] as PFFile
println(thumbNail)
let imageData? = thumbNail.getData
if (imageData != nil) {
let image1 = UIImage(data:imageData!)
//image object implementation
self.imageResources.append(image1!)
println(self.imageResources.count)
}
}//for - end
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
}
A more sophisticated approach would be to use a dispatch group and keep the parallel image loading. In order to do this you would need to guard the access to the shared array