I use Alamofire library for showing image in cell in the collectionView
but my problem is when I scrolling up && down , my CollectionView showing wrong image in cell
and this is my snippet code for set cell data
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! CardViewCell
let pos = indexPath.item % 5
var paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 2.5
paragraphStyle.alignment = .Right
paragraphStyle.lineBreakMode = .ByTruncatingTail
if let text = currentCollection.titles?[indexPath.item] {
var attrString = NSMutableAttributedString(string: text)
attrString.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range:NSMakeRange(0, attrString.length))
cell.title.attributedText = attrString
} else {
cell.title.text = currentCollection.titles?[indexPath.item]
}
if let catId = currentCollection.catIds?[indexPath.item] {
cell.iconImage.image = UIImage(named: icons[catId-1])
}
cell.title.font = UIFont(name: "B Yekan", size: 14)
cell.title.preferredMaxLayoutWidth = cell.frame.size.width - 48
cell.iconImage.image = cell.iconImage.image!.imageWithRenderingMode(.AlwaysTemplate)
cell.iconImage.tintColor = UIColor.whiteColor()
let imageUrl = currentCollection.urlImages![indexPath.item]
if let image = currentCollection.imageCache.objectForKey(imageUrl) as? UIImage {
cell.backImage.image = image
} else {
cell.backImage.image = nil
cell.request = Alamofire.request(.GET, imageUrl).validate(contentType: ["image/*"]).responseImage() {
(request, _, image, error) in
if error == nil && image != nil {
self.currentCollection.imageCache.setObject(image!, forKey: request.URLString)
cell.backImage.image = image
}
}
}
cell.layer.shadowPath = UIBezierPath(roundedRect: cell.bounds, cornerRadius: cell.layer.cornerRadius).CGPath
if indexPath.item == currentCollection.titles!.count-3 {
currentCollection.page++
appendTitlesInPage(currentCollection.page)
}
return cell
}
can help me where is the wrong? please!
The image request takes time - during that time the request keeps a reference to the cell, and sets the cell's backImage.image once the request is complete. Unfortunately, by that time, the cell may have been scrolled off-screen and may be being re-used at another indexPath, where the image is incorrect. Instead, you should use the table view's cellForItemAtIndexPath method to get the correct cell (if the cell is no longer visible, this will return nil which will prevent your error).
EDIT: Now I have developed a small CocoaPods library to handle this problem seamlessly. I suggest you to use this one, I have it running in a couple of projects myself without any issues.
https://github.com/gchiacchio/AlamoImage
As #johnpatrickmorgan said, the problem is: when you scroll the cell is reused, and by the time the request for the image is responded, it's no longer valid (image & cell don't match).
As the response time is determined by networking conditions, you only can wait for the response, but what you CAN DO is to CANCEL the request in progress at the moment you know the cell will be reused. To accomplish that you can put a cell.request?.cancel() BEFORE setting the new image, as follows:
let imageUrl = currentCollection.urlImages![indexPath.item]
//Cancel the request in progress (to a wrong image) if any.
cell.request?.cancel()
if let image = currentCollection.imageCache.objectForKey(imageUrl) as? UIImage {
cell.backImage.image = image
} else {
cell.backImage.image = nil
cell.request = Alamofire.request(.GET, imageUrl).validate(contentType: ["image/*"]).responseImage() {
(request, _, image, error) in
if error == nil && image != nil {
self.currentCollection.imageCache.setObject(image!, forKey: request.URLString)
cell.backImage.image = image
}
}
}
Look at the "LazyTableImages" sample app that Apple provides. It's with a UITableView, but it would be essentially the same code as with a UICollectionView, and you don't need a third party library to do it.
IconDownloader *iconDownloader = (self.imageDownloadsInProgress)[indexPath];
if (iconDownloader == nil)
{
iconDownloader = [[IconDownloader alloc] init];
iconDownloader.appRecord = appRecord;
[iconDownloader setCompletionHandler:^{
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
// Display the newly loaded image
cell.imageView.image = appRecord.appIcon;
// Remove the IconDownloader from the in progress list.
// This will result in it being deallocated.
[self.imageDownloadsInProgress removeObjectForKey:indexPath];
}];
(self.imageDownloadsInProgress)[indexPath] = iconDownloader;
[iconDownloader startDownload];
The relevant part to you is in the completion handler, where you're getting the cell using cellForRowAtIndexPath. This (and the corresponding collection view method) ensure you're getting the right cell, and if it's offscreen it returns nil and everything degrades gracefully.
Related
I am using collection view and we have added bubble to the cell.
Also, bubble has the different fill color and border color.
Please find attachment for details.
but when we scroll collection view that time bubble color sometime changed to different and again restored to correct color.
Here's my code:
func collectionView(_ collectionView: UICollectionView,cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// INIT CELLS INSIDE COLLECTION VIEW
if(collectionView == customContentCollectionView){
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: contentCellId, for: indexPath) as! MyContentCell
setupContentCellComponents(cell: cell)
// Configure the cell
cell.horizontalLine.backgroundColor = Color.lightBlue
var labelColour = UIColor()
// If we found record
if(self.bubbleArray[indexPath.section][indexPath.item].interactions != ""){ // GENERATING CUSTOM BUBBLE COLOR AS PER INTERACTIONS
let (bubbleBorder, bubbleFill, labelColor, inspectorNav) = getBubbleColor(controlType: controlParam, count: Int(self.bubbleArray[indexPath.section][indexPath.item].interactions)!, selected: false)
cell.shapeLayer.strokeColor = bubbleBorder.cgColor
cell.shapeLayer.fillColor = bubbleFill.cgColor
cell.gradient.colors = [bubbleFill.cgColor, bubbleFill.cgColor]
labelColour = labelColor
}
cell.labelCount.font = UIFont(name: cellFontName, size: cellFontSize)
cell.labelCount.text = self.bubbleArray[indexPath.section][indexPath.item].interactions
if (self.bubbleArray[indexPath.section][indexPath.item].umid != ""){
cell.tag = Int(self.bubbleArray[indexPath.section][indexPath.item].umid)!
cell.labelCount.tag = Int(self.bubbleArray[indexPath.section][indexPath.item].umid)!
cell.labelCount.textColor = labelColour
}
else (self.bubbleArray[indexPath.section][indexPath.item].umid == ""){ // REMOVING BUBBLE IF NO CONTENT
cell.shapeLayer.removeFromSuperlayer()//remove from superview
}
}
Here's scrolling logic :
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// selectedSection is the value where you have tapped
if(selectedSection != -1){
for i in 0...numberOfItemsInSection{ // CODE TO MAINTAIN ROW HIGHLIGHT POST SCROLL
let newIndices = IndexPath(row: i, section: selectedSection)
for visibleIndices in customContentCollectionView.indexPathsForVisibleItems{
if(newIndices == visibleIndices){
print("NEW INDICES: \(newIndices)")
if(newIndices.section == selectedSection){
selectedRowCells.append(customContentCollectionView.cellForItem(at: newIndices)!)
}
// horizontal line for selected bubble
let singleCell : MyContentCell = customContentCollectionView.cellForItem(at: newIndices)! as! MyContentCell
singleCell.horizontalLine.backgroundColor = UIColor.white
if(previouslySelectedIndex != nil && visibleIndices == previouslySelectedIndex){
changeBubbleColor(index: previouslySelectedIndex, selected: true)
break
}
}
}
Try this way,
Here if cell satisfies the condition then properties are set but if the condition is not satisfied at that time cell takes previous properties set in the conditon so you also need to set the properties in else condition also
so try by setting properties as suggested with comment in code below
I hope you will get your solution
func collectionView(_ collectionView: UICollectionView,cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// INIT CELLS INSIDE COLLECTION VIEW
if(collectionView == customContentCollectionView){
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: contentCellId, for: indexPath) as! MyContentCell
setupContentCellComponents(cell: cell)
// Configure the cell
cell.horizontalLine.backgroundColor = Color.lightBlue
var labelColour = UIColor()
// If we found record
if(self.bubbleArray[indexPath.section][indexPath.item].interactions != "")
{ // GENERATING CUSTOM BUBBLE COLOR AS PER INTERACTIONS
let (bubbleBorder, bubbleFill, labelColor, inspectorNav) = getBubbleColor(controlType: controlParam, count: Int(self.bubbleArray[indexPath.section][indexPath.item].interactions)!, selected: false)
cell.shapeLayer.strokeColor = bubbleBorder.cgColor
cell.shapeLayer.fillColor = bubbleFill.cgColor
cell.gradient.colors = [bubbleFill.cgColor, bubbleFill.cgColor]
labelColour = labelColor
}
else
{
//set default cell.shapeLayer.strokeColor
//set default cell.shapeLayer.fillColor
//set default cell.gradient.colors
//set default labelColour
}
cell.labelCount.font = UIFont(name: cellFontName, size: cellFontSize)
cell.labelCount.text = self.bubbleArray[indexPath.section][indexPath.item].interactions
if (self.bubbleArray[indexPath.section][indexPath.item].umid != "")
{
cell.tag = Int(self.bubbleArray[indexPath.section][indexPath.item].umid)!
cell.labelCount.tag = Int(self.bubbleArray[indexPath.section][indexPath.item].umid)!
cell.labelCount.textColor = labelColour
}
else
{
//Set default cell.tag
//Set default cell.labelCount.tag
//Set default cell.labelCount.textColor
}
}
Im trying to do a table where the imageView on the cell changes alpha from 0 to 1 when the image is done loading (async).
What ever I do it seem that the image is just shown at one and not fading in. I'm sure it's some kind of race condition but I am new to animations in iOS and have no idea how to solve this. Any input would be great.
Here is my code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
//Configure the cell...
let episode = episodes[indexPath.row]
cell.textLabel?.text = episode.title
cell.detailTextLabel?.text = episode.content
let logoUrl = URL(string: episode.logoUrl!)
if (episode.logoImage == nil){
episode.logoImage = UIImage()
DispatchQueue.global().async {
let data = try? Data(contentsOf: logoUrl!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
DispatchQueue.main.async {
episode.logoImage = UIImage(data: data!)
cell.imageView?.image = episode.logoImage
self.episodesTable.reloadData()
cell.imageView?.alpha = 0
UIView.animate(withDuration: 1, animations: {
cell.imageView?.alpha = 1
})
}
}
} else{
cell.imageView?.image = episode.logoImage
}
return cell
}
You need to set alpha to 0 first before animating to 1.
cell.imageView?.alpha = 0
UIView.animate(withDuration: 1, animations: {
cell.imageView?.alpha = 1
})
Also, you dont need to reload table. Remove self.episodesTable.reloadData().
You are spanning a background thread and loading the image from url inside that thread. What if, in between user has scrolled the cell. You would be left with a wrong image on a wrong cell(because of cell reuse, that is).
My advice is to use SDWebImageCache, and use its completion block to animate the alpha.
// Changing animation duration to 0.2 seconds from 1 second
if(cacheType == SDImageCacheTypeNone) {
cell.imageView?.alpha = 0
[UIView animateWithDuration:0.2 animations:^{
cell.imageView?.alpha = 1;
}];
}
reloadData() call is causing reloading of all the cells including the one you are trying to animate. My advice is to mark your cell with it's index path. After async call check if it is still presenting the right data and animate it without reloading the whole table view.
// ...
cell.tag = indexPath.item
DispatchQueue.global().async {
// async load
DispatchQueue.main.async {
guard cell.tag == indexPath.item else { return }
cell.imageView?.alpha = 0.0
cell.imageView?.image = image
// animate
}
}
// ...
In my cellForRowAtIndexPath method I set the cornerRadius of the layer on my image:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("feedCell")!
if self.pictunes?.count > 0 {
// There are some pictunes to show; Create and update the posts
if self.pictuneImages.count > indexPath.row {
// We have an image for the user who made the current pictune
if let pictunerImageView = cell.contentView.viewWithTag(2) as? UIImageView {
pictunerImageView.layer.cornerRadius = 17.5 // Cut it to a circle
pictunerImageView.image = self.pictunerImages[0]
}
// We also have an image for the pictune at the current post
if let pictuneImageView = cell.contentView.viewWithTag(1) as? UIImageView {
pictuneImageView.image = self.pictuneImages[indexPath.row]
}
}
return cell
} else {
// No pictunes have loaded yet; We probably need more time
// to load the image and audio assets for some of them
cell.textLabel!.text = "No Pictunes Available Yet"
return cell
}
}
But when I scroll I see this nasty background effect:
How should I get rid of this effect? Clearing the background context didn't help at all. Thanks!
Set:
pictunerImageView.layer.masksToBounds = true
after:
pictunerImageView.layer.cornerRadius = 17.5
You could also change this to:
pictunerImageView.layer.cornerRadius = pictunerImageView.frame.size.height *0.5
In case you ever want to change the size but still want it to be a circle.
Allright, I think I've read all the issues which seemed to be the same as I did.
I have a UITextView in a UITableViewCell, the cell itself contains an imageView, a UILabel as title, the UITextView and another UILabel.
Screenshot:
I believe the constraints to be correct. Currently the view does an estimate which is correct in most cases, but in my case the content is larger than the UITableViewCell's height, this is probably due to the delayed rendering of the UITableViewCell, causing other list items to draw behind this one (it only appears on top).
The code which estimates the height:
func heightForCellAtIndexPath(indexPath:NSIndexPath) -> CGFloat {
if (calculatorCell[cellIdentifier] == nil) {
calculatorCell[cellIdentifier] = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? UITableViewCell
}
if let cell = calculatorCell[cellIdentifier] {
populateCell(cell, indexPath: indexPath)
cell.setNeedsLayout()
cell.layoutIfNeeded()
return cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
}
ErrorClass.log("Could not instantiate cell? returning 100 float value instead for listView")
return 100.0
}
This is a generic function as you see since it's handling a UITableViewCell not a custom, so the populateCell in this case, the first index returns the cell with the issue, the rest are comments which are working fine, I'm just sharing it all since people will ask for it:
if (indexPath.row == 0) {
let cellUnwrapped = cell as! ChallengeHeader
cellUnwrapped.setContent(
challenge!.content,
date: StrFormat.deadLineFromDate(challenge!.endAt),
likes: Language.countValue("challenge_count_like", count: challenge!.totalLikes),
likeController: self,
commentCount: Language.countValue("global_count_comment", count: challenge!.totalComments)
)
cellUnwrapped.like(userLiked)
cellUnwrapped.loadAttachments(challenge!.attachments)
return cellUnwrapped
} else {
let cellUnwrapped = cell as! ListItemComment
let challengeComment = items[(indexPath.row - 1)] as! ChallengeComment
var username = "Anonymous"
if let user = challengeComment.author as User? {
cellUnwrapped.iconLoad(user.avatar)
username = user.displayName
}
cellUnwrapped.setContentValues(
StrFormat.cleanHtml(challengeComment.text),
date: StrFormat.dateToShort(challengeComment.createdAt),
username: username
)
return cellUnwrapped
}
The cell's setContentvalues which parses the html string to NSAttributedString, it's wrapped in an if to prevent reloads to re-render the NSAttributedString. It won't change but it does take a while to render:
func setContent(content:String, date:String, likes:String, likeController : LikeController, commentCount:String) {
if (content != txtContent) {
self.contentLabel.text = nil
self.contentLabel.font = nil
self.contentLabel.textColor = nil
self.contentLabel.textAlignment = NSTextAlignment.Left
contentLabel.attributedText = StrFormat.fromHtml(content)
txtContent = content
}
deadlineLabel.text = date
likesLabel.text = likes
self.likeController = likeController
likeButton.addTarget(self, action: "likeClicked:", forControlEvents: .TouchUpInside)
totalComments.text = commentCount
totalComments.textColor = StyleClass.getColor("primaryColor")
doLikeLayout()
}
So TL;DR; I have a UITextView inside a UITableViewCell scaling sizes using autolayout and containing an NSAttributedString to parse html and html links. How come it does not render well, and how to fix it?
I am trying to make a table wherein first cell has a differernt layout than the rest. I want to put the image as background for first cell i.e it shd look something like this:
and here is the code for my implementation
func imageCellAtIndexPath(indexPath:NSIndexPath) -> MainTableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier(imageCellIdentifier) as MainTableViewCell
let object = self.fetchedResultsController.objectAtIndexPath(indexPath) as NSManagedObject
let eTitle:NSString = object.valueForKey("title")!.description
let deTitle = eTitle.stringByDecodingHTMLEntities()
cell.artTitle.text = deTitle
var full_url = object.valueForKey("thumbnailURL")!.description
var url = NSURL(string: full_url)
var image: UIImage?
var request: NSURLRequest = NSURLRequest(URL: url!)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {(response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
image = UIImage(data: data)
if((indexPath.row)==0) {
var imageView = UIImageView(frame: CGRectMake(10, 10, cell.frame.width - 10, cell.frame.height - 10))
imageView.image = image
cell.backgroundView = UIView()
cell.backgroundView?.addSubview(imageView)
}
else{
cell.thumb.image = image
}
})
return cell
}
bt the problem is.. when i scroll down and scroll up back again, the background image starts repeating and the thumbnails also get overlapped as shown:
If i scroll up and down again.. this is wht happens:
i might have done some silly mistake bt m not able to figure out what it is. pls help
In table views cells are reused and you should reset parts of the cell that are not relevant the specific version of the cell style you are after. Something like:
if((indexPath.row)==0) {
let frame = CGRectMake(10, 10,
cell.frame.width - 10, cell.frame.height - 10)
var imageView = UIImageView(frame: frame)
imageView.image = image
cell.backgroundView = UIView()
cell.backgroundView?.addSubview(imageView)
// Reset
cell.thumb.image = nil
} else{
cell.thumb.image = image
// Reset
cell.backgroundView = nil
}
Even better and more idiomatic idea is to use separate UITableViewCell designs for these two cell types, each with different reuse identifiers. This way you don't need to care about resetting.
P.S. You should use dequeueReusableCellWithIdentifier:forIndexPath: instead of older dequeueReusableCellWithIdentifier: as it guarantees that a cell is returned.
The problem is that the cell is being reused by the tableView for efficiency purposes but it is never being reset.
You need to clear / remove the imageView from the background view if the cell is not at indexPath 0.