I have an optional image in the cloudkit DB(checked DB and the image is there in cases where I added it in my testing). I have created a class that initializes the record fields into variables I use in my tableview. I have a custom cell as well. But the image won't display in my custom tableview cell. I don't know if having an optional image in a tableview image is causing a problem or if there's an issue with my code/settings in cloudkit.
Any help is greatly appreciated, as I've been stuck for over a week and there's little to nothing online about this.
Here's my class code:
var feedResults = [UserPosts]()
class UserPosts {
var record: CKRecord
var description: String
var username: String
//var image: CKAsset? // tried also initializing the CKAsset separately
var postPic: UIImage?
init(record: CKRecord) {
self.record = record
self.description = record.objectForKey("description") as String
self.username = record.objectForKey("username") as String
// self.image = record.objectForKey("postPic") as? CKAsset
if var pic = record.objectForKey("postPic") as CKAsset! {
self.postPic = UIImage(contentsOfFile: pic.fileURL.path!)
// below is the other way I've seen it done.
// var url = pic.fileURL!
// var imageData = NSData(contentsOfFile: url.path!)
// self.postPic = UIImage(data: imageData!)
}
}
}
Here's my tableview code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as TableViewCell
let record = feedResults[indexPath.row]
cell.postDescription.text = record.description
cell.userName.text = record.username
if record.postPic != nil {
cell.postPic.image = record.postPic!
}
return cell
}
Also I've seen a couple ways of pulling a CKAsset into a UIImage. The first way, is how I saw Apple do it in their CloudKitAtlas project. Although I'm not well versed in Obj-C, I think I followed it correctly - CloudKitAtlas Example
I've also seen it done using NSData(contentOFFile:) and UIImage(data:), as shown in this post here: SO CKAsset Example
Try doing it without the .paths and using contentsOfURL. This is how I do this (within your if for the CKAsset):
if var data = NSData(contentsOfURL: pic!.fileURL) {
self.postPic = UIImage(data: data!)!
}
And besides that... You do a dequeueReusableCellWithIdentifier but don't check if it returns nil. You should create a new cell instance If it was nil
You can display the image from the CKAsset like this:
var img = record.valueForKey("Picture") as? CKAsset
cell.imageView?.image = UIImage(contentsOfFile: img!.fileURL.path!)
Hope this helps!
Related
I am able to get it working if the ImageView is in the same View Controller. But I created a custom cell .xib and a model class. All data (text) seems to be transferring, even the URL of the Firebase database image, but the image doesn't change, the default placeholder image is showing. If I don't set the default placeholder image, I will get an error saying that it is nil.. Again, all cells are populating text data from Firebase, the only thing that isn't is the image from the given URL. Debugging shows that the URL does get passed into the "profileImageURL" variable. Here is some of the code:
class Profile{
var name: String = ""
var age: String = ""
var profileImg : UIImageView! = UIImageView.init(image: "main"))
var description : String = ""
}
Here is the table view controller code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCellTableViewCell", for: indexPath) as! CustomCellTableViewCell
let profile = profileArray[indexPath.row]
// Configure the cell...
profileTableView.rowHeight = 300
cell.nameLbl.text = profile.name
cell.descriptionLbl.text = profile.description
cell.profileImage.image = UIImage(named: "main")
return cell
}
func retrievePosts(){
let dataRef = Database.database().reference()
let postsDB = dataRef.child("Messages")
postsDB.observe(.childAdded) { (snapshot) in
let value = snapshot.value as! Dictionary<String,String>
let text = value["postText"]!
let profileImgURL = value["postImage"]!
let userID = value["sender"]!
let url = NSURL(string:"\(profileImgURL)")
let profile = Profile()
profile.description = text
profile.name = name as! String
profile.profileImg.sd_setImage(with: URL(string:profileImgURL), placeholderImage: nil)
self.profileTableView.reloadData()
self.profileArray.insert(profile, at: 0)
})
}
here is the firebase data structure:
- Messages
-L4IkuSxWDnsiJvTKoo0
-postImage: "https://firebasestorage.googleapis.co......"
-postText: "Lorem ipsum dolor sit er elit lamet, consecteta..."
-sender: "C19ghii6OVPNnzJNYPvVJeKuoi73"
bro, the image for any cell is meant to be set in cellForRowAt method. You are getting the raw data because you are fetching it from profile instead of hardcoding, whereas for the image you are setting the image as "main" each time. Hope this helps. – Ashish Sharma
Yup! Thank you! New to App dev. That was it. Had to create a new variable in Profile to hold the url and then in cellForRowAt set the Imageview with that variable URL. There is most likely an easier way to do it but worked for me! Thank you so much! – Guillermo Greco
It's not really the solution, but an alternative:
Use kingfisher pod to set images from URLs.
let url = URL(string: "https://example.com/image.jpg")!
imageView.kf.setImage(with: url)
https://github.com/onevcat/Kingfisher
I've had a great help in creating a functional image cache for a UITableViewCell in cellForRowAtIndex. Unfortunately, with the code below, only one image is displayed over and over. I was under the impression that cellForRowAtIndexPath was like a for loop, running again for each row. Therefore, I'm wondering why only one image is displayed.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "restaurantcell") as? RestaurantTableCell
var oneRestaurant: Restaurant = tablerestaurantarray[indexPath.row]
if let cachedVersion = cache.object(forKey: "image") {
oneRestaurant = cachedVersion
} else {
cache.setObject(oneRestaurant, forKey: "image")
}
cell?.picture?.image = oneRestaurant.value(forKey: "image") as! UIImage?
let restaurant = restaurantArray[indexPath.row]
cell?.name?.text = restaurant.value(forKey: "Name") as? String
return cell!
}
Update 2:
Results from the added breakpoint
You use the same NSCache key ("image") for different objects. That's why only the first Restaurant object is saved into the cache. For all other cells you look for the object cached for key "image" and get the same previously saved Restaurant object back.
You have to use different keys for caching different Restaurant objects. Try to append the index path to the cache key:
let key = "restaurant \(indexPath)"
if let cachedVersion = cache.object(forKey: key) {
oneRestaurant = cachedVersion
} else {
cache.setObject(oneRestaurant, forKey: key)
}
I don't quite understand why you want to cache restaurant objects though. You already have them in the tablerestaurantarray, so there won't be any benefit caching them. Maybe your intention was caching the images?
I have created a fully functioning tableview that populates its data from a text array (String) and an image array (PFFile). I have also implemented a search bar that displays the filtered results based on the created text array.
var fruitArray = [String]()
var imageFile = [PFFile]()
the problem is that fruitArray[indexPath.row] is filtered according to the input in the search bar but, the search bar cannot filter anything from imageFile and imageFile[indexPath.row] is displayed as if nothing has been searched.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
print(fruitArray[indexPath.row])
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! TableViewCell
if searchController.active && searchController.searchBar.text != "" {
cell.labelFruitName?.text = searchResults[indexPath.row]
} else {
cell.labelFruitName?.text = fruitArray[indexPath.row]
}
let placeHolder = UIImage(named: "plchlder.png")
cell.fruitImages?.image = placeHolder
imageFile[indexPath.row].getDataInBackgroundWithBlock { (data, error) -> Void in
if let downloadedImage = UIImage(data: data!) {
cell.fruitImages?.image = downloadedImage
}
}
return cell
}
as a result, the images won't change inside the cell. in other words, before searching anything, if the first cell shows a text and picture of an apple, after the search, the first cell will always display an apple but the text and number of cells change.
Any solutions would be appreciated. Thanks in advance
I would suggest you create an array of type [fruit] , where fruit is just a struct with an image and a string. Then you can filter out the bad apples.
Here is the code for the struct:
struct Fruit {
let name: String
let image: PFFile
}
Then in your searchResultsUpdating function just filter out your search term using a simple filter:
results = fruitArray.filter{$0.name.containsString(searchController.searchBar.text!)}
I am using parse to retrieve my images and labels and display it on a collection view. The problem was that the collection view loads all the images and labels at once making the load time long and memory usage was high. I was thinking that I would load 10 cells each time however I was recommended to use SDWebImage to make the app lighter. However I don't know how to implement it with parse using swift. I am suspecting that I would put some code in this piece of code below
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("newview", forIndexPath: indexPath) as! NewCollectionViewCell
let item = self.votes[indexPath.row]
let gesture = UITapGestureRecognizer(target: self, action: Selector("onDoubleTap:"))
gesture.numberOfTapsRequired = 2
cell.addGestureRecognizer(gesture)
// Display "initial" flag image
var initialThumbnail = UIImage(named: "question")
cell.postsImageView.image = initialThumbnail
// Display the country name
if let user = item["uploader"] as? PFUser{
item.fetchIfNeeded()
cell.userName!.text = user.username
var profileImgFile = user["profilePicture"] as! PFFile
cell.profileImageView.file = profileImgFile
cell.profileImageView.loadInBackground { image, error in
if error == nil {
cell.profileImageView.image = image
}
}
var sexInt = user["sex"] as! Int
var sex: NSString!
if sexInt == 0 {
sex = "M"
}else if sexInt == 1{
sex = "F"
}
var height = user["height"] as! Int
cell.heightSexLabel.text = "\(sex) \(height)cm"
}
if let votesValue = item["votes"] as? Int
{
cell.votesLabel?.text = "\(votesValue)"
}
// Fetch final flag image - if it exists
if let value = item["imageFile"] as? PFFile {
println("Value \(value)")
cell.postsImageView.file = value
cell.postsImageView.loadInBackground({ (image: UIImage?, error: NSError?) -> Void in
if error != nil {
cell.postsImageView.image = image
}
})
}
return cell
}
I have implemented SDWebImage using Pods and have imported through the Bridging Header. Is there anyone who knows how to implement SDWebImage with parse using Swift?
You should rethink your approach -
I believe you are using collectionViewDelegate method - collectionView(_:cellForItemAtIndexPath:)
this fires every time the collection view needs a view to handle.
In there you can access the cell imageView and set its image (For Example)-
cell.imageView.sd_setImageWithURL(url, placeholderImage:placeHolderImage, completed: { (image, error, cacheType, url) -> Void in })
And if you wish to fade in the image nicely, you could -
cell.imageView.sd_setImageWithURL(url, placeholderImage:placeHolderImage, completed: { (image, error, cacheType, url) -> Void in
if (cacheType == SDImageCacheType.None && image != nil) {
imageView.alpha = 0;
UIView.animateWithDuration(0.0, animations: { () -> Void in
imageView.alpha = 1
})
} else {
imageView.alpha = 1;
}
})
EDIT
I see the you use Parse, so you don't need SDWebImage, you need to use Parse - PFImageView, It will handle your background fetch for the image when it loads. You will need to save reference to your PFObject, but I believe you already do that.
For example (inside your cellForItemAtIndexPath)-
imageView.image = [UIImage imageNamed:#"..."]; // placeholder image
imageView.file = (PFFile *)someObject[#"picture"]; // remote image
[imageView loadInBackground];
How many objects are displaying in the collection view?
Since you mentioned SDWebImage, are you downloading the images in the background as well?
If you want to load the images as the user scrolls, have a look at the documentation for SDWebImage. The first use case describes how to display images in table view cells withouth blocking the main thread. The implementation for collection view cells should be similar.
I have a UITableView with its contents managed by a NSFetchedResultsController fetching CoreData. I have two types of table view cells, one with an image and one without an image. The images are handled by UIDocument and are kept in the iCloud ubiquitous documents container and are referenced by client name.
As the cells are generated and reused when there are many user generated images on the screen, the memory usage of my program creeps higher and higher. Around 110 mb, I get a low memory warning.
I suspect my prepareForReuse() method in my tableView cell isn't doing its job correctly.
Here's what my UITableViewCell looks like now:
class ClientTableViewCell: UITableViewCell {
#IBOutlet weak var clientName: UILabel!
#IBOutlet weak var clientModifiedDate: UILabel!
#IBOutlet weak var profileImage: UIImageView!
let dateFormatter = NSDateFormatter()
var document: MyDocument?
var documentURL: NSURL?
var ubiquityURL: NSURL?
var metaDataQuery: NSMetadataQuery?
var myClient : Client?
{
didSet
{
updateClientInfo()
}
}
override func prepareForReuse() {
super.prepareForReuse()
clientName.text = nil
clientModifiedDate.text = nil
if let mc = myClient
{
if mc.imageurl != ""
{
if let p = profileImage
{
p.image = nil
} else
{
NSLog("nil document")
}
}
} else
{
NSLog("nil client")
}
}
func updateClientInfo()
{
if myClient != nil
{
clientName.text = myClient!.name
dateFormatter.dateStyle = .ShortStyle
let dispDate = dateFormatter.stringFromDate(NSDate(timeIntervalSinceReferenceDate: myClient!.dateModified))
clientModifiedDate.text = dispDate
if let imageName = myClient?.imageurl {
if myClient?.imageurl != "" {
var myClientName : String!
myClientName = myClient!.name
metaDataQuery = NSMetadataQuery()
metaDataQuery?.predicate = NSPredicate(format: "%K like '\(myClientName).png'", NSMetadataItemFSNameKey)
metaDataQuery?.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "metadataQueryDidFinishGathering:",
name: NSMetadataQueryDidFinishGatheringNotification,
object: metaDataQuery!)
metaDataQuery!.startQuery()
}
}
}
}
func metadataQueryDidFinishGathering(notification: NSNotification) -> Void {
let query: NSMetadataQuery = notification.object as! NSMetadataQuery
query.disableUpdates()
NSNotificationCenter.defaultCenter().removeObserver(self, name: NSMetadataQueryDidFinishGatheringNotification, object: query)
query.stopQuery()
let results = query.results
if query.resultCount == 1 {
let resultURL = results[0].valueForAttribute(NSMetadataItemURLKey) as! NSURL
document = MyDocument(fileURL: resultURL)
document?.openWithCompletionHandler({(success: Bool) -> Void in
if success {
if let pi = self.profileImage
{
pi.image = self.document?.image
}
} else {
println("iCloud file open failed")
}
})
} else {
NSLog("Could not find profile image, creating blank document")
}
}
}
If you're not familiar with iCloud and UIDocument, you might be wondering why I'm querying the metadata to get at these images/documents. If you know of a reliable way of fetching these images/documents in the ubiquitous container, I'm all ears. It causes this pop in effect during the scrolling of the table view cells.
And here's my function that populates the cells:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//set the client
let client = clients.fetchedObjects![indexPath.row] as! Client
//set the correct identifier
if let imageurl = client.imageurl as String?
{
if imageurl == ""
{
var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifierNoImage, forIndexPath: indexPath) as? ClientTableViewCell
cell!.myClient = client
return cell!
} else
{
var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as? ClientTableViewCell
cell!.myClient = client
return cell!
}
}
var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifierNoImage, forIndexPath: indexPath) as? ClientTableViewCell
cell!.myClient = client
return cell!
}
I originally was going to use the attribute imageurl to store the name and url of the client profile image, but since the ubiquitous container url is unique for every device, I now only use it to detect if there is an image with this client or not.
I've been having this issue for weeks now and cannot seem to nail it down. Any guidance would be greatly appreciated!
Workbench: Xcode 6.3 and Swift 1.2.
Solved my own memory leak! With the help of this answer here: [UIDocument never calling dealloc
I was querying UIDocuments and never closing them. That's why the ARC wasn't cleaning up my unused UIDocuments. The link above has the Object C version, but for folks using Swift, try these lines to close your documents after using them:
document?.updateChangeCount(UIDocumentChangeKind.Done)
document?.closeWithCompletionHandler(nil)
Whew! Glad that mess was over. Hope this helps someone!