I am trying to show some content in the cell of the UITableView. The program does reach the cellForRowAtIndexPath:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: EventTableViewCell = self.tableView.dequeueReusableCellWithIdentifier("eventCell") as! EventTableViewCell
let date = self.eventArray[indexPath.row].startTime
let calendar = NSCalendar.currentCalendar()
let minutes = calendar.component(NSCalendarUnit.Minute, fromDate: date)
var minutesString: String
if (minutes == 0) {
minutesString = "00"
} else {
minutesString = String(calendar.component(NSCalendarUnit.Minute, fromDate: date))
}
let hours = calendar.component(NSCalendarUnit.Hour, fromDate: date)
//this next line of code works, i see cell text:
// cell.textLabel?.text = self.eventArray[indexPath.row].title + " - \(hours):\(minutesString)"
//these lines do not work, see empty cells:
cell.cellLable?.text = self.eventArray[indexPath.row].title + " - \(hours):\(minutesString)"
cell.textField?.text = self.eventArray[indexPath.row].notes
return cell
}
I have properly connected the outlets:
If I set breakpoint, the cell appears to be the EventTableViewCell, but both cellLabel and textField are nil:
My table view connections look like this:
Connection inspector for the eventCell here:
I have also made Content View background color blue, but it seems like I don't see the whole Content view in my cell.
My custom cell class looks like this:
Check that you have the correct identifier for your cell in IB. Let cell... is returning nil so that appears to be your problem.
let cell: EventTableViewCell = self.tableView.dequeueReusableCellWithIdentifier("eventCell") as! EventTableViewCell
cell.textLabel works since you inherit the UITabelViewCell Class. You are getting a UITabelViewCell as reference - that is granted.
Create a strong reference to your label within the scope.
Place a breakpoint and validate what you are actually getting as cell. (indeed the correct subclass of yours or something generic.)
If the cell object is correct, but you don't get the label it must be outlet related.
In some cases in Swift I came upon the need to change the OUTLET for an UIElement from weak to strong.
If yes, only the properties of the label remain as cause. Give the label a background color. It's layout is actually visible? (eg.: hidden = NO, alpha = 1, frame/constrains render it visible and so on.)
Related
I have a tableview consisting of 2 cells and each cell has a textfield. I'd like to hold the text in textfield in an another variable and append it into an array but it just save the second cell's textfield's text into the variable and doesn't append the array.
Here is cellForRowAt code :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == queryTableView {
let cell = tableView.dequeueReusableCell(withIdentifier: queryCellReuseIdentifier,
for: indexPath) as! queryCell
cell.selectionStyle = UITableViewCell.SelectionStyle.gray
queryTextField = UITextField(frame: CGRect(x: 20, y: 0, width: 300, height: 20))
queryTextField.delegate = self
queryTextField.placeholder = "Soruyu buraya giriniz"
queryTextField.font = UIFont.systemFont(ofSize: 15)
cell.addSubview(queryTextField)
return cell
}
Here is my related function :
func textFieldDidEndEditing(_ textField: UITextField) {
var temp = queryTextField.text!
queryArray.append(temp)
print(temp)
}
if your project is non-storyboard approach, then I would say, it would be more easier and flexible to take control of each type and data and action since sometimes things not quite flexible with storyboard only.. . as you create, even a single UIElement, UI conforms to those properties, methods and actions which you define, means fully customized code - custom defined code base..
now to your question, according to what you did, probably, your textfield text is overwritten to blank, losing the previously entered value every time the cell is dequeued, cell is generated fresh again for use every time " dequeue cell " method is executed, if you want to persist your previous value, I would say, " remind the dequeued cell that it had some value before being re- dequeued again " means:
declare your global array or dict.
dequeue your cell and setup your cell properties
3 after cell is dequeued, do something like this, and your cells previous value is saved even after the cell is dequeued.
example, common approach:
struct DefaultInfo {
var string: String?
var type: SomeData? //optional
}
let array: [DefaultInfo] = []
var array = self.array[indexPath.row]
// after cell is dequeued:
switch array.type {
case .someCase:
let string = cell.textField.text
array.string = string
cell.textField.text = array.string
//print(array.string!)
// now you should be able to see your input after cell is
//renewed.. . try it.. .
default: break
}
It seems like you have an independant variable queryTextField which is overriden when you create the second cell.
Also, in textFieldDidEndEditing try accessing textField instead of queryTextField, like this:
func textFieldDidEndEditing(_ textField: UITextField) {
var temp = textField.text!
queryArray.append(temp)
print(temp)
}
Responding to this:
I need every cell has its textfield and I need to be able to save
their texts into an array. After that I am going to send them to web
service. To sum up, they will be my parameters.
You don't need to have global queryTextField just for that. You can remove this variable, if that's its only goal.
Do you need to send web request on some trigger? Like button tap. I assume, yes.
Since theoretically not all your cells are visible at the same time (e.g. when thay do not fit the screen), it's bad idea to try and track texts in text fields. Instead, you need some kind of model to store all texts (paired with indeces, maybe). The simplest would be dictionary where key is cell identifier (e.g. indexPath) and value is the text.
In textFieldDidEndEditing you can report the changes to the view controller. For this you need to assign the cell as the delegate for its text field. And view controller - as the delegate for the cell. In textFieldDidEndEditing cell would be calling view controller delegate method to report the text change, passing (for example) self and text as parameters. View controller would then be able to find its index path and store the text in the model (dictionary).
On trigger (button click?) view controller will be able to build parameters from the model (dictionary).
Consider this pseudocode as a direction:
cellForRow {
...
cell = ...
let textField = ...
textField.delegate = cell
cell.addSubview(textField)
cell.delegate = self
}
In cell:
textFieldDidEndEditing(textField: UITextField) {
delegate?.report(text: textField.text ?? "", in: self)
}
In view controller:
report(text: String, in cell: UITableViewCell) {
let indexPath = tableView.indexPath(of: cell)
model[indexPath] = text
}
First let me say this seems to be a common question on SO and I've read through every post I could find from Swift to Obj-C. I tried a bunch of different things over the last 9 hrs but my problem still exists.
I have a vc (vc1) with a collectionView in it. Inside the collectionView I have a custom cell with a label and an imageView inside of it. Inside cellForItem I have a property that is also inside the the custom cell and when the property gets set from datasource[indePath.item] there is a property observer inside the cell that sets data for the label and imageView.
There is a button in vc1 that pushes on vc2, if a user chooses something from vc2 it gets passed back to vc1 via a delegate. vc2 gets popped.
The correct data always gets passed back (I checked multiple times in the debugger).
The problem is if vc1 has an existing cell in it, when the new data is added to the data source, after I reload the collectionView, the label data from that first cell now shows on the label in new cell and the data from the new cell now shows on the label from old cell.
I've tried everything from prepareToReuse to removing the label but for some reason only the cell's label data gets confused. The odd thing is sometimes the label updates correctly and other times it doesn't? The imageView ALWAYS shows the correct image and I never have any problems even when the label data is incorrect. The 2 model objects that are inside the datasource are always in their correct index position with the correct information.
What could be the problem?
vc1: UIViewController, CollectionViewDataSource & Delegate {
var datasource = [MyModel]() // has 1 item in it from viewDidLoad
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: customCell, for: indexPath) as! CustomCell
cell.priceLabel.text = ""
cell.cleanUpElements()
cell.myModel = dataSource[indexPath.item]
return cell
}
// delegate method from vc2
func appendNewDataFromVC2(myModel: MyModel) {
// show spinner
datasource.append(myModel) // now has 2 items in it
// now that new data is added I have to make a dip to fb for some additional information
firebaseRef.observeSingleEvent(of: .value, with: { (snapshot) in
if let dict = snapshot.value as? [String: Any] else { }
for myModel in self.datasource {
myModel.someValue = dict["someValue"] as? String
}
// I added the gcd timer just to give the loop time to finish just to see if it made a difference
DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
self.datasource.sort { return $0.postDate > $1.postDate } // Even though this sorts correctly I also tried commenting this out but no difference
self.collectionView.reloadData()
// I also tried to update the layout
self.collectionView.layoutIfNeeded()
// remove spinner
}
})
}
}
CustomCell Below. This is a much more simplified version of what's inside the myModel property observer. The data that shows in the label is dependent on other data and there are a few conditionals that determine it. Adding all of that inside cellForItem would create a bunch of code that's why I didn't update the data it in there (or add it here) and choose to do it inside the cell instead. But as I said earlier, when I check the data it is always 100% correct. The property observer always works correctly.
CustomCell: UICollectionViewCell {
let imageView: UIImageView = {
let iv = UIImageView()
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
let priceLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var someBoolProperty = false
var myModel: MyModel? {
didSet {
someBoolProperty = true
// I read an answer that said try to update the label on the main thread but no difference. I tried with and without the DispatchQueue
DispatchQueue.main.async { [weak self] in
self?.priceLabel.text = myModel.price!
self?.priceLabel.layoutIfNeeded() // tried with and without this
}
let url = URL(string: myModel.urlStr!)
imageView.sd_setImage(with: url!, placeholderImage: UIImage(named: "placeholder"))
// set imageView and priceLabel anchors
addSubview(imageView)
addSubview(priceLabel)
self.layoutIfNeeded() // tried with and without this
}
}
override func prepareForReuse() {
super.prepareForReuse()
// even though Apple recommends not to clean up ui elements in here, I still tried it to no success
priceLabel.text = ""
priceLabel.layoutIfNeeded() // tried with and without this
self.layoutIfNeeded() // tried with and without this
// I also tried removing the label with and without the 3 lines above
for view in self.subviews {
if view.isKind(of: UILabel.self) {
view.removeFromSuperview()
}
}
}
func cleanUpElements() {
priceLabel.text = ""
imageView.image = nil
}
}
I added 1 breakpoint for everywhere I added priceLabel.text = "" (3 total) and once the collectionView reloads the break points always get hit 6 times (3 times for the 2 objects in the datasource).The 1st time in prepareForReuse, the 2nd time in cellForItem, and the 3rd time in cleanUpElements()
Turns out I had to reset a property inside the cell. Even though the cells were being reused and the priceLabel.text was getting cleared, the property was still maintaining it's old bool value. Once I reset it via cellForItem the problem went away.
10 hrs for that, smh
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: customCell, for: indexPath) as! CustomCell
cell.someBoolProperty = false
cell.priceLabel.text = ""
cell.cleanUpElements()
cell.myModel = dataSource[indexPath.item]
return cell
}
I have a monthly calender collection view and a xml that I am parsing to get some dates. I am trying to show these dates with borders on the calendarview but am running into an issue that I'm not sure how to debug - dates that are not from the xml also show up with borders around them and then random change when you scroll to the next month and back, like this - https://imgur.com/a/mxyUtJe
Here is my code for settings borders right now. Is there anything visibly wrong or missing
//Mark: Configure Calendar Cell
func configureCalendarCell(cell: JTAppleCell?, cellState: CellState) {
guard let myCustomCell = cell as? CalendarCell else { return }
//configure functions including:
getEventDates(cell: myCustomCell, cellState: cellState)
}
//Mark: set events from XML == selected cell from Calendar
func getEventDates(cell: CalendarCell, cellState: CellState) {
for calendarDate in tableViewDataSource {
let datesFromCalendarXML = calendarDate.date
//print("Found \(datesFromCalendarXML)") This prints dates parsed from XML file - See picture 1 below
let visibleDatesToString = formatter.string(from: cellState.date)
//print("Visible dates \(visibleDatesToString)") This prints all of the dates in the current month showing. So for September, it shows all of the dates in September. See picture 2 below
// If there is a date match, then show a round blue border
if datesFromCalendarXML == visibleDatesToString {
cell.dateLabel.layer.cornerRadius = 25
cell.dateLabel.layer.borderWidth = 3
cell.dateLabel.layer.borderColor = UIColor.blue.cgColor
cell.dateLabel.clipsToBounds = true
// If there is no date match, show no border. I had set this to UIColor.clear.cgColor but then only the last datesFromCalendarXML Oct 30 showed as a blue, so I changed it to red and saw that random dates including the ones from the XML are being circled red.
} else if datesFromCalendarXML != visibleDatesToString {
cell.dateLabel.layer.borderColor = UIColor.red.cgColor
cell.dateLabel.clipsToBounds = true
}
}
}
}
//Mark: CalendarView Delegate
extension CalendarViewController: JTAppleCalendarViewDelegate {
func calendar(_ calendar: JTAppleCalendarView, cellForItemAt date: Date, cellState: CellState, indexPath: IndexPath) -> JTAppleCell {
let cell = calendar.dequeueReusableJTAppleCell(withReuseIdentifier: "CalendarCell", for: indexPath) as! CalendarCell
cell.dateLabel.text = cellState.text
configureCalendarCell(cell:cell, cellState: cellState)
return cell
}
}
It seems this only works for the last value in datesFromCalendarXML as Oct 30 shows u pwith a blue ring. The other values in the XML, Sept 1, 10, 20 and Oct 1, 10, 20 show up with red rings as do other random dates.
In my console for //print("Found (datesFromCalendarXML)" - https://i.stack.imgur.com/dYlYL.png
In my console for //print("Visible dates (visibleDatesToString)") - https://i.stack.imgur.com/sbRtA.png
Added:
I tried to just change the color of the datelabel to green and for whatever reason, that works to make only the matched dates green and does not add random other green dates. I have no idea why I can change the dateLabel.textColor and not the background - https://imgur.com/a/St3b8ND
if datesFromCalendarXML == visibleDatesToString {
cell.dateLabel.textColor = UIColor.green
} else if datesFromCalendarXML != visibleDatesToString {
}
The problem is your logic
for calendarDate in tableViewDataSource {}
The calendarDate always loop to fill the red background
pretend you in date 10, and your xml is : 10,20,30
When the date 10 check, it loop to fill it background blue, then red, then red
So use break inside if datesFromCalendarXML == visibleDatesToString {} so it will stopped after fill blue backgrund
In configureCalendarCell you are creating cell everytime. Instead of that you can use dequeueReusableCell, which actually reuses your created cell and just update new data to it. This is the main advantage of a CollectionView.
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TDCheckMarkLabelCollectionViewCell", for: indexPath)
First you have to set unique identifier for your cell in storyboard or XIB.
One of the other answers almost got it... You are using dequeueReusableCell as you are supposed to. However, you forgot or didn't realize that this means you are reusing cells! That means that sometimes you get a cell that has already been setup to display a border and using it to display a cell that isn't supposed to display a border. Since your function assumes that the cell doesn't have a border, it does nothing to the cell, and since the cell already has a border, it displays with a border.
There are two ways to fix this.
Inside your cell you can override the prepareForReuse method and remove any circle that may exist on the cell.
Finish the logic in your getEventDates function. Your logic doesn't say what should happen to a cell that shouldn't have either a red or a blue border, but happens to have one currently.
I have a simply project with a tableView and a detail vc. The tableView displays 20 rows with "cell (n)" text and the detail view shows a label with the cell pressed.
I want to assert given a tap into a cell i get the text found in the tableView in the detail vc label. So for instance if i tap of the cell 3, which contains "cell 3" i want to get this text, instead of hardcoding it, and assert that i can find this text in the detail vc.
func testCanNavigateToDetailVCWithTheTextFromCell() {
let labelInTableView = app.staticTexts["cell 3"]
labelInTableView.tap()
let labelInDetailVC = app.staticTexts[labelInTableView.label]
XCTAssertTrue(labelInDetailVC.exists)
}
This seems working. But i want to do this:
func testCanNavigateToDetailVCWithTheTextFromCellV2() {
let cell = app.tables.element.cells.element(boundBy: 3) //Get the third cell of the unique tableView
cell.tap()
let textFromPreviousCell = cell.staticTexts.element(boundBy: 0).label //Since is a "Basic" cell it only has one label.
//I will also want to set an accessilibtyIdentifier to the label and access it via cell.staticTexts["id"].label
let labelInDetailVC = app.staticTexts[textFromPreviousCell]
XCTAssertTrue(labelInDetailVC.exists)
}
I set up a project with this issue here
The problem is that you are trying to get the text of the cell after tapping it. This means that the cell is no longer on the screen (the new screen has appeared). All you need to do is change the order of the lines cell.tap() and let textFromPreviousCell = cell.staticTexts.element(boundBy: 0).label. See the new function below:
func testCanNavigateToDetailVCWithTheTextFromCellV2() {
let cell = app.tables.element.cells.element(boundBy: 3) //Get the third cell of the unique tableView
let textFromPreviousCell = cell.staticTexts.element(boundBy: 0).label //Since is a "Basic" cell it only has one label.
cell.tap()
let labelInDetailVC = app.staticTexts[textFromPreviousCell]
XCTAssertTrue(labelInDetailVC.exists)
}
I tried to copy only the necessary code to show my problem. I have a tableview with dynamic content. I created a prototype cell and it has a user name and 10 stars (it's a rating page). People in the group are allowed to rate other people. Everything is working ok, but I have a problem when I scroll down. If I rate my first user with 8 stars, when I scroll down then some user that was in the bottom area of the tableview, appears with the rate that I gave to my first user. I know that tableview reuse cells. I tried many things but with no success. Hope someone can help me on that.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let model = users[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier("RatingCell") as! RatingTableViewCell
cell.tag = indexPath.row
cell.playerLabel.text = model.name
cell.averageView.layer.borderWidth = 1
cell.averageView.layer.borderColor = Color.Gray1.CGColor
cell.averageView.layer.cornerRadius = 5
cell.starsView.userInteractionEnabled = true
cell.averageLabel.text = "\(user.grade)"
for i in 0...9 {
let star = cell.starsView.subviews[i] as! UIImageView
star.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(starTap)))
star.userInteractionEnabled = true
star.tag = i
star.image = UIImage(named: (i + 1 <= grade ? "star-selected" : "star-empty"))
}
return cell
}
func changeRating(sender: UIImageView) {
let selectedStarIndex = sender.tag
let cell = sender.superview?.superview?.superview as! RatingTableViewCell
let model = users[cell.tag]
let stars = sender.superview?.subviews as! [UIImageView]
cell.averageLabel.text = "\(selectedStarIndex + 1)"
for i in 0...9 {
let imgName = i <= selectedStarIndex ? "star-selected" : "star-empty"
stars[i].image = UIImage(named: imgName)
}
}
func starTap(gesture: UITapGestureRecognizer) {
changeRating(gesture.view as! UIImageView)
}
The way to solve this problem is by updating the model that holds all the information for the uitableviewcell. Whenever a rating is updated fora particular cell, make sure you reflect that update in the respective object / dictionary in an array. Furthermore, if you have a customuitableviewcell, it might be a good idea to reset the stars in the "prepareForUse" function, so that way when a cell is reused it doesn't use old data.
In your comments, you said that you have an array with selected rates.But you did not show that in your code.
In my opinion, you need record indexPath too, because indexPath.row is binding with your rate data(may be grade?).The best way to do so is that #Jay described up.And you should not write the code of configuring cell data and cell's logic in your view controller.If your business logic is complex, you will find that it is a nightmare.^=^