I'm successfully pulling in data from Parse into swift, but my images don't seem to be working the way I'm doing it.
In my cellForRowAtIndexPath method, I do the following:
var event: AnyObject? = eventContainerArray[indexPath.row]
if let unwrappedEvent: AnyObject = event {
let eventTitle = unwrappedEvent["title"] as? String
let eventDate = unwrappedEvent["date"] as? String
let eventDescription = unwrappedEvent["description"] as String
let eventImage = unwrappedEvent["image"] as? UIImage
println(eventImage)
if (eventImage != nil){
cell.loadItem(date: eventDate!, title: eventTitle!, description: eventDescription, image: eventImage!)}
else {
let testImage: UIImage = UIImage(named: "test-image.png")!
cell.loadItem(date: eventDate!, title: eventTitle!, description: eventDescription, image: testImage )
}
}
}
return cell
}
I'm using println() with my PFQuery, and I am seeing this as part of the object that's loading in: image = "<PFFile: 0x7fee62420b00>";
So I'm getting title, date, description, etc. all loading fine as part of the above eventContainerArray, but when I look at eventImage, it's nil every time. In the code above, it always defaults to loading test-image.png, being that the image is coming up nil. Am I simply handling that PFFile the improper way? Not sure why it's not working.
Thanks!
Your image is likely a PFFile object. You will need to load it to get an UIImage object from it.
let userImageFile = unwrappedEvent["image"] as PFFile
userImageFile.getDataInBackgroundWithBlock {
(imageData: NSData!, error: NSError!) -> Void in
if error == nil {
let eventImage = UIImage(data:imageData)
// do something with image here
}
}
I'm not familiar with Parse, but given what you're seeing from the println() it looks like what you've received isn't a UIImage but instead some sort of data type. This makes sense; you aren't going to be receiving ObjC objects over the network. So you are getting a file, but when you use the conditional downcast (x as? UIImage) it's returning nil because you don't have a UIImage.
Instead you're going to need to cast to PFFile, and then probably create a UIImage with UIImage(data: file.getData) or something similar. I'm not familiar with exactly how Parse works.
Edit: here's a related question that might be helpful: I can't get image from PFFile
Related
I try to do a query with Parse containing a few Strings and Images that will be added to an array. The strings in the array are all in the right order but not the images. I think its probably because some images are smaller than the other ones and so they get appended to the array earlier than they are supposed to. Is there any way to "save" space in the array for the images to keep them in the right order? It's probably not that hard to solve that but I am a Newbie :( Thank you!
query.findObjectsInBackground (block: { (objects:[PFObject]?, error: Error?) -> Void in
for object in objects! {
DispatchQueue.global(qos: .userInteractive).async {
// Async background process
if let imageFile : PFFile = self.bild.append(object.value(forKey: "Bild") as! PFFile) {
imageFile.getDataInBackground(block: { (data, error) in
if error == nil {
DispatchQueue.main.async {
// Async main thread
let image = UIImage(data: data!)
image2.append(image!)
}
} else {
print(error!.localizedDescription)
}
})
}
}
}
})
Your analysis is correct that the requests will complete in non-deterministic order, partly or mostly influenced by the amount of data that must be returned.
Instead of an array to which you append the UIImage (or data), use a mutable dictionary that maps strings to UIImage. A reasonable choice for the string key is the PFFile name.
EDIT I'm not a Swift writer, but I tried to express the idea below (don't depend on it compiling, but I think the idea is sound)
class MyClass {
var objects: [PFObject] = []
var images: [String: UIImage] = [:] // we'll map names to images
fetchObjects() {
// form the query
query.findObjectsInBackground (block: { (objects:[PFObject]?, error: Error?) -> Void in
self.objects = objects
self.fetchImages()
})
}
fetchImages() {
for object in self.objects! {
if let imageFile : PFFile = object["Bild"] as PFFile {
self.fetchImage(imageFile);
}
}
}
fetchImage(imageFile: PFFile) {
imageFile.getDataInBackground(block: { (data, error) in
if error == nil {
self.images[imageFile.name] = UIImage(data: data!)
// we can do more here: update the UI that with image that has arrived
// determine if we're done by comparing the count of images to the count of objects
} else {
// handle error
}
}
}
}
This will get the images in the background and keep them associated with their filenames using a dictionary. The OP code didn't explain what self.bild is, but I assumed it was an instance array of retrieved PFFiles. I replaced this with the images instance var.
Image file order is maintained by the object collection: to get the Nth image, get the Nth object, get it's "Bild" property, that PFFile's name is the key into your images dictionary.
var n = // some index into objects
var object : PFObject = self.objects[n]
var file : PFFile = object["Bild"]
var name : String = file.name
var nthImage = self.images[name] // is nil before fetch is complete
I'm having issues with my project. I'm new to coding. I keep getting the error fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb). It seems that the variable "image" is not producing an image and instead producing "nil". the variable "photo" is producing a name that corresponds with my JPG image name. For some the variable "image" is not able to produce that UIImage and rather is producing nil. Hopefully you can help me.
import UIKit
class Photo {
class func allPhotos() -> [Photo] {
var photos = [Photo]()
if let URL = NSBundle.mainBundle().URLForResource("Photos", withExtension: "plist") {
if let photosFromPlist = NSArray(contentsOfURL: URL) {
for dictionary in photosFromPlist {
let photo = Photo(dictionary: dictionary as! NSDictionary)
photos.append(photo)
}
}
}
return photos
}
var caption: String
var comment: String
var image: UIImage
init(caption: String, comment: String, image: UIImage) {
self.caption = caption
self.comment = comment
self.image = image
}
convenience init(dictionary: NSDictionary) {
let caption = dictionary["Caption"] as? String
let comment = dictionary["Comment"] as? String
let photo = dictionary["Photo"] as? String
let image = UIImage(named: photo!)?.decompressedImage
self.init(caption: caption!, comment: comment!, image: image!)
}
func heightForComment(font: UIFont, width: CGFloat) -> CGFloat {
let rect = NSString(string: comment).boundingRectWithSize(CGSize(width: width, height: CGFloat(MAXFLOAT)), options: .UsesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
return ceil(rect.height)
}
}
I have a feeling that it has to do with the decompression of the image:
import UIKit
extension UIImage {
var decompressedImage: UIImage {
UIGraphicsBeginImageContextWithOptions(size, true, 0)
drawAtPoint(CGPointZero)
let decompressedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return decompressedImage
}
}
Most likely it's these two lines:
let image = UIImage(named: photo!)?.decompressedImage
self.init(caption: caption!, comment: comment!, image: image!)
The first line assigns a value to image but specifically allows for a nil value. That's what the ? implies. Also, in decompressedImage (the purpose of which is... unclear) the call to UIGraphicsGetImageFromCurrentImageContext is allowed to return nil.
Then in the second line you use image! without checking whether it's nil. It is nil, according to your crash, which implies that one of the following is true:
photo doesn't actually contain the name of an image in your app bundle, or
photo has a valid name but the file is somehow corrupt to the point where UIImage can't handle it, or
decompressedImage is returning nil, because UIGraphicsGetImageFromCurrentImageContext for some reason.
There are many things going on here that might go wrong at runtime. So I'll focus on one, and advice you to be careful with force unwrapping (optionalProperty!) unless you know your optionals and in which situations it's ok to use force unwrapping. If you don't yet know the latter, you should avoid !.
Let's look at this segment of code
let image = UIImage(named: photo!)?.decompressedImage
As you mention, photo is an optional string that might be nil. But if we look at the class reference or UIImage, specifically the initialiser UIImage(named: ...), it specifies
init?(named name: String)
I.e., the parameter named should be a string. nil is not a string and hence your error.
If we proceed to assume that photo actually forcefully unwrapped to a string (path), we have our next problem; "Value of type UIImage has no member 'decompressedImage'". Looking back at the class reference for UIImage, we can't find this method here.
Anyway, I suggest read up on optionals and optional chaining in swift (if let, guard let, error handling), e.g.
Optional chaining.
In your specific case, you need to handle, at the very least, the case where expression dictionary["Photo"] as? String returns nil.
I'm working on app to do paged photos inside scrollViews like in the photos app to swipe right to get the old photos and swipe left to get the new photos until the end of photos.
Alright,so i am getting the photos as a multi-diminutional from the web :
imageArray[indexPath.row][0] as? String
Thats in the ViewController1 to show all the images in CollectionView .
When the user press on a photo i do segue and show the image larger in ViewController2 so if swipe left it show the new photos and right to show the old photos which is stored in the array.
but i need to covert my two-dimensional array to one and use it dynamically to be something like this :
pageImages = [UIImage(named:"photo1.png")!,
UIImage(named:"photo2.png")!,
UIImage(named:"photo3.png")!,
UIImage(named:"photo4.png")!,
UIImage(named:"photo5.png")!]
how is it possible to do ?
i could say like :
pageImages = [UIimage(named:thewholearray)] ?
i tried first to convert to one-diminutional array but i failed :
var imageArray : NSArray = []
var mybigarray : NSArray = []
for (var i=0; i<=self.imageArray.count; i++) {
self.mybigarray = self.imageArray[i][0] as! NSArray
}
which generate this casting error :
Could not cast value of type '__NSCFString' (0x196806958) to 'NSArray' (0x196807308).
You can use the map-function to extract the image names from your multi-dimensional array.
var imageNames:[String] = imageArray.map({
$0[0] as! String
})
The map function iterates through all array entries like a for-in-loop.
The statement in the closure determines the new entry of your imageNames array.
EDIT:
If you don't use Swift-Arrays:
var imageNames:NSMutableArray = []
for image in imageArray{
imageNames.addObject(image[0] as! String)
}
I looked at the tutorial and I think this will help with the answer from Daniel. Keep Daniel's Answer and use the URLs from the array with the extension.
Add this to create you images from a URL
extension UIImageView {
public func imageFromUrl(urlString: String) {
if let url = NSURL(string: urlString) {
let request = NSURLRequest(URL: url)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {
(response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
if data != nil {
self.image = UIImage(data: data)
}
else {
self.image = UIImage()
}
}
}
}
}
Use this in the PagedImageScrollViewController:
newPageView.imageFromUrl(pageImages[page])
pageImages is your array of Strings.
I am trying to set the image property on a UIImageView. When I use a UIImage to set the .image property it throws this error every time:
"unexpectedly found nil while unwrapping an Optional value"
The problem is that my UIImage is not nil.
Here is the code where I am setting the UIImage
func setPhotosForNewsItem(photoArray:[Photo]) {
println("Image Count: " + String(photoArray.count))
var image:UIImage = photoArray[0].photo
println(image.description)
self.newsImage.image = image
}
Here is the console output:
Image Count: 2
UIImage: 0x7fdd93c5cdd0, {1115, 1115}
fatal error:
unexpectedly found nil while unwrapping an Optional value (lldb)
I am able to use the Quick Look tool in xCode on my supposedly nil UIImage and see the photo that I am trying to use. Why would I be throwing a nil error when my UIImage is clearly not nil?
UPDATE::
It seems that I am not properly storing the UIImage in my array. Here is where I download my images and store them to my array for unpacking later.
var relatedPhotos:[PFObject] = relations as! [PFObject]
//println(relations!)
var photoArray:[Photo] = [Photo]()
for photo in relatedPhotos {
var newPhoto = Photo()
var photoFile:PFFile = photo.objectForKey("photo") as! PFFile
newPhoto.object = photo
newPhoto.objectID = photo.objectId!
photoFile.getDataInBackgroundWithBlock({(imageData: NSData?, error: NSError?) -> Void in
if (error == nil) {
newPhoto.photo = UIImage(data:imageData!)!
//println(newPhoto.photo)
photoArray.append(newPhoto)
if photoArray.count == relatedPhotos.count {
if newObject is FieldReport {
var report = newObject as! FieldReport
report.photos = photoArray
updatedReports.append(report)
//println("Report Count 1: " + String(updatedReports.count))
}
else {
var report = newObject as! Feature
report.photos = photoArray
updatedReports.append(report)
}
if updatedReports.count == objects.count {
self.delegate?.fieldReports(updatedReports)
}
}
}
})
}
I know that this works to download and display the photo as I have just used it successfully to do so. To me that means I am not storing the UIImage properly. Is there a different way I should be storing these image files?
You can prevent the crash from happening by safely unwrapping
func setPhotosForNewsItem(photoArray:[Photo]) {
println("Image Count: " + String(photoArray.count))
if var image:UIImage = photoArray[0].photo
{
println(image.description)
self.newsImage.image = image
}
}
Setup breakpoint and check what's wrong
EDIT:
The only thing i can purpose - check what happens here:
newPhoto.photo = UIImage(data:imageData!)!
seems like it's the main reason of all problems. check type of imageData, try to convert it to image via, for example UIImage(CGImage: <#CGImage!#>). You need to figure out how to deal with this image.
I have an array that starts out empty but is filled with PFFiles (image data) with a PFQuery. A UIImageView has its image set using the data in the PFFile Array. However, if the array is empty then there is an error saying array index is out of range. Therefore, I need something testing to see if array is empty and I can't find a way to do that.
var imageFiles = [PFFile]()
And then in the viewDidLoad
self.imageFiles[self.imageCounter].getDataInBackgroundWithBlock{
(imageData, error) -> Void in
if error == nil {
let image = UIImage(data: imageData!)
self.mainPic.image = image
}else {
}
}
I would like to be able do something like:
If let testVariable = self.imageFiles[self.imageCounter] as PFFile {
}
Or more simply:
If self.imagesFiles[self.imageCounter] == nil {
}
But neither of those work
The problem is you're going out of the bounds of the array, as you said. You can void this by checking the size of the array before trying to access an element. The following should do the trick:
if (self.imagesFiles.count > self.imageCounter) {
//myImage = self.imageFiles[self.imageCounter];
}