Swift: UITableViewCell Automatic Row Height For style Subtitle - ios

I have a tableView with 2 sections, the first section is for text the user inputed earlier, the other section for selections based on that text. The first section has a Default tableViewCell style, the second section has a style of Subtitle. The first section is just a single cell, and it sizes dynamically based on the amount of text without issue. The second section is multiple cells, with UITableViewCell.textLabel and UITableViewCell.detailText set. These are the cells that do not auto size properly at all, I don't know what I am doing wrong. Note: 1) I do have tableView.rowHeight = UITableViewAutomaticDimension set in the viewDidLoad() method. 2) I am not using prototype cells in the storyboard.
This article states that I "must have constraints on the contentView". I honestly have no idea what that means. I know what constraints are in terms of setting content on the storyboard. I just don't know what he means in this context or how I would go about that if I don't have prototype cells.
Also, I have to set two re-use identifiers, depending on which section it is. That way it doesn't try to reuse the cell/section I have set aside for the user's input text.
With all that said in mind, here's the code I have. I'm a newbie to Swift and developing for iOS in general so if you have suggestions/advice for refactoring feel free to let me know. I have commented out some things I have tried. Setting the row height to 66 does work, but that's not the goal here. I want it dynamic because I don't know what will change later on.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cellIdentifier = ""
if indexPath.section == 1 {
//tableView.rowHeight = 66
//tableView.rowHeight = UITableViewAutomaticDimension
cellIdentifier = "DistortionItem"
} else {
//tableView.rowHeight = 160
cellIdentifier = "NegativeThought"
}
var cell: UITableViewCell! = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as? UITableViewCell
if indexPath.section == 1 {
if cell == nil {
cell = UITableViewCell(style: .Subtitle, reuseIdentifier: cellIdentifier)
}
cell.textLabel?.text = distortionslist.distortions[indexPath.row].0
cell.detailTextLabel?.font = UIFont.systemFontOfSize(10)
cell.detailTextLabel!.text = distortionslist.distortions[indexPath.row].1
cell.textLabel?.numberOfLines = 0
cell.detailTextLabel?.numberOfLines = 0
//tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
//cell.textLabel?.sizeToFit()
//cell.detailTextLabel?.sizeToFit()
} else {
if cell == nil {
//println("Cell set to default")
cell = UITableViewCell(style: .Default, reuseIdentifier: cellIdentifier)
}
cell.textLabel?.font = UIFont.systemFontOfSize(12)
cell.textLabel?.text = entry.thoughtText
cell.textLabel?.numberOfLines = 0
//cell.textLabel?.sizeToFit()
}
return cell
}
Example screenshot:

Related

How to implement a tableview cell that contains 2 more cells inside counts are not same

I am trying to achieve a layout like this
My tableview has 3 cells (SubItemsCell (main cell), SubItemsListCell(n number of products inside main cell) and noSubstituteCell (fixed cell after the second cell count 1))
SubItemsCell has a "SELECT" Button that will expand and show SubItemsListCell, this cell will load as (dynamic) any count of products under the main SubItems Cell.
NoSubstitute Cell comes after the n number of products loads.
What my expected result is first load main cell that is self.archivedProducts and when u click each archived product's select button it expand and load self.newproducts and nosubstitute static cell. So main cell paired up with those 2 cells and show at once when we expand only.
So far I just loaded Main Cell only. I have no idea what should I do next, please give me an idea with a code or very similar example.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "SubItemsCell", for: indexPath) as? SubItemsCell else {
fatalError ("SubstituteItems Cell not found!")
}
let product = self.archivedProducts[indexPath.row]
cell.titleLabel.text = product.title ?? ""
cell.priceLabel.text = "AED \(String(format: "%.2f", product.price ?? 0.00))"
cell.scaleLabel.text = product.uom ?? ""
cell.unavailableLabel.isHidden = false
cell.selectBtn.isHidden = false
let imageUrl = product.image?.url
let escapedUrl = imageUrl?.replacingOccurrences(of: "\\", with: "")
let replacedUrl = "\(escapedUrl ?? "")"
let url = URL(string: replacedUrl)
let plImage = UIImage(named: "whiteBg")
DispatchQueue.main.async {
cell.thumbImage.kf.setImage(with: url, placeholder: plImage, options: [.transition(.fade(0.2))])
}
}
I don't think you need a nested tableview. You can achieve the above with other elements. In order to achieve the layout above, my suggestion is the following:
Subclass your UITableViewCell into at least 2 different formats. There's a lot of ways to do this, my suggestion is to use a CocoaTouch Class w/ XIB (tutorial here) so you can be meticulous with the layout and constraints:
a. CellWDropdown (Cell #1 shown): You have a cell prototype that will include the image, the 3 labels in question and a button which act as the dropdown and will invoke a UIPickerView.
b. CellWStepper (Cells #2 and #3) : You'll have your image and 2 labels and what's called UIStepper for the +/- buttons
c. LastCell (Cell #4 if that's a cell? It could also be a view): likewise create the needed UI elements and constrain them appropriately.
Register the nib file you created:
tableView.register(UINib(nibName: "CellWDropDown", bundle: nil), forCellReuseIdentifier: "cellWDropdownIdentifier")
In cellForRowAt method, you invoke the XIBs created above to make the cell views based on the index OR desired cell type for example:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: cellWDropdownIdentifier, for: indexPath) as! CellWDropdown
}
else if(indexPath.row == 1 || indexPath.row == 2){
let cell = tableView.dequeueReusableCell(withIdentifier: cellWStepperIdentifier, for: indexPath) as! CellWStepper
}
//do other actions/styling
}
Hook up delegate relationships/methods between the nibs you created and the UITableViewController. This is going to application specific.
There's a lot of detail that will have to fill in the gaps but this should put a major dent in getting started

xcode8 swift3 multiple UITableViews with custom cells with

Following up to the excellent write up of a ViewController with a single tableView, I'd like to extend the question to having 2 separate tableViews and custom cells belonging to each one independently.
At the moment, I have the following skeleton, which is semi-working, and I am sure there is a more elegant and less naive approach to solving this.
after the viewDidLoad()
vInfoTV.dataSource = self
vInfoTV.delegate = self
vInfoTV.tag = Int.min
vAppTV.dataSource = self
vAppTV.delegate = self
vAppTV.tag = Int.max
numberOfRowsInSection function
func tableView(_ tableView: UITableView, numberOfRowsInSection section:Int) -> Int {
if tableView.tag == Int.min {
return mydata.cats.count
} else {
return mydata.dogs.count
}
}
Is it appropriate to set the tableView's tags as I do here, and switch on them based on tag value?
Cells in func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView.tag == Int.min {
// Cat Table View
if indexPath.row == 0 {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "firstCustomCell")
//set the data here
return cell
}
else if indexPath.row == 1 {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "secondCustomCell")
//set the data here
return cell
}
else {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "thirdCustomCell")
//set the data here
return cell
}
} else {
// Dog Table View
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "poodleCell", for: indexPath) as! NewApplicationViewCell
cell.typeL.text = Dogs[(indexPath as NSIndexPath).row].type
return cell
}
else {
let cell = tableView.dequeueReusableCell(withIdentifier: "mastifCell", for: indexPath) as! InitialDMVInspectionTableView
cell.typeL.text = Dogs[(indexPath as NSIndexPath).row].type
return cell
}
}
What is the best way to now hide a tableView, which has no cells (ie no data)?
Is this the right way to do this? All comments welcome!
THANK YOU!
Is it appropriate to set the tableView's tags as I do here, and switch on them based on tag value?
yes, you did exactly how I would do. Set the tag of your tables' differently. However I would not say Int.min or Int.max, rather I would want to know instantly what did I set as the tableviews' tag. So, I would just pick a number like 99 and 100. the reason I would not pick 0 and 1 is by default any object's tag is 0. So, if I put 99, I would be just keeping myself safe saying that even if someone comes and drag another table view inside my view, it will still not conflict with the ones before.
What is the best way to now hide a tableView, which has no cells (ie no data)?
Your tableview will not show up if you don't have data as in your numberOfRowsInSection, you set the row number to be the number of data in your desired data array.

In Swift, I'd like the last 3 sections have only labels in their rows (and NOT text fields)

In my Swift project I have a table controller view with a table view inside. The table view is divided into 4 section and every section has 4 rows. Currently, each row is formed by a label beside a text field.
My purpose is that only rows in the first section has label beside text field. On the contrary, I want the last 3 sections have only labels in their rows (and NOT text fields).
Please, help me.
That's the code I wrote to manage with this problem but it's not working:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
print("index ", indexPath.section);
let cell = tableView.dequeueReusableCellWithIdentifier("tableCell", forIndexPath: indexPath)
cell.textLabel?.text = self.items[indexPath.section][indexPath.row];
if(indexPath.section == 0){
let cell = tableView.dequeueReusableCellWithIdentifier("tableCell") as! TextInputTableViewCell
cell.configure("", placeholder: "name")
}
return cell
}
It's not working because you are using the same cell for all the rows. You need to define two different rows. You can do this by setting prototype cells to more than one row (two in your case).
Each cell must have its own reuse identifier and it must be unique within that table view.
Then in your tableView(cellForRowAtIndexPath:) you can ask:
if indexPath.section == 0 {
cell = tableView.dequeueReusableCellWithIdentifier("firstSectionCell", forIndexPath: indexPath)
//
} else {
cell = tableView.dequeueReusableCellWithIdentifier("otherSectionCell", forIndexPath: indexPath)
//
}
Also note that in Swift you do not need to use parenthesis in if-statement (nor for, while, etc). So I suggest you remove them as they are pointless.
It looks like your cell in if(indexPath.section == 0) block doesn't actually have scope outside that block so any properties set there won't get returned there. If you just want to remove the textField, but keep the label, You can just set the textField.hidden = true. Here is how I would go about it.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("tableCell", forIndexPath: indexPath) as! TextInputTableViewCell
cell.textLabel?.text = self.items[indexPath.section][indexPath.row];
if(indexPath.section == 0){
cell.textField.hidden = false //Assumes TextInputTableViewCell's textField property is called "textField"
cell.configure("", placeholder: "name")
} else {
cell.textField.hidden = true //Not in first section we will hide textField
}
return cell
}
Doing it this way you can use the same cell class for every cell in your tableView, but just hide what you want based on its section.

Converting Objective to Swift Table view not displaying the data in the cells

I'm still very inexperienced with Swift and am having a problem converting an objective-c based app.
Most of the app is working ... including changing size and colors of section headers and background color of the cells but I cannot display the content of the cells (a TextView and a switch).
Any suggestions about fixing this would be appreciated.
I'm including the code where I change the background color which is where I suspect the problem resides:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//variable type is inferred
var index = indexPath.row
NSLog ("index %d",indexPath)
var cell = tableView.dequeueReusableCellWithIdentifier("CELL") as? UITableViewCell
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Value1, reuseIdentifier: "CELL")
}
//we know that cell is not empty now so we use ! to force unwrapping
var grayishCyan = colorWithHexString("#9bc2c2")
var grayishRed = colorWithHexString("#ffc2c2")
if (indexPath.row == 0 || indexPath.row%2 == 0) {
cell!.backgroundColor = grayishCyan;
}
else {
cell!.backgroundColor = grayishRed
}
return cell!
} // end of cellForRowAtIndexPath
I removed the cellForRowAtIndexPath function and replaced it with the willDisplayCell function

Access different views inside of a UITableViewCell by tag in Swift

I'm trying to make an app for iOS 8, using swift. The goal here is to make a kind of news feed. This feed displays posts from users, which follows a certain pattern.
I thought of using the UITableView where each cell follows a custom layout. The problem appears when I try to access a text label inside it. I try to access it by its tag, but when I do it, the whole app crashes. The error reported is "Swift dynamic cast failed", and I'm using the following code to access the view:
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cellId: String = "cell"
var cell : UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellId) as UITableViewCell
if let ip = indexPath{
cell.textLabel.text = myData[ip.row] as String
var lbl = cell.contentView.viewWithTag(0) as UILabel
lbl.text = "ola"
}
return cell
}
Am I doing something wrong? Thanks in advance!
i think the Problem is the Tag 0. All Views are on default value 0. So try another tag value.
Just faced the same issue. The solution was to change tag to 10 and 20. I used 1 and 2 before. Is anybody aware of a range of tags that is used by the system?
So my 'cellForRowAtIndexPath' for a table with an image and a label per row looks like this now:
internal func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCellWithIdentifier("cellIdentifier") as? UITableViewCell {
(cell.contentView.viewWithTag(10) as UIImageView).image = IMAGES[indexPath.row]
(cell.contentView.viewWithTag(20) as UILabel).text = LABELS[indexPath.row]
return cell
} else {
NSLog("Prototype did not work")
return UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "errorIdentifier")
}
}
Change the 0 to 1 or another tag, it work for me.
0 is overlapping to another cell label's tag.

Resources