SelectedBackgroundView of UICollectionViewCell visible when it should not be - ios

I've got a UICollectionView. With some cells inside with a white background color. I've set the selectedBackgroundView to a basic purple view.
My CollectionView has a constraint with a height of 0 and when I hit a button I update the constraint to 80. When I'm doing that, during the animation i can see the purple background on the screen until the end on the animation and i cannot understand why or how prevent this ?
Everything else working fine, it's just a "visual" bug.
Any suggestion about how to fix this ?
Gif of the bug where you can see the purple appearing during the animation
Here is my cell construction if it can be of any help :
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell = collectionView.dequeueReusableCellWithReuseIdentifier("WidgetMenuCellIdentifier", forIndexPath: indexPath) as UICollectionViewCell
cell.removeSubviews()
// some code setup
cell.selectedBackgroundView = UIView()
cell.selectedBackgroundView.backgroundColor = UIColor.purpleColor()
return cell
}

Subclass your UICollectionViewCell
Do
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
// your code
cell.selectedBackgroundView.hidden = true
return cell
}
Then in your subclass :
override var selected:Bool {
willSet {
self.selectedBackgroundView.hidden = false
}
}
It should work.

It seems like this code is being executed within an animation, causing unexpected behavior at times based on how various properties animate. Another complicating factor is that, because cells are reused, it won't reproduce if a reused cell is already configured correctly (i.e. there is nothing to animate). Adding the following after styling the selectedBackgroundView was the least hacky solution I could think of.
[cell.selectedBackgroundView.layer removeAllAnimations];
Depending on what your cells are like you may also want to consider removing animations on other layers as well. For example:
[cell.backgroundView.layer removeAllAnimations];

Related

Scrolling bad practice for UICollectionViews

I made a horizontal scrolling UICollectionView and I want the cell that's in the middle to have a white font while the rest is black.
If I just use scrollViewDidEndDecelerating highlighting of the middle cell seems to jump around more than if I use both scrollViewWillBeginDecelerating and scrollViewDidEndDecelerating to highlight the middle cell. Is this bad practice?
extension CurrencySelectorTableViewCell: UIScrollViewDelegate{
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
self.findCenterIndex()
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
self.findCenterIndex()
}
}
This code btw still doesn't animate perfectly like I want it to so I'm open to any advice how to make this scrolling mechanism as smooth as possible.
When the UICollectionView thus starts scrolling this function is triggered:
func findCenterIndex() {
let center = self.convert(self.collectionView.center, to: self.collectionView)
let index = collectionView!.indexPathForItem(at: center)
if let selectedIndex = index {
self.selectedCell = selectedIndex.item
self.collectionView.reloadData()
}
}
Upon reloading the UICollectionView the label in the cell that is located in the middle will look different from the rest:
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CurrencySelectorCollectionViewCell", for: indexPath) as! CurrencySelectorCollectionViewCell
if (indexPath.item == self.selectedCell) {
cell.currencyLabel.textColor = UIColor.white
cell.currencyLabel.font = cell.currencyLabel.font.withSize(22)
} else {
cell.currencyLabel.textColor = UIColor.black
cell.currencyLabel.font = cell.currencyLabel.font.withSize(15)
}
cell.currencyLabel.text = currencies[indexPath.item]
return cell
}
Right now it jumps around a little bit because it will only change the label when the scrolling has just started or just stopped. I would like this effect on the UITextLabel to happen continuously throughout the scrolling process.
Try to add removeAllAnimations() of your UILabel layer before you fire off a new animation:
[view.layer removeAllAnimations];
EDIT:
Based on your edit in the question, you are not running any animation. You are calling reloadData on your UICollectionView, which is really bad practice.
You should just simple either:
1: (Bad option)
Reload the Cell only with performBatchUpdates(_:completion:)
2: Good option
Access the cell as a variable in your cell findCenterIndexwith cellForItem(at:) and simply just do your update to the label.
You can also deselect the other cells by getting an array of the visibleCells and simply just do as same described above, but you fire your "deselection" code instead. You could actually do this before you run your selection code. Or do everything in one action by Simply run a for loop on the visible cells and "deselect" them in your loop, and select the one in your CGPoint center.
This way, you never even have to reload your UICollectionView and is the best practice. And you also avoid flickers and animations.

UICollectionView Cell subview resizes after collectionView: cellForItemAtIndexPath is complete

I am using a collection view to display a collection of profile images and the person's name. So my cell has a UIImageView and a UILabel as subviews. I am using the UICollectionViewDelegateFlowLayout method:
collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
to calculate the cell's size depending on the available space.
All of this is working fine. I have added constraints to my subviews in the cell so that they also resize accordingly.
The issue I am running into is that I want my UIImageViews to be circles. It seems like auto layout is not recalculating the cell's subview size until after I have applied that effect. Instead, when I calculate the cornerRadius for the imageView it is still saying the imageViews width is 114.0 (which is what is in the storyboard) regardless of how big the cell is. This results in circles on the iPhone 5s but only rounded corners on any bigger device. Here is my code for that:
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("PersonCell", forIndexPath: indexPath)
configureCell(cell, atIndexPath: indexPath)
return cell
}
func configureCell(cell: UICollectionViewCell, atIndexPath indexPath: NSIndexPath) {
if let person = personAtIndexPath(indexPath) {
let imageView = cell.viewWithTag(100) as! UIImageView
let nameLabel = cell.viewWithTag(200) as! UILabel
cell.contentView.frame = cell.bounds
cell.contentView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
circularizeImageView(imageView)
imageView.image = person.profileImage
nameLabel.text = person.name
}
}
func circularizeImageView(imageView: UIImageView) {
imageView.layer.cornerRadius = (CGRectGetWidth(imageView.bounds) / 2)
imageView.clipsToBounds = true
imageView.layer.borderWidth = 2.0
imageView.layer.borderColor = UIColor.whiteColor().CGColor
}
I saw in a couple places that previously the subviews would not resize at all like here: UICollectionView cell subviews do not resize
I don't think this is an issue any more, however I did add the fixes into my code as you can see in configureCell() but it still isn't helping.
So it seems like those subviews are not resized until after the cellForItemAtIndexPath call is completed. Any thoughts on how I might address this? See screenshot of :rounded corners on UIImageViews instead of complete circles
Don't use viewWithTag(), it's bad practice. Instead make the UILabel and UIImageView public or leave off the scope modifier.
Is the UIImageView a fixed size? If so, you don't need to call circularizeImageView() each time a cell is reused. Instead, call it in layoutSubviews() in your UITableViewCell subclass. This will also give you the correct size for imageView.bounds.height

UITableView with fading cells on top and bottom - swift

I need my table view to fade its top and bottom cells as it is scrolled. I have tried some solutions involving gradient and masks but non of it worked, the gradient from clear to white has a black tint. Does anyone has a solution to accomplish that in swift?
You can achieve desired effect by using some methods defined in UITableViewDelegate protocol. First thing you need to know that cell main subview is contentView add all other subviews are subviews of it. What you need to do is to set contentView alpha to 0 in cellForRowAtIndexPath:indexPath: method.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel?.text = "Fade Cell"
cell.contentView.alpha = 0 // Here we set the alpha of the content view
return cell
}
If you run your application now, you would have plain white cells. Only thing we need now is to know when cells are displayed, so we can show contentView. Second UITableViewDelegate protocol method comes in handy now.
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
UIView.animateWithDuration(0.4) {
cell.contentView.alpha = 1
}
}
This delegate method is called when cells are preparing to be displayed, and it's the perfect place to animate contentView alpha property to 1.

How to add a background image to a UICollectionViewCell in Swift?

I had assumed that it would be easy to add a background image to a UICollectionViewCell, but I am having trouble figuring it out. I have a border image like this that I want to add to every cell in my UICollectionView.
border.png
I couldn't see an option to add it in the Interface Builder and I'm not sure which method to use on UICollectionViewCell programmatically.
I have seen these similar questions:
UICollectionView Cell with Image, change Background with click (image is already added)
How to add a background image to UICollectionView that will scroll and zoom will cells (UICollectionView, not UICollectionViewCell)
I know this is a old question, with an accepted answer. But why not just set the border of the cell like so:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath)
cell.layer.borderWidth = 1.5
cell.layer.borderColor = UIColor.blueColor().CGColor
cell.layer.cornerRadius = 4
return cell
}
That way you don't need to use a image. Just adjust the borderWidth and cornerRadius until you get the desired effect.
let View=UIView()
View.backgroundColor=UIColor(patternImage:UIImage(named:"border.png")!)
cell.backgroundView=View
in uicollectionview cellForItemAtIndexPath method
Add a UIImageView to your CollectionViewCell and set this image as the image.

Swift - Selecting item in Collection View

I'm having a problem with selected items in a collection view.
Selected items change backgroundColor to blue, but it seems like the reusable cells are also affected.
my code looks like this:
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
var cell = collectionView.cellForItemAtIndexPath(indexPath) as UICollectionViewCell?
cell?.contentView.backgroundColor = UIColor.blueColor()
func collectionView(collectionView: UICollectionView, didDeslectItemAtIndexPath indexPath: NSIndexPath) {
var cell = collectionView.cellForItemAtIndexPath(indexPath) as UICollectionViewCell?
cell?.contentView.backgroundColor = UIColor.blackColor()
}
func collectionView(collectionView: UIControllerView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell: boxCell = collectionView.dequeueReusableCellWithReuseIdentifier("demoCell", forIndexPath: indexPath) as boxCell
cell.cellTitle.text = name[indexPath.row]
}
When I run the application, the selection works, selecting another cell, deselects the other selected cells, but when I scroll, the reusable cells are also turning blue.
I am using a horizontal scroll direction with only 1 row and 4 cells per row.
Where did I go wrong? Anybody else have had this issue?
It's normal behavior - the reason for that is that the items are reused, and after reusing they go through cellForItemAtIndexPath where you are not setting background color, so they keep last one you ever set - in your case didSelect method.
You should create a NSSet where you will keep all selected NSIndexPath's and add / remove NSIndexPath to it when selecting / deselecting.
Setting background color logic should be done in cellForItemAtIndexPath - after you check if NSIndexPath is existing in your NSSet you set a desired color.
On selection you will have to also reload certain element, so it will call cellForItemAtIndexPath for the one you clicked.
Use the prepare for resue method in your cell class.It will work fine.
override func prepareForReuse()
{
self.backgroundColor = UIColor.lightGrayColor()
}

Resources