dequeueReusableCell causes text to change color - ios

I've got a collection view and I'm using a custom class for the cells. Each cell has two text view, chapterTitleTextView and chapterBodyTextView. I've added placeholders to both text views like so:
class CustomWriterPageCell: UICollectionViewCell, UITextViewDelegate {
// When the user taps on a text view
func textViewDidBeginEditing(_ textView: UITextView) {
if textView.textColor == .gray {
textView.text = nil
textView.textColor = .black
}
}
// When the user taps out of a text view or taps on the done button in the toolbar
func textViewDidEndEditing(_ textView: UITextView) {
// If the chapter title text view is empty
if chapterTitleTextView.text.isEmpty {
chapterTitleTextView.text = "Chapter Title"
chapterTitleTextView.textColor = .gray
}
// If the chapter body text view is empty
if chapterBodyTextView.text.isEmpty {
chapterBodyTextView.text = "Chapter Body"
chapterBodyTextView.textColor = .gray
}
}
}
How this works is that, the text color is initially gray and there is some text, when the user taps on the text view, the color changes to black and the text in the text view is removed.
Now there is this problem with using dequeueReusableCell, it’s that it reuses the cells, this caused problem number 1: Whatever I type on the text view in the first cell appears on the 4th cell, to solve this problem I had to create 2 global lists to hold whatever I type and this gets displayed in the text views of the cells, here’s the code:
Global Lists:
var chapterTitleText = Array(repeating: "", count: 4) // To store the chapter title text
var chapterBodyText = Array(repeating: "", count: 4) // To store the chapter body text
The following code snippet is inside the textViewDidEndEditing from earlier
// Append the chapter body text to the chapterBodyText array
let titleText = chapterTitleTextView.text
let titleRow = textView.tag //This the indexPath.row
chapterTitleText[titleRow] = titleText!
// Append the chapter title text to the chapterTitleText array
let bodyText = chapterBodyTextView.text
let bodyRow = textView.tag
chapterBodyText[bodyRow] = bodyText!
And in cellForItemAt:
cell.chapterBodyTextView.tag = indexPath.row
cell.chapterTitleTextView.tag = indexPath.row
cell.chapterTitleTextView.text = chapterTitleText[indexPath.row]
cell.chapterBodyTextView.text = chapterBodyText[indexPath.row]
This got rid of problem number 1 (text in the text views duplicating). But then I got a new problem, remember the placeholder text I was talking about? When I type something in the one of the textviews of the first cell, the text color of the text view in the fourth cell changes.
Here is a GIF replicating the problem:

Following your chain of questions, I suggest you to do this every time you're dealing with UICollectionView or UITableView.
Define a method in your cell class and take whatever data it needs to display itself as arguments:
func configure(text : String?) { //text is optional here which means it can be nil.
//However, from my previous answer you can replace it with an empty string condition.
if let txt = text { //or if text != ""
self.chapterTitleTextView.text = txt
self.chapterTitleTextView.text.textColor = .black
}
else {// As others mentioned make sure to always handle else because cells are reusable.
self.chapterTitleTextView.text = "Chapter Title"
self.chapterTitleTextView.text.textColor = .gray
}
}
The intuition behind reusable cells are that since they are reusable, you should reconfigure them completely and not to expect that the configuration is saved or attached to the cell.
Now, in the cellForItemAt:
let cell = ...
cell.configure(text : chapterTitleText[indexPath.row])
And remember, in this way, you do not need to define a global array. As I've told you previously, this array need to be only defined in your Controller and your cell does not need to know about it. Your cell only needs to know about one index of that array which is passed through configure function.
Although that global array will work, I'm talking about propriety in coding.
Comment your problems with this approach(if any), I will try to answer with patience but don't expect a (copy-paste)able code.

This happens because of the reuse mechanism of the UICollectionView and UITableView. Since you only update the color in one way, collection "remembers" previous color and upon new cell recreates its previous state. You have two basic solutions here.
First is to update cell state for both cases like:
if myCondition == true {
color = UIColor.gray
} else {
color = UIColor.black
}
Second is to utilize prepareForReuse method of the UICollectionViewCell
func prepareForReuse() {
// code that resets state of your cell to default
}
Method description in Apple Documentation

Related

How to retrieve value from a row with changed textfield with Eureka form?

I'm trying to retrieve the value of the typed field in a row of an Eureka form (https://eurekacommunity.github.io). I've set up a new textfield with a mask, but when I send it back it returns me empty.
TextRow:
<<< TextRow(Constants.CPF) {
$0.title = Constants.cpf_title
$0.placeholder = Constants.cpf_placeholder
}
.cellSetup { cell, _ in
let tf = JMMaskTextField(frame: cell.textField.frame)
tf.maskString = "000.000.000-00"
cell.textField.removeFromSuperview()
tf.translatesAutoresizingMaskIntoConstraints = false
cell.textField = tf
cell.contentView.addSubview(tf)
}
.cellUpdate { cell, _ in
cell.textField.delegate = self
}
Result:
let values = self.form.values()
print(values[Constants.CPF])
Optional(nil)
Apparently the mask is working properly, but I think I'm forgetting to set something up. Does anyone have any ideas?
If you want to use different UITextField in your row, you should create custom row for that. In this case, you can subclass from TextRow and implement you cell with JMMaskTextField. Just remove default textField and set the new (as you did) doesn't work.

My CustomTableViewCell's read more/less function crashes

Thanks for your attention.
I have an app that uses a UITableView as a timeline that shows certain events, The Cell prototype was a little complex because i use multiple labels, a button and some imageViews that automatically change in function of the content of other fields.
In that cell, I have a UILabel, this UILabel can have 140 characters or 4 line jumps, if the text inside the label have more line jumps (\n) or are longer that 140 chars, I take a fragment and only display that fragment and add the text "... READ MORE"; when the user taps on the text, the label change and shows all the text, and at the end, appends the label "READ LESS", If the user taps again the label, it return to the initial state showing the fragment and the label "READ MORE" and so.
When I test this, it works on a device with iOS9, but in devices with iOS 10 (including simulators) It stops to work; it appears that when I Tap the label, the label changes as usual, but immediately returns to their original form. I register only one tap.
Have an idea?, Here is my code that is called to update the cell when the user taps the text label:
func cellTextPressed(gesture: UITapGestureRecognizer){
let cell: TimeLineViewCell = gesture.view?.superview?.superview as! TimeLineViewCell
let tappedIndexPath: NSIndexPath = self.timelineTableView.indexPathForCell(cell)!
NSLog ("Text Tapped at: \(tappedIndexPath)")
if ((cell.isReasonExpanded) == true)
{
cell.isReasonExpanded = false
let attrs = [NSForegroundColorAttributeName: UIColor.magentaColor()]
let attributedReducedText = NSMutableAttributedString(string: cell.reducedReason)
attributedReducedText.appendAttributedString(NSAttributedString(string: "... "))
attributedReducedText.appendAttributedString(NSAttributedString(string: "READ MORE", attributes: attrs))
cell.labelReason.attributedText = attributedReducedText
}
else
{
cell.isReasonExpanded = true
let attrs = [NSForegroundColorAttributeName: UIColor.magentaColor()]
let attributedRealText = NSMutableAttributedString(string: cell.realReason)
attributedRealText.appendAttributedString(NSAttributedString(string: " "))
attributedRealText.appendAttributedString(NSAttributedString(string: "READ LESS", attributes: attrs))
cell.labelReason.attributedText = attributedRealText
}
let lastScrollOffset = self.timelineTableView.contentOffset
UIView.performWithoutAnimation
{
self.timelineTableView.beginUpdates()
self.timelineTableView.endUpdates()
self.timelineTableView.layer.removeAllAnimations()
self.timelineTableView.setContentOffset(lastScrollOffset, animated: false)
}
}
I already have a method to avoid this.In IOS 9 and older versions, there are no problem with that code; but in iOS 10 there is different.
In documentation, apple says that the delegate method tableView cellForRowAtIndexPath is called everytime when a cell is displayed on screen, for that cell. In the older versions of IOS, it appears to be a bug that make this situation don't work, let us modify the view outside of this method whitout callback cellForRowAtIndexPath after refresh the cell.
I Added an array of Bool values that represents the status of the labels (If it have extended or shrinked value of the label) and initialize it on the ItemsDownload Method of my JSON receiver method
I add a condition that evaluates the value in that stateArray in cellForRowAtIndexPath() and put the full or the fragment of the text as corresponds.
finally, in the method posted above, added the change of the value at index path to the status Array instead to change all in that method

Is there any way to grab the text and detail text labels of multiple selected table view cells in swift?

I have some code right now that gets what cells were selected. The following is my code for doing that:
if let selectedUserRows = self.tableView.indexPathsForSelectedRows {
self.groupUserArray.append(selectedUserRows)
for index in selectedUserRows {
let text = groupUserArray[index.row]
print(text)
}
}
The new error is saying that the index is out of range!
I was trying to use the logic for grabbing one selected cells text but it does not work. So I was wondering if anyone knew how to grab multiple selected cell's text?
Any help would be appreciated!
self.tableView.indexPathsForSelectedRows returns a [IndexPath]? so if you obtain an array of all the rows and sections of you selected rows. I suppose you are using some sort of collection to populate your tableView (in my example I will call it textArray: [String] ) so you can do something like that to get all the text you need:
if let selectedUserIndexes = self.tableView.indexPathsForSelectedRows {
for index in selectedUserIndexes {
let text = textArray[index.row]
}
}

Changing button color in cell if clicked

I'm trying to create like button in tableview cell for my project. If like button clicked it must change tint color to red. I'm using Alamofire and if user liked it returns wich feed is liked and in cell:
let likerHash = data[indexPath.row]["liker_hash"] as? String
if(likerHash == ""){
cell.likeButton.tintColor = UIColor.blackColor()
}else{
cell.likeButton.tintColor = UIColor.redColor()
}
will set color of buttons for feeds. But if i click one button to like if it's not liked it changes color and iwhen i scroll down and come back again button color will change again to previous. (if on loading data it is not liked it will keep color when i scroll down.) I have tride to change value of liker_hash but it gives me an error: mutating method sent to immutable object. I am tying to change value like:
self.data[sender.tag]["liker_hash"] = ""
My data is from type [NSMutableDictionary](). Any idea how can i do it in swift language?
The main feature with using dequeueReusableCellWithIdentifier is that the cell is recreated every time you come back to it.
Based on the code that you've added, I don't believe you're editing your data source when the user hits the like button to save which cell has been changed the next time the tableView reloads.
What I'd suggest is holding all the cells in an array. Supposed you have 5 cells, create an array holding their current state:
var cellArr = ["Black", "Black", "Black", "Black", "Black"]
Then, if the user selects the like button, make sure to update this array with the right color as well. So if I select the second row, I update it like:
var cellArr = ["Black", "Red", "Black", "Black", "Black"]
And then in your cellForRowAtIndexPath function:
if cellArr[indexPath.row] == "Black"{
cell.likeButton.tintColor = UIColor.blackColor() }
else {
cell.likeButton.tintColor = UIColor.redColor()
}

UITableView changing things of some cells in Swift

I have a UITableView with a custom UITalbeCellView. Every cell has a title a body and a date. When I fetch the data some of the titles must be red so there is a Bool that is set to true for those cells which title has to be red.
The first time the data is fetched it looks fine, but as you scroll up and down a few times all of the titles end up being red.
Im using an array of structures to store the data and on the cellForRowAtIndexPath there is an if and if the boolean in that position of the array is true I change the color to red:
let cell = tableView.dequeueReusableCellWithIdentifier("CommentsRowTVC", forIndexPath: indexPath) as! CommentsRowTVC
let single_comment = self.AOS[indexPath.row]
cell.titulo_comentario?.text = single_comment.titulo_comment
if single_comment.flag == true {
cell.titulo_comentario.textColor = UIColor.redColor()
}
Im gessing it reuses Views or something like that. Does this have any easy fix? Or do I have to implement my own TableView to prevent this from happening?
Im also thinking the method dequeueReusableCellWithIdentifier might be the one causing my problem... but Im new on swift and cant think of any replacement for that.
It is happening similar thing on buttons there are on the cells, when you click a button from a cell it changes its color and disables it, but just the one on that cell. However when clicking on a cell, buttons from other cells are also suffering those changes.
Since cells get reused you end up with cells that were previously red because they hadsingle_comment.flag == true and are now used for another row where there actually is single_comment.flag == false. You have to reset the color in the else branch again:
if single_comment.flag == true {
cell.titulo_comentario.textColor = UIColor.redColor()
} else {
cell.titulo_comentario.textColor = UIColor.whiteColor()
}
UITableViews reuse their cells, meaning that you will get cells inside your cellForRowAtIndexPath which have previously been used before, maybe have have set the textColor to red. Your job is it now to revert the color of the text to its original state.
In general whatever change you make inside the if-branch of cell-customization has to be undone in the else-branch. If you remove a label in the if, add it in the else.

Resources