PrepareForReuse in CollectionViewCell - ios

I want to hide the label in a cell that was tapped and instead show an image. But I want to do this only if a cell with a certain index has already been set to the imageView.
What is the best way to address the cells and store if they are set to imageView or not? How do I use the prepareForReuse method?
This is the way I do it until now, but as the cells are reused. The image is shown in other cells at scrolling.
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
println("user tapped on door number \(indexPath.row)")
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! MyCollectionViewCell
if (cell.myLabel.text == "1") {
one = true
if(seven = true) {
if (cell.myLabel.hidden) {
cell.myLabel.hidden = false
cell.MyImageView.image = nil
}
else {
cell.myLabel.hidden = true
cell.MyImageView.image = UIImage(named:"1")!
}
}
}

You didn't say if your collection view has exactly 7 cells or if it can have "N" (e.g. 100) cells in the collection, so if this were my problem and I had to solve it, I would make the state of your "seven" cell a property of the class (e.g. "var sevenState : Bool") and then I could display the button or image of other cells depending on what sevenState is.

In my app I have to configure a UICollectionReusableView based on the index path, if the indexPath has a particular value then I send an array which is used to set labels and images.
I use a function in the custom UICollectionReusableView, if I call it with an array it populates the labels and images and if I call it with nil it resets these.
func collectionView(collectionView: UICollectionView!, viewForSupplementaryElementOfKind kind: String!, atIndexPath indexPath: NSIndexPath!) -> UICollectionReusableView! {
.... [logic around selecting index path based on data returned]
....
if filteredEvents != nil{
reusableView.populateCalendarDayDates(sortedEvents)
}else{
reusableView.populateCalendarDayDates(nil)
}
In the function in the custom UICollectionReusableView I reset labels back to default values before possibly updating them :
func populateCalendarDayDates(arrayEvents: NSArray?){
let firstDayTag = tagStartDay()
var dayDate = 1
for var y = 1; y < 43; y++ {
let label = self.viewWithTag(y) as! BGSCalendarMonthLabel
label.delegate = callingCVC
label.backgroundColor = UIColor.clearColor()
label.textColor = UIColor.whiteColor()
label.text = ""
You can get the same effect, and it is probably a bit more readable, by moving this code to prepareForReuse in the custom UICollectionReusableView :
override func prepareForReuse() {
super.prepareForReuse()
for var y = 1; y < 43; y++ {
let label = self.viewWithTag(y) as! BGSCalendarMonthLabel
label.backgroundColor = UIColor.clearColor()
label.textColor = UIColor.whiteColor()
label.text = ""
}
}
Hope that helps.

Related

Collection view cells duplicate when scrolling

I've got a collection view and I've got a custom class for the cells. Each cell contains a text view, here is the code:
class CustomWriterPageCell: UICollectionViewCell, UITextViewDelegate {
fileprivate let textViewOne: UITextView = {
let tv = UITextView()
tv.backgroundColor = .cyan
tv.text = "Chapter Title"
tv.font = UIFont(name: "Avenir-Roman", size: 27)
tv.textColor = .gray
return tv
}()
}
Here is the cellForItemAt:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "WriterPageCellID", for: indexPath) as! CustomWriterPageCell
cell.backgroundColor = .clear
return cell
}
The text view has a placeholder that I've achieved through the following code (This is inside the custom cell class):
func textViewDidBeginEditing(_ textView: UITextView) {
if textView.textColor == .gray {
textView.text = nil
textView.textColor = .black
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.isEmpty {
textView.textColor = .gray
textView.text = "Chapter Title"
}
}
The problem is that, whatever I type on the text view of the first cell, appears on the 4th cell, I know that this is happening because of dequeueReusableCell but I can't seem to solve this problem.
The problem is that you don't specify what text to appear on each of your CollectionViewCell's textView. As long as you don't specify the same in cellForItemAt indexPath it is going to show the reused cell and its content, from dequeueReusableCell as you said.
For the solution to your specific problem you can do as below in the viewcontroller:
`var textViewContentArray = [Int: String]()` //Create a dictionary to hold the texts globally
In textViewDidEndEditing:
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.isEmpty {
textView.textColor = .gray
textView.text = "Chapter Title"
} else {
guard let cell = textView.superview?.superview as? CustomWriterPageCell else {
return
}
guard let indexPath = self.collectionView.indexPath(for: cell) else { return }
textViewContentArray[indexPath.row] = textView.text
}
}
In textViewDidBeginEditing:
func textViewDidBeginEditing(_ textView: UITextView) {
textView.textColor = .black
}
And in cellForItemAt indexPath:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomWriterPageCell", for: indexPath) as! CustomWriterPageCell
cell.textView.delegate = self
let text = textViewContentArray[indexPath.row]
if !text.isEmpty {
cell.textView.textColor = .black
cell.textView.text = text
} else {
cell.textView.textColor = .gray
cell.textView.text = "Chapter Title"
}
return cell
}
Note: Here I am assuming you have set the Controller which holds the collectionView as delegate for the textViewInCell, if the cell is the delegate you can update the textViewContentArray using protocol
Hope this adds to your understanding.
I am assuming that 4th cell does not fit in current view (i.e available below on scroll - i.e Reused cell)
1) If you are reusing cell then you need to ensure that before cell goes out of view you need to save UITextView data somewhere. Otherwise it will lose data (i.e text written) due to reuse. Save data for each cell in data structure/any form.
2) If you scroll to 4th cell then check if data exists. If does not then set empty state to text view. If you write something then save data for 4th cell in data structure/any form..
(You can use this textViewDidEndEditing() to save current data)
3) Again scroll back to 1st cell type something there are save data of 1st cell data structure/any form for that index.
4) Now scroll back to 4th cell. During reuse set saved data of 4th cell to cell text view.
Data Saved at Index == Cell Index
Make sure you handle your use case correctly. There is nothing wrong with the reuse implementation. Make sure you save data and reset data correctly.
I suggest that the part you want to change is encapsulated into a model, such as the textColor, text you mentioned. Don't manually change the UI display, I mean by modifying the model and then refreshing the UI, so that's not There will be repetitions.
This is the rule for all tables, modify the model to refresh the UI, I hope to be useful to you.

Weird behavior when scrolling and selecting in UICollectionView

I am having issues with displaying a checkmark on the a custom cell in a UICollectionView. For the first few taps everything works as expected, but when I begin scrolling or tapping repeatedly or click on the already selected cell, the behavior becomes odd as shown in the gif. Perhaps I am going about this in an incorrect way? The .addCheck() and .removeCheck() are methods inside the custom UICollectionViewCell class I made and all they do is add a checkmark image or remove one from the cell view. The odd behavior shown here
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! ColorUICollectionViewCell
// Configure the cell
let color = colorList[(indexPath as NSIndexPath).row]
cell.delegate = self
cell.textLabel.text = color.name
cell.backgroundColor = color.color
if color.selected {
cell.addCheck()
}
else {
cell.removeCheck()
}
return cell
}
// user selects item
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// set colors to false for selection
for color in colorList {
color.selected = false
}
// set selected color to true for selection
let color = colorList[indexPath.row]
color.selected = true
settings.backgroundColor = color.color
//userDefaults.set(selectedIndex, forKey: "selectedIndex")
collectionView.reloadData()
}
Below is what the addCheck() and removeCheck() functions in my custom cell look like.
func addCheck() {
// create check image
let checkImage = UIImage(named: "checkmark")
checkImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: bounds.size.height / 4, height: bounds.size.height / 4))
checkImageView.image = checkImage!.withRenderingMode(UIImageRenderingMode.alwaysTemplate)
checkImageView.tintColor = UIColor.white()
// add the views
addSubview(checkImageView)
}
func removeCheck() {
if checkImageView != nil {
checkImageView.removeFromSuperview()
}
}
first off, you can simplify your didSelect a bit:
override func collectionView(collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// set colors to false for selection
for (index, color) in colorList.enumerate() {
if index == indexPath.row {
color.selected = false
settings.backgroundColor = color.color
}
else {
color.selected = false
}
}
collectionView.reloadData()
}
Based on the language in your cellForItemAt method, I'm guessing you're adding a second check mark image when you tap on the same cell twice, and it's not being tracked properly so that cell just keeps getting rotated around overtime the collectionView's reloaded
Post your cell class, or at least the logic for addCheck and removeCheck and we might find the problem.
What I would recommend is permanently having an imageView with the check mark over the cell, when simple show/hide it based on the selection. This should speed up the collectionView as well.

UIStepper inside UITableViewCell Goes Problematic When Tapped

I need to present a UIStepper in a row of a UITableView(only the second row - see the image below).
Therefore I implemented func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell like below:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("OptionCell")!
let debugModeOptionType = DebugModeOptionsType(rawValue: indexPath.row)
switch(debugModeOptionType!) {
case .DummyCurrentLocation:
cell.textLabel!.text = "Dummy current location"
case .StepLength:
cell.textLabel!.text = "Step Length: \(stepLength)"
// create a UIStepper
let stepper = UIStepper(frame: CGRectMake(220, 10, 100, 10))
// customize UIStepper
stepper.autorepeat = true
stepper.value = stepLength
stepper.minimumValue = 0.1
stepper.stepValue = 0.02
stepper.maximumValue = 1.5
stepper.addTarget(self, action: #selector(adjustStepLength(_:)), forControlEvents: UIControlEvents.AllEvents)
// add UIStepper into the cell
stepper.translatesAutoresizingMaskIntoConstraints = false
cell.contentView.addSubview(stepper)
case .TrueHeading:
cell.textLabel?.text = "True Heading: \(trueHeading)"
case .MagneticHeading:
cell.textLabel?.text = "Magnetic Heading: \(magneticHeading)"
case .HeadingAccuracy:
cell.textLabel?.text = "Heading Accuracy: \(headingAccuracy)"
case .CurrentDirection:
cell.textLabel?.text = "Current Direction: \(currentDirection)"
case .DrawWalking:
cell.textLabel?.text = "Draw walking while navigating"
}
if selectedDebugModeOptions.contains(debugModeOptionType!) {
cell.accessoryType = UITableViewCellAccessoryType.Checkmark
} else {
cell.accessoryType = UITableViewCellAccessoryType.None
}
return cell
}
However when I touch the UIStepper on a real device(this does not happen inside the simulator) the following happens:
When this happens, the other cells' UISteppers start flashing as well. Why does such a problem occurs?
I can't say why this happening only on a real device, but because table view cells are reused, you have to be careful when adding elements to the cells programmatically, because those elements (such as your stepper) will be spread to other cells as the cells are reused.
There are (at least) two ways you can deal with this:
Check for the presence of a stepper after you dequeue a reusable cell and remove it if it is on a row that doesn't need a stepper. You could do this by giving the stepper a unique tag number (such as 123) and then search for subviews with that tag and remove them.
let stepperTagNumber = 123
let cell = tableView.dequeueReusableCellWithIdentifier("OptionCell")!
let debugModeOptionType = DebugModeOptionsType(rawValue: indexPath.row)
if let stepper = cell.contentView.viewWithTag(stepperTagNumber) {
// We have a stepper but don't need it, so remove it.
if debugModeOptionType != .StepLength {
stepper.removeFromSuperview()
}
} else {
// We don't have a stepper, but need one.
if debugModeOptionType == .StepLength {
// create a UIStepper
let stepper = UIStepper(frame: CGRectMake(220, 10, 100, 10))
stepper.tag = stepperTagNumber // This is key, don't forget to set the tag
// customize UIStepper
stepper.autorepeat = true
stepper.value = stepLength
stepper.minimumValue = 0.1
stepper.stepValue = 0.02
stepper.maximumValue = 1.5
stepper.addTarget(self, action: #selector(adjustStepLength(_:)), forControlEvents: UIControlEvents.AllEvents)
// add UIStepper into the cell
stepper.translatesAutoresizingMaskIntoConstraints = false
cell.contentView.addSubview(stepper)
}
}
OR:
Create a second prototype cell for your tableview (called "OptionCellWithStepper"). Add the stepper to that cell in your storyboard. Then, when you call dequeueReusableCellWithIdentifier, use "OptionCellWithStepper" for case .StepLength and use identifier "OptionCell" for all the other cases. Doing it this way, you don't have to programmatically add the stepper, and you don't have to remember to remove it for the other cells.
let debugModeOptionType = DebugModeOptionsType(rawValue: indexPath.row)
let cellID = (debugModeOptionType == .StepLength) ? "OptionCellWithStepper" : "OptionCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellID)!

Swift / Array of Images in UIImage only last Image is displayed multiple times

I have the following code:
var posts = [EventPosts]() {
didSet {
eventsCollectionView.reloadData()
}
}
//MARK:PLACEHOLDER IMAGES
var eventImagesPlaceholder: [UIImage] = [
UIImage(named: "wildstyle.jpg")!,
UIImage(named: "geilesleben.jpg")!]
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let event = posts[indexPath.row]
let cell = eventsCollectionView.dequeueReusableCellWithReuseIdentifier("eventsCell", forIndexPath: indexPath) as! EventsCollectionViewCell
for element in eventImagesPlaceholder {
cell.eventsImageView.image = element
}
if Reachability.isConnectedToNetwork() == true {
offlineModusLabel.hidden = true
let imgURL = NSURL(string: event.imageUrl)!
cell.eventsImageView.sd_setImageWithURL(imgURL)
activityIndicator.stopAnimating()
} else {
offlineModusLabel.hidden = false
}
return cell
}
If I turn the internet on, four images from the desired source will be parsed and correctly displayed. If I turn the internet off, just the last placeholder image will be displayed 4 times. If I set wildstyle.jpg last, it is displayed four times. If I set geilesleben.jpg last, only that one is.
How can I display BOTH placeholder images. optimally only one time each.
Help is very appreciated.
You could use this, the two placeholder images are displayed in all cells alternately.
If you add more placeholder images to the array, the number of items will be considered automatically.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let event = posts[indexPath.row]
let cell = eventsCollectionView.dequeueReusableCellWithReuseIdentifier("eventsCell", forIndexPath: indexPath) as! EventsCollectionViewCell
if Reachability.isConnectedToNetwork() == true {
offlineModusLabel.hidden = true
let imgURL = NSURL(string: event.imageUrl)!
cell.eventsImageView.sd_setImageWithURL(imgURL)
activityIndicator.stopAnimating()
} else {
offlineModusLabel.hidden = false
cell.eventsImageView.image = eventImagesPlaceholder[indexPath.row % eventImagesPlaceholder.count]
}
return cell
}
In your code always the last image is displayed because of the repeat loop which assigns all images to the same image view and keeps the last image.
Add
override func prepareForReuse() {
self.eventsImageView.image = nil
}
in your cell's class
for element in eventImagesPlaceholder {
cell.eventsImageView.image = element
}
change this loop statment to like this
let element = eventImagesPlaceholder.objectAtIndex(indexPath.row) as! UIImage
cell.eventsImageView.image = element
for element in eventImagesPlaceholder {
cell.eventsImageView.image = element
}
This iterates for every cell you have. Basically every cell gets every image and keeps the last. pretty straight forward
What you want to do is sth like this:
cell.eventsImageView.image = eventImagesPlaceholder[someKindOfIndex]
Instead of that for loop.
Like when you know you have 4 cells and 4 images in your placeholder variable, just take indexPath.row as "someKindOfIndex"

Identify unique CollectionViewCells

I want each CollectionViewCell to show an image and hide a label if it is tapped. But if the user scrolls the image suddenly is displayed in other cess that haven't been touched. How can I identify certain cells?
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
println("user tapped on door number \(indexPath.row)")
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! MyCollectionViewCell
if (cell.myLabel.text == "1") {
one = true
if (cell.myLabel.hidden) {
cell.myLabel.hidden = false
cell.MyImageView.image = nil
}
else {
cell.myLabel.hidden = true
cell.MyImageView.image = UIImage(named:"1")!
}
}
That's because cells are reused so instead of changes each cell, change the data source at the index of the cell that was tapped.

Resources