Why is the cell not resizing with its content? - ios

I have read all of the similar questions I could find here and applied all of the proposed solutions ... none of them works.
The table view is a grouped-one, the number of lines for the textLabel and the detailTextLabel is set to 0 both in code and in the storyboard, the string data is made of a multiline string created via "\n" characters...
Setting tableView.estimatedRowHeight to something else rather than the automatic 43.5 and then tableView.rowHeight = UITableView.automaticDimension as suggested in some other questions does absolutely nothing... or if I put something like 100, it will resize all the cells instead of just the ones which have multiple lines of text.
I am unfamiliar with this type of behaviour from table views: in my experience, simply putting the number of lines to 0 for the labels just worked...
Why do I get this result?
EDIT: I have also tried to implement the heightForRowAt method, passing in the desired sections and rows but nothing at all changed ... I am lost.
EDIT (2): Removed previous snippet and inserted ViewController source code: the only thing I can show that is relevant to the cell is this, as the only property is an instance of a custom data type that is populating the table view (and it is being very good at that) and viewDidLoad() has nothing of mine added inside of it.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.numberOfLines = 0
cell.detailTextLabel?.numberOfLines = 0
func configureTextLabels(withString labelText: String, and detailText: String) {
cell.textLabel?.text = labelText
cell.detailTextLabel?.text = detailText
}
if let country = detailCountry {
switch indexPath.section {
// insert 14 different cases here
case 14:
func populateRows() {
let regionalBlocs = country.regionalBlocs
for i in 0 ..< regionalBlocs.count {
switch indexPath.row {
case 0 + (i * 4): configureTextLabels(withString: "Name: ", and: "\(regionalBlocs[i].name)")
case 1 + (i * 4): configureTextLabels(withString: "\tAcronym: ", and: "\(regionalBlocs[i].acronym)")
case 2 + (i * 4):
var detailText = ""
let otherNames = regionalBlocs[i].otherNames
if !otherNames.isEmpty {
for name in otherNames {
if name == otherNames.last {
detailText += name
} else {
detailText += "\(name)\n"
}
}
configureTextLabels(withString: "\tOther names: ", and: detailText)
} else {
configureTextLabels(withString: "", and: "No other names found")
}
case 3 + (i * 4):
var detailText = ""
let otherAcronyms = regionalBlocs[0].otherAcronyms
if !otherAcronyms.isEmpty {
for acronym in otherAcronyms {
if acronym == otherAcronyms.last {
detailText += acronym
} else {
detailText += "\(acronym)\n"
}
}
configureTextLabels(withString: "\tOther acronyms: ", and: detailText)
} else {
configureTextLabels(withString: "", and: "No other acronym found")
}
default: break
}
}
}
let regionalBlocs = country.regionalBlocs
if regionalBlocs.isEmpty {
cell.textLabel?.text = ""
cell.detailTextLabel?.text = "No regional bloc data found for this country"
} else {
populateRows()
}
}
return cell
}

You can look answer in here: Add multiple lines to detailTextLabel in UITableViewCell
But I suggest you create custom cell for this case, your issue is UIKit's bug.

Related

Tableview need to be reloaded twice to update the data from textfield?

I have question about the tableView.
Here is my tableView code
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tierCount
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "InterestRateTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? InterestRateTableViewCell else {
fatalError("The dequed cell is not an instance of InterestRateTableViewCell.")
}
cell.interestRateTextField.delegate = self
cell.rowLabel.text = "\(indexPath.row + 1)."
if let interestText = cell.interestRateTextField.text {
if let interest = Double(interestText){
interestRateArray[indexPath.row] = interest
} else {
interestRateArray[indexPath.row] = nil
}
} else {
interestRateArray[indexPath.row] = nil
}
return cell
}
As you can see, I have the cellForRowAt method to get the value from the textfields in the cell, and assign to my arrays. (I actually have 2 textfields per cell.)
Basically, I let the users input and edit the textfield until they are happy then click this calculate button, which will call the calculation method. In the calculation method I call the "tableView.reloadData()" first to gather data from the textfields before proceed with the actual calculation.
The problem was when I ran the app. I typed values in all the textfields then clicked "calculate", but it showed error like the textfields were still empty. I clicked again, and it worked. It's like I had to reload twice to get things going.
Can anyone help me out?
By the way, please excuse my English. I'm not from the country that speak English.
edited: It may be useful to post the calculate button code here as someone suggested. So, here is the code of calculate button
#IBAction func calculateRepayment(_ sender: UIButton) {
//Reload data to get the lastest interest rate and duration values
DispatchQueue.main.async {
self.interestRateTableView.reloadData()
}
//Get the loan value from the text field
if let loanText = loanTextField.text {
if let loanValue = Double(loanText) {
loan = loanValue
} else {
print("Can not convert loan value to type Double.")
return
}
} else {
print("Loan value is nil")
return
}
tiers = []
var index = 0
var tier: Tier
for _ in 0..<tierCount {
if let interestRateValue = interestRateArray[index] {
if let durationValue = durationArrayInMonth[index] {
tier = Tier(interestRateInYear: interestRateValue, tierInMonth: durationValue)
tiers.append(tier)
index += 1
} else {
print("Duration array contain nil")
return
}
} else {
print("Interest rate array contain nil")
return
}
}
let calculator = Calculator()
repayment = calculator.calculateRepayment(tiers: tiers, loan: loan!)
if let repaymentValue = repayment {
repaymentLabel.text = "\(repaymentValue)"
totalRepaymentLabel.text = "\(repaymentValue * Double(termInYear!) * 12)"
} else {
repaymentLabel.text = "Error Calculating"
totalRepaymentLabel.text = ""
}
}
cellForRowAt is used for initially creating and configuring each cell, so the textfields are empty when this method is called.
UITableView.reloadData() documentation:
// Reloads everything from scratch. Redisplays visible rows. Note that this will cause any existing drop placeholder rows to be removed.
open func reloadData()
As it says in Apple's comment above, UITableView.reloadData() will reload everything from scratch. That includes your text fields.
There are a number of ways to fix your issue, but it's hard to say the best way without more context. Here's an example that would fit the current context of your code fairly closely:
class MyCustomTableViewCell: UITableViewCell {
#IBOutlet weak var interestRateTextField: UITextField!
var interestRateChangedHandler: (() -> ()) = nil
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
interestRateTextField.addTarget(self, action: #selector(interestRateChanged), for: UIControlEvents.editingChanged)
}
#objc
func interestRateChanged() {
interestRateChangedHandler?()
}
}
and cellForRowAtIndex:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "InterestRateTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? InterestRateTableViewCell else {
fatalError("The dequed cell is not an instance of InterestRateTableViewCell.")
}
cell.rowLabel.text = "\(indexPath.row + 1)."
cell.interestRateChangedHandler = { [weak self] in
if let interestText = cell.interestRateTextField.text {
if let interest = Double(interestText){
self?.interestRateArray[indexPath.row] = interest
} else {
self?.interestRateArray[indexPath.row] = nil
}
} else {
self?.interestRateArray[indexPath.row] = nil
}
}
return cell
}

tableView cell.detailTextLabel.text returns nil

I try to set a string to my detailTextLabel in a tableView but it's returning nil. I have read other posts where I am not the first one but I cannot understand what is going wrong in my case. I am using Swift 4.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell = {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") else {
return UITableViewCell(style: UITableViewCellStyle.value1, reuseIdentifier: "Cell")
}
return cell
}()
let filtersRow: Bool = (currentSearchType == .all && indexPath.section == 0)
var titleText: String = ""
if filtersRow == true {
titleText = "Filters"
var detailText: String = ""
if currentFilters.count == 0 {
detailText = "None"
}
else if currentFilters.count == 1 {
detailText = currentFilters.first!
}
else {
detailText = "\(currentFilters.count)"
}
cell.textLabel?.text = titleText /// -> shows 'Filters' as expected
cell.detailTextLabel?.text = detailText /// -> shows nothing
print("Detail text: \(cell.detailTextLabel?.text)") --> returns nil
print("cell.textLabel? \(String(describing: cell.textLabel))") /// --> Optional(<UITAbleViewLabel...>)
print("cell.detailTextLabel? \(String(describing: cell.detailTextLabel))") /// ---> nil
cell.accessoryType = .disclosureIndicator
cell.accessoryType = .disclosureIndicator
return cell
}
...
There is definitely something wrong with the way I get my cell, but I do the same thing in an other viewController and it is going well...
Does anyone would have an idea?
This happens when the detailTextLabel isnt created. Mostly a bug in your code or storyboard. So check the creation of the problematic Cell.
Read also this Stackoverflow Q&A about this topic

How to hide empty cell tableview, which are not at the end? Swift 3

I'm just starting swift and I'm making a calendar app. Right now I'm showing a list on events if you click on a date. All questions here are about hiding cells that are at the end of a tablieview, but mine are not so eventsTableView.tableFooterView = UIView() doesn't work.
override func viewDidLoad() {
super.viewDidLoad()
eventsTableView.register(UITableViewCell.self, forCellReuseIdentifier: "theCell")
self.eventsTableView.rowHeight = 80
}
func tableView(_ eventsTableView: UITableView,
cellForRowAt indexPath: IndexPath)->UITableViewCell{
let event = model.events[indexPath.row]
let theItem = eventsTableView.dequeueReusableCell(
withIdentifier: "theCell",for: indexPath)
let what = event.value(forKeyPath:"what") as? String
let location = event.value(forKeyPath:"location") as? String
let when = event.value(forKeyPath:"when") as? Date
if model.checkDay(date: when!) == model.givendate && model.checkMonth(date: when!) == model.displayedMonth {
theItem.textLabel?.numberOfLines = 3
let labelText = what! + "\n" + "time: " + model.getTime(date: when!) + "\n" + "location: " + location!
theItem.textLabel?.text = labelText
} else {
theItem.textLabel?.numberOfLines = 0
}
print(theItem)
return theItem
}
This is what my output looks like
Try this code :
You can put a check for empty data in a model and hide that cell completely like this:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
//Check if model is empty
if shouldHideCell {
return 0
} else {
return UITableViewAutomaticDimension
}
}
Please refer to this SO Post. Happy coding.
What you can do here is just customise your dataSource, like if the Data corresponding to that row is empty then it's better to not to add that row in your datasource array.
Example while customising your dataSource
if hasEvent{
dataSource.append(day)
}else{
// No need to show in UI
}

How to get the textvalue of each dynamically created row in UITableView in swift

As shown in below image,i created a prototype cell with label and textfield in tableviewController.
Each row is created at runtime as shown below. When user clicks SAVE button all the details like Course detail,Registration id, Username and passowrd should be saved in respective varaibles. But it does not work. It stores value of last textfield in all variables.
How do i get text value in each row of the table.
// Row creation code
cell variable is global
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
switch(indexPath.row)
{
case 0 :
cell = tableView.dequeueReusableCellWithIdentifier("RegCell", forIndexPath: indexPath) as! RegCell
cell.configure("Course detail", txtValue: "Enter course.")
break;
case 1 :
cell = tableView.dequeueReusableCellWithIdentifier("RegCell", forIndexPath: indexPath) as! RegCell
cell.configure("Registration ID", txtValue: "Enter registration id.")
cell.txtIpValue.tag = 1
break;
case 2 :
cell = tableView.dequeueReusableCellWithIdentifier("RegCell", forIndexPath: indexPath) as! RegCell
cell.configure("Username ", txtValue: "Username")
cell.txtIpValue.tag = 2
break;
case 3 :
cell = tableView.dequeueReusableCellWithIdentifier("RegCell", forIndexPath: indexPath) as! RegCell
cell.configure("Password ", txtValue: "Password")
cell.txtIpValue.tag = 2
break;
default :
break;
}
return cell
}
// Save button code
func saveButtonClicked() {
if(cell.txtIpValue.tag == 1)
{
strCourse = cell.txtIpValue.text
}
if(cell.txtIpValue.tag == 2)
{
strID = cell.txtIpValue.text
}
if(cell.txtIpValue.tag == 3)
{
strUsername = cell.txtIpValue.text
}
if(cell.txtIpValue.tag == 1)
{
strPassword = cell.txtIpValue.text
}
}
// Regcell
class RegCell: UITableViewCell {
#IBOutlet var txtIpValue: UITextField
#IBOutlet var lblstr: UILabel
func configure(lblValue : String,txtValue :String)) {
lblstr.text = lblValue
txtIpValue.text = txtValue
}
}
All code is in swift. Please if possible give an example.
Iterate over all cells in your tableView.
Assuming you have only one section, try this:
Update : Using label texts as keys for the values - you might want to think about finding constant values for that
func save() {
var fields = NSMutableDictionary();
for rowIndex in 0...self.tableView.numberOfRowsInSection(0) {
let indexPath : NSIndexPath = NSIndexPath(forItem: rowIndex, inSection: 0);
let cell : RegCell = self.tableView.cellForRowAtIndexPath(indexPath);
fields.setObject(cell.txtIpValue.text, forKey: cell.lblstr.text)
}
// ... here fields will contain all values, using the lblstr.text as key
let username = fields["Username "];
}

UITableView Cells shifting positions or disappearing entirely on scroll

I am currently developing and application for the iPhone in Swift, and I have run into a very peculiar error: in one of my UITableViewControllers,enter code here cells disappear or change sections when I scroll up and down on the Table View.
I've been dealing with this issue for a few days now, and it has even prompted me to recode the entire class, but to no avail. I have researched extensively on this error, and I believe it has something to do with my data source and how the tableView handles it, and I have also noticed that other users have had the same problem before, but I cannot find a solution that applies to my problems.
For example, here seems to deal with the cell height, but I have continued to check and double check my code, and the cell height returns the correct values.
In addition, this post talks about different errors with the tableView's data source, but I have a strong pointer to the datasource's alert array and my content and heights are correct in cellForRowAtIndexPath.
This post also deals with my question, but everything I am currently doing with the tableView is on the main thread.
Currently the tableView has 4 sections: the first, second, and fourth contain only one cell and the third has a dynamic amount of cells based on the amount of alerts the user has added (for example, it has 3 alert cells plus one "Add Alert" cell always at the bottom). The only cells that are affected are those in the 2, 3, and 4 sections.
This is what my tableView should look like always:
But, however, here is what happens when I scroll:
I first create the variables here:
var currentPrayer: Prayer! // This is the prayer that the user is currently editing
var prayerAlerts: NSMutableOrderedSet! // This is the mutable set of the prayer alerts that are included in the prayer
Then I initialize them in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
if currentPrayer == nil {
NSException(name: "PrayerException", reason: "Current Prayer is nil! Unable to show prayer details!", userInfo: nil).raise()
}
navItem.title = currentPrayer.name // Sets the Nav Bar title to the current prayer name
prayerAlerts = currentPrayer.alerts.mutableCopy() as! NSMutableOrderedSet // This passes the currentPrayer alerts to a copy called prayerAlerts
prayerAlertsCount = prayerAlerts.count + 1
}
Below are my TableView methods:
Here is my cellForRowAtIndexPath:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
println("cellForRowAtIndexPath called for the \(cellForRowRefreshCount) time")
cellForRowRefreshCount += 1
switch indexPath.section {
case 0:
var cell = tableView.dequeueReusableCellWithIdentifier(DetailsExtendedCellID, forIndexPath: indexPath) as! PrayerDetailsExtendedCell
cell.currentPrayer = currentPrayer
cell.refreshCell()
return cell
case 1:
var cell = tableView.dequeueReusableCellWithIdentifier(SetPrayerDateCellID, forIndexPath: indexPath) as! AddPrayerDateCell
cell.currentPrayer = currentPrayer
cell.refreshCell(false, selectedPrayer: cell.currentPrayer)
return cell
case 2:
if indexPath.row == prayerAlerts.count {
var cell = tableView.dequeueReusableCellWithIdentifier(AddNewAlertCellID, forIndexPath: indexPath) as! AddPrayerAlertCell
cell.currentPrayer = currentPrayer
cell.refreshCell(false, selectedPrayer: currentPrayer)
cell.saveButton.addTarget(self, action: "didSaveNewAlert", forControlEvents: .TouchDown)
return cell
} else {
var cell = tableView.dequeueReusableCellWithIdentifier(PrayerAlertCellID, forIndexPath: indexPath) as! PrayerAlertCell
let currentAlert = prayerAlerts[indexPath.row] as! Alert
cell.alertLabel.text = AlertStore.sharedInstance.convertDateToString(currentAlert.alertDate)
return cell
}
case 3:
var cell = tableView.dequeueReusableCellWithIdentifier(AnsweredPrayerCellID, forIndexPath: indexPath) as! PrayerAnsweredCell
cell.accessoryType = currentPrayer.answered == true ? .Checkmark : .None
return cell
default:
return UITableViewCell()
}
}
And my numberOfRowsInSection:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0: println("Returning 1 Row for section 0"); return 1
case 1: println("Returning 1 Row for section 1"); return 1
case 2: println("Returning \(prayerAlertsCount) Rows for section 2"); return prayerAlertsCount
case 3: println("Returning 1 Row for section 3"); return 1
default: println("Returning 0 Rows for section Default"); return 0
}
}
And my heightForRowAtIndexPath:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
switch indexPath.section {
case 0: return UITableViewAutomaticDimension
case 1:
let cell = tableView.cellForRowAtIndexPath(indexPath) as? AddPrayerDateCell
if let thisCell = cell {
let isAdding = thisCell.isAddingDate
if isAdding {
if thisCell.selectedType == PrayerType.None || thisCell.selectedType == PrayerType.Daily {
println("Selected Type is None or Daily")
println("Returning a height of 89 for AddPrayerDateCell")
return 89
} else {
println("Returning a height of 309 for AddPrayerDateCell")
return 309
}
} else {
println("Returning a height of 44 for AddPrayerDateCell")
return 44
}
} else {
println("Returning a default height of 44 for AddPrayerDateCell")
return 44
}
case 2:
if indexPath.row == prayerAlerts.count {
let cell = tableView.cellForRowAtIndexPath(indexPath) as? AddPrayerAlertCell
if let thisCell = cell {
let isAdding = thisCell.isAddingAlert
if isAdding { return 309 }; return 44
} else {
return 44
}
} else {
return 44
}
case 3: return 44
default: return 44
}
}
And estimatedHeightForRowAtIndexPath:
override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
switch indexPath.section {
case 0: return 130
case 1: return 44
case 2: return 44
case 3: return 44
default: return 44
}
}
I have tried editing these methods extensively, as well as checking the code in each individual cell. Nothing seems to work.
Does anyone have any solutions to this error? I can always update with more code if necessary, but I believe that either my data source could be the problem, or that the cell's resuse could be creating this error, but I cannot seem to pinpoint anything. Thanks in advance for any help!
UPDATE
Here is my AddAlertCell "refreshCell()" method as well as my UITableViewCell extension:
func refreshCell(didSelect: Bool, selectedPrayer: Prayer!) {
tableView?.beginUpdates()
selectionStyle = didSelect == true ? .None : .Default
saveButton.hidden = !didSelect
cancelButton.hidden = !didSelect
addNewAlertLabel.hidden = didSelect
isAddingAlert = didSelect
dateLabel.text = AlertStore.sharedInstance.convertDateToString(datePicker.date)
println("AddPrayerAlertCell: Cell Refreshed")
tableView?.scrollEnabled = !didSelect
tableView?.endUpdates()
}
UITableViewCell Extension:
extension UITableViewCell {
var tableView: UITableView? {
get {
var table: UIView? = superview
while !(table is UITableView) && table != nil {
table = table?.superview
}
return table as? UITableView
}
}
}
You shouldn't need to call beginUpdates / endUpdates when you refresh the cell - these methods are used if you are adding / deleting rows or sections from the tableview.
What happens if you remove the beginUpdates() and endUpdates() calls?

Resources