UITableViewCell doesn't update height after adding a view to UIStackView - ios

I have a UIStackView inside UITableViewCell's contentView. Based on user interaction, I add/remove items in the UIStackView. After modifying the items in UIStackView, I expect the cell to update it's height accordingly. But, it doesn't update it's height unless I call tableView.reloadData(). But, calling reloadData() in cellForRowAtIndexPath / willDisplayCell becomes recursive.
What is the proper way to adjust the cell height at run time based on items in UIStackView?
I use UITableViewAutomaticDimension
Updating the Problem:
Here is a simple prototype of what I am trying to do.
My actual problem is dequeuing the cell.
In the prototype, I have 2 reusable cells and 3 rows. For row 0 and 2, I dequeue cellA and for row 1, I dequeue cellB. Below is the overview on the condition I use.
if indexPath.row == 0 {
// dequeue cellA with 2 items in stackView
}
if indexPath.row == 1 {
// dequeue cellB with 25 items in stackView
}
if indexPath.row == 2 {
// dequeue cellA with 8 items in stackView
}
But the output is,
row 0 contains 2 items in stackView - expected
row 1 contains 25 items in stackView - expected
row 2 contains 2 items in stackView - unexpected, row 0 is dequeued
I also tried removing all arranged subViews of stackView in cellForRowAtIndexPath. But, doing so, flickers the UI when scrolling. How can I manage to get the desired output?

I believe the problem is when you are adding views to the stack view.
In general, adding elements should take place when the cell is initialized.
willDisplay cell: is where one handles modifying attributes of cell contents.
If you move your code from willDisplay cell: to cellForRowAt indexPath: you should see a big difference.
I just made that one change to the code you linked to, and the rows are now auto-sizing based on the stack view contents.
Edit: Looked at your updated code... the issue was still that you are adding your arrangedSubviews in the wrong place. And you compound it by calling reloadData().
Second Edit: Forgot to handle previously added subviews when the cells are reused.
Updated code... replace your ViewController code with:
//
// ViewController.swift
//
import UIKit
class ViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableFooterView = UIView()
tableView.estimatedRowHeight = 56
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = UITableViewCell()
if indexPath.row == 0 || indexPath.row == 2 {
cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
if let stackView = cell.viewWithTag(999) as? UIStackView {
let numberOfItemsInStackView = (indexPath.row == 0) ? 2 : 8
let color = (indexPath.row == 0) ? UIColor.gray : UIColor.black
// cells are reused, so clear out any previously added subviews...
// but leave the first view that is part of the cell prototype
while stackView.arrangedSubviews.count > 1 {
stackView.arrangedSubviews[1].removeFromSuperview()
}
// use "i" so we can count
for i in 1...numberOfItemsInStackView {
// use label instead of view so we can number them for testing
let newView = UILabel()
newView.text = "\(i)"
newView.textColor = .yellow
// add a border, so we can see the frames
newView.layer.borderWidth = 1.0
newView.layer.borderColor = UIColor.red.cgColor
newView.backgroundColor = color
let heightConstraint = newView.heightAnchor.constraint(equalToConstant: 54)
heightConstraint.priority = 999
heightConstraint.isActive = true
stackView.addArrangedSubview(newView)
}
}
}
if indexPath.row == 1 {
cell = tableView.dequeueReusableCell(withIdentifier: "lastCell")!
if let stackView = cell.viewWithTag(999) as? UIStackView {
let numberOfItemsInStackView = 25
// cells are reused, so clear out any previously added subviews...
// but leave the first view that is part of the cell prototype
while stackView.arrangedSubviews.count > 1 {
stackView.arrangedSubviews[1].removeFromSuperview()
}
// use "i" so we can count
for i in 1...numberOfItemsInStackView {
// use label instead of view so we can number them for testing
let newView = UILabel()
newView.text = "\(i)"
newView.textColor = .yellow
// add a border, so we can see the frames
newView.layer.borderWidth = 1.0
newView.layer.borderColor = UIColor.red.cgColor
newView.backgroundColor = UIColor.darkGray
let heightConstraint = newView.heightAnchor.constraint(equalToConstant: 32)
heightConstraint.priority = 999
heightConstraint.isActive = true
stackView.addArrangedSubview(newView)
}
}
}
return cell
}
// override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// if cell.reuseIdentifier == "cell" {
// if let stackView = cell.viewWithTag(999) as? UIStackView {
// let numberOfItemsInStackView = (indexPath.row == 0) ? 2 : 8
// let color = (indexPath.row == 0) ? UIColor.gray : UIColor.black
// guard stackView.arrangedSubviews.count == 1 else { return }
// for _ in 1...numberOfItemsInStackView {
// let newView = UIView()
// newView.backgroundColor = color
// let heightConstraint = newView.heightAnchor.constraint(equalToConstant: 54)
// heightConstraint.priority = 999
// heightConstraint.isActive = true
// stackView.addArrangedSubview(newView)
// }
// tableView.reloadData()
// }
// }
//
// if cell.reuseIdentifier == "lastCell" {
// if let stackView = cell.viewWithTag(999) as? UIStackView {
// let numberOfItemsInStackView = 25
// guard stackView.arrangedSubviews.count == 1 else { return }
// for _ in 1...numberOfItemsInStackView {
// let newView = UIView()
// newView.backgroundColor = UIColor.darkGray
// let heightConstraint = newView.heightAnchor.constraint(equalToConstant: 32)
// heightConstraint.priority = 999
// heightConstraint.isActive = true
// stackView.addArrangedSubview(newView)
// }
// tableView.reloadData()
// }
// }
// }
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
}

Try to reload only the cell using: https://developer.apple.com/documentation/uikit/uitableview/1614935-reloadrows
Example code
Here is an example. We have basic table view cells (TableViewCell) inside a view controller. The cells have 2 labels inside a stack view. We can hide or show the second label using the collapse/reveal methods.
class TableViewCell : UITableViewCell {
#IBOutlet private var stackView: UIStackView!
#IBOutlet private var firstLabel: UILabel!
#IBOutlet private var secondLabel: UILabel!
func collapse() {
secondLabel.isHidden = true
}
func reveal() {
secondLabel.isHidden = false
}
}
class ViewController : UIViewController {
#IBOutlet var tableView: UITableView!
fileprivate var collapsedCells: Set<IndexPath> = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 128
}
#IBAction private func buttonAction(_ sender: Any) {
collapseCell(at: IndexPath(row: 0, section: 0))
}
private func collapseCell(at indexPath: IndexPath) {
if collapsedCells.contains(indexPath) {
collapsedCells.remove(indexPath)
} else {
collapsedCells.insert(indexPath)
}
tableView.reloadRows(at: [indexPath], with: .automatic)
}
}
extension ViewController : UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TableViewCell
if collapsedCells.contains(indexPath) {
cell.collapse()
} else {
cell.reveal()
}
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
}

Related

How to show a parent view which is outside tableview and is scrollable?

I have a scenario where I need to show a parent view with shadow and corner radius containing a long list of reusable items. I used a tableView to display items. But I stuck at making my tableview expand as much as its contentSize. It works but not accurate. Any solutions?
Edit:
Desired result:
I used the following reference for self sizing tableview.
Self Sizing UITableView
I made a few modifications as below:
final class SelfSizedTableView: UITableView {
var maxHeight = CGFloat.greatestFiniteMagnitude
override func reloadData() {
super.reloadData()
self.invalidateIntrinsicContentSize()
self.layoutIfNeeded()
}
override var intrinsicContentSize: CGSize {
let height = min(contentSize.height, maxHeight)
let size = CGSize(width: contentSize.width, height: height)
return size
}
}
I used a parent tableView with a cell having my containerView and embedding this self sized tableView.
class MyContainerViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
// MARK: - IBOutlets
#IBOutlet weak var parentTableView: UITableView!
// MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
}
private func estimateDataHeight() -> CGFloat {
let detailCellHeight: CGFloat = 32
let headingCellHeight: CGFloat = 43
let headings: CGFloat = headingCellHeight*2
let detailsHeight: CGFloat = detailCellHeight*4
let baseHeight = headings + detailsHeight
let membersHeight =
CGFloat(sectionsArray.count) * detailCellHeight
return baseHeight + membersHeight
}
}
// MARK: - UITableViewDataSource
extension MyContainerViewController {
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let id = String(describing: MyContainerTVCell.self)
guard let cell = tableView
.dequeueReusableCell(withIdentifier: id, for: indexPath)
as? MyContainerTVCell else {
return UITableViewCell()
}
cell.policyDetails = dataSource
// my cheat/trick doesn't work on large data.
DispatchQueue.main.asyncAfter(deadline: .now()+0.4) {
tableView.beginUpdates()
cell.tableView.layoutIfNeeded()
cell.tableView.reloadData() // the overridden one
tableView.endUpdates()
}
return cell
}
}
extension MyContainerViewController {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return estimateDataHeight()
}
}
My cell class which has the self size tableView and containerView:
class MyContainerTVCell: UITableViewCell, UITableViewDataSource, UITableViewDelegate {
// MARK: - IBOutlets
#IBOutlet weak var containerView: UIView!
#IBOutlet weak var shadowView: UIView!
#IBOutlet weak var tableView: SelfSizedTableView!
// MARK: - Properties
let titles = ["Email ID:", "Mobile Number:", "Address:", "ID: "] // first section data array
let moreData: [String] = [] // remaining reusable sections array
// no of subsequent sections for moreData array type
var numberOfSections: Int {
return 4
}
// MARK: -
var dataSource: MyDataSource!
// MARK: - Life Cycle
override func awakeFromNib() {
super.awakeFromNib()
setupView()
}
override func layoutSubviews() {
super.layoutSubviews()
}
// MARK: - Setup
func setupView() {
containerView.rounded(with: 10)
shadowView.layer.applyShadow()
tableView.dataSource = self
tableView.delegate = self
}
}
// MARK: - UITableViewDataSource
extension MyContainerTVCell {
func numberOfSections(in tableView: UITableView) -> Int {
return numberOfSections + 1
}
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
if section == 0 { return titles.count + 1 }
else if section == 1 { return moreData.count + 1 }
else { return moreData.count }
}
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let headerID = String(describing: MyHeaderTVCell.self)
let itemID = String(describing: MyItemTVCell.self)
switch indexPath.section {
case 0:
if indexPath.row == 0 {
guard let cell = tableView
.dequeueReusableCell(withIdentifier: headerID, for: indexPath)
as? MyHeaderTVCell else {
return UITableViewCell()
}
cell.titleLabel.text = dataSource.title
return cell
} else {
guard let cell = tableView
.dequeueReusableCell(withIdentifier: itemID, for: indexPath)
as? MyItemTVCell else {
return UITableViewCell()
}
let item = titles[indexPath.row-1]
cell.titleLabel.text = item
cell.separatorView.isHidden = true
let data: String
switch indexPath.row {
case 1:
data = dataSource.emailID
case 2:
data = dataSource.mobileNo
case 3:
data = dataSource.address
case 4:
data = dataSource.name
case 5:
data = dataSource.age
case 6:
data = dataSource.id
case 7:
data = dataSource.office
case 8:
data = dataSource.academic
default: data = String()
}
cell.detailLabel.text = data
return cell
}
case 1:
if indexPath.row == 0 {
guard let cell = tableView
.dequeueReusableCell(withIdentifier: headerID, for: indexPath)
as? MyHeaderTVCell else {
return UITableViewCell()
}
cell.titleLabel.text = "More Data"
return cell
} else {
guard let cell = tableView
.dequeueReusableCell(withIdentifier: itemID, for: indexPath)
as? MyItemTVCell else {
return UITableViewCell()
}
let sectionIndex = indexPath.section-1
guard sectionIndex <= numberOfSections-1,
let section = sectionsArray?[indexPath.section-1] else {
return UITableViewCell()
}
cell.titleLabel.text = moreData[indexPath.row-1]
cell.separatorView.isHidden = true
switch indexPath.row {
case 1:
cell.detailLabel.text = section.a
case 2:
cell.detailLabel.text = section.b
case 3:
cell.detailLabel.text = "\(section.c ?? 0)"
case 4:
cell.detailLabel.text = section.d
case 5:
cell.detailLabel.text = section.e
case 6:
cell.detailLabel.text = section.f
if indexPath.section < numberOfSections {
cell.separatorView.isHidden = false
}
default: break
}
return cell
}
default:
guard let cell = tableView
.dequeueReusableCell(withIdentifier: itemID, for: indexPath)
as? MyItemTVCell else {
return UITableViewCell()
}
let sectionIndex = indexPath.section-1
guard sectionIndex <= numberOfSections-1,
let section = sectionsArray?[indexPath.section-1] else {
return UITableViewCell()
}
cell.titleLabel.text = moreData[indexPath.row]
cell.separatorView.isHidden = true
switch indexPath.row {
case 0:
cell.detailLabel.text = section.a
case 1:
cell.detailLabel.text = section.b
case 2:
cell.detailLabel.text = "\(section.c ?? 0)"
case 3:
cell.detailLabel.text = section.d
case 4:
cell.detailLabel.text = section.e
case 5:
cell.detailLabel.text = section.f
if indexPath.section < numberOfSections {
cell.separatorView.isHidden = false
}
default: break
}
return cell
}
}
}
// MARK: - UITableViewDelegate
extension MyContainerTVCell {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 0 && indexPath.row == 0 { return 43 }
if indexPath.section == 1 && indexPath.row == 0 { return 43 }
return 32
}
}
Why would you want to expand tableView as much as its content size to make it scrollable, when tableView is already scrollable?
However, if you have some other content, aside from table, on the screen and you want them to scroll together, then you need to embed all your content into UIScrollView.
Then, make a height constraint for you tableView in xib/storyboard with any value.
Then you might do something like this:
// in your view controller
private var heightObservation: NSKeyValueObservation?
// called once, for example, in viewDidLoad()
private func setupTableView() {
...
observation = tableView.constraintFrameHeightToContentSizeHeight()
}
extension UITableView {
func constraintFrameHeightToContentSizeHeight() -> NSKeyValueObservation {
return observe(\.contentSize, changeHandler: { (tableView, _) in
tableView.heightConstraint?.constant = tableView.contentSize.height
})
}
}
// find height constraint
extension UIView {
var heightConstraint: NSLayoutConstraint? {
return constraints.first(where: { $0.firstAttribute == .height })
}
}
Don't forget to uncheck "Scrolling Enabled" in xib/storyboard for that table view.

Layout and sizing issues with TableViewCells that change height based on content inside - Swift 3

I'm building a simple messenger app that uses a tableview to display the messages. Each cell contains text and a stretchable background image. When messages are added to the tableview, they do change height to accommodate the text. However, whenever a single-line message is entered, the table view cell appears to be way too long for just a single line of text.
I think it has to do with the initial height and width of the tableviewcell, but I am not sure. How can I fix this to ensure the text bubble image encompasses the text but does not expand too much over it?
Screenshot of single and multi-lined texts:
Screenshot of long single-lined text for comparison:
I am using auto layout, if it helps.
ViewController code:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
var texts: [String] = ["Hey, how are you?", "Good, you?", "Great!"]
var user: [Int] = [1, 0, 1]
let screenSize = UIScreen.main.bounds
#IBOutlet weak var tableView: UITableView!
#IBAction func sendMessage(_ sender: Any) {
if textBox.text != ""
{
let str:String = textBox.text
let retstr:String = insert(seperator: "\n", afterEveryXChars: 27, intoString: str)
let rand:UInt32 = arc4random_uniform(2)
addText(text: String(retstr), user: Int(rand))
}
}
#IBOutlet weak var textBox: UITextView!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.texts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (self.user[indexPath.row]==1)
{
let cell:CustomCell = self.tableView.dequeueReusableCell(withIdentifier: "cell") as! CustomCell
cell.myCellLabel.text = self.texts[indexPath.row]
cell.myCellLabel.textAlignment = NSTextAlignment.left
cell.myCellLabel.sizeToFit()
cell.myBackgroundImage.image = UIImage(named: "bubbleReversed")?.resizableImage(withCapInsets: UIEdgeInsetsMake(60, 50, 60, 50))
return cell
}
else
{
let cell:CustomCellOther = self.tableView.dequeueReusableCell(withIdentifier: "cell2") as! CustomCellOther
cell.myCellLabel.text = self.texts[indexPath.row]
cell.myCellLabel.textAlignment = NSTextAlignment.right
cell.myCellLabel.sizeToFit()
cell.myBackgroundImage.image = UIImage(named: "bubble")?.resizableImage(withCapInsets: UIEdgeInsetsMake(60, 50, 60, 50))
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You tapped cell number \(indexPath.row).")
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = 10.0
tableView.rowHeight = UITableViewAutomaticDimension
tableView.reloadData()
}
func addText(text:String, user:Int)
{
if (self.texts.count > 20)
{
self.texts.remove(at: 0)
self.user.remove(at: 0)
}
self.texts.append(text)
self.user.append(user)
tableView.reloadData()
let indexPath = NSIndexPath(row: self.texts.count-1, section: 0)
tableView.scrollToRow(at: indexPath as IndexPath, at: .top, animated: true)
}
func insert(seperator: String, afterEveryXChars: Int, intoString: String) -> String {
var output = ""
intoString.characters.enumerated().forEach { index, c in
if index % afterEveryXChars == 0 && index > 0 {
output += seperator
}
output.append(c)
}
return output
}
}
My tableviewcell classes just contain a UIImageView and a label.

Two tableViews in same ViewController won't show in swift 3

I am trying to have two UITableViews in the same ViewController. I am trying to do all of it programmatically but for some reason neither of the tableViews display at all.
I have noticed that the code never gets to
else if tableView == self.tableView2 {
}
in the cellForRowAt method. I have no idea why this is the case.
I appreciate any help to solve this.
import UIKit
class TestViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView1: UITableView?
var tableView2: UITableView?
let tableView1Data = ["Option 1", "Option 2", "Option 3", "Option 4", "Other"]
let tableView2Data = ["Cancel"]
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .apricot
// Initalize
tableView1 = UITableView()
tableView2 = UITableView()
// Register cells
// tableView1!.register(UITableViewCell.self, forCellReuseIdentifier: "Cell1")
// tableView2!.register(UITableViewCell.self, forCellReuseIdentifier: "Cell2")
tableView1!.register(UINib(nibName: "yourNib", bundle: nil), forCellReuseIdentifier: "Cell1")
tableView2!.register(UINib(nibName: "yourNib", bundle: nil), forCellReuseIdentifier: "Cell2")
// Set delegates
tableView1!.delegate = self
tableView1!.dataSource = self
tableView2!.delegate = self
tableView2!.dataSource = self
// Add to view
view.addSubview(tableView1!)
view.addSubview(tableView2!)
// Set size constraints
tableView1!.translatesAutoresizingMaskIntoConstraints = false
tableView1!.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView1!.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView1!.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
tableView2!.translatesAutoresizingMaskIntoConstraints = false
tableView2!.topAnchor.constraint(equalTo: tableView1!.bottomAnchor, constant: 15).isActive = true
tableView2!.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView2!.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
// Customize looks
tableView1!.layer.cornerRadius = 10
tableView2!.layer.cornerRadius = 10
// Reload data
tableView1!.reloadData()
tableView2!.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
tableView1!.reloadData()
tableView2!.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == self.tableView1 {
return tableView1Data.count
}
else if tableView == self.tableView2 {
return tableView2Data.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: UITableViewCell?
if tableView == self.tableView1 {
cell = tableView.dequeueReusableCell(withIdentifier: "Cell1", for: indexPath)
guard let cell = cell else {return UITableViewCell()}
let cellLabel = UILabel()
cellLabel.text = tableView1Data[indexPath.row]
cellLabel.textColor = .black
cell.addSubview(cellLabel)
cellLabel.frame = cell.frame
}
else if tableView == self.tableView2 {
cell = tableView.dequeueReusableCell(withIdentifier: "Cell2", for: indexPath)
guard let cell = cell else {return UITableViewCell()}
let cellLabel = UILabel()
cellLabel.text = tableView2Data[indexPath.row]
cellLabel.textColor = .black
cell.addSubview(cellLabel)
cellLabel.frame = cell.frame
}
return cell!
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return view.frame.height * 0.1
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//...
}
}
You need to somehow constrain the height of either tableView1 or tableView2 (Once you have established 1 height, the other can be inferred). At the moment you have told autolayout that the two table views must be 15 apart vertically, but not where that separation is in terms of the superview. As a result, autolayout is probably sizing tableView1 to the full height of the superview and the other tableview isn’t on screen, so it’s datasource methods don’t get called
For example, to set the table views to half the screen each:
TableView1!.bottomAnchor.constraint(equalTo:self.view.centerYAnchor, constant:-7).isActive=true
one caveat with tableView is that it's datasource method will not get called unless view has it's frame . If tableView try to render it self and doesn't find it's frame valid data source method will never get's called.
You need to check if both of the tableView has it's frame defined

Unable to select the correct first row in UITableView

I have a table row with 6 rows. I want to have the background colour of the first row to be different but the background color of 4th row ( index 3 ) also changes. I am adding the screenshot of the same and here is the code
import UIKit
var items = ["admksalmdlsamdlsamdlkasmdlasmdlsamdlksamdlkasmdlkasmdlksamdklasmdlkasmdlkasmdlkasmdcjkndscnksndcdkcnksdjcnksdnckjsdncsdnjkanckjanckjsnckjasncjkasnckjasncjkasnckdslnjcvlkjdncjsnjkasncjkasncjkasnckjasncjkasnckjsanckjsanckjasncjksancjksancjksancjkasncjasncjkasncklas;NKCNDJKVALNVJKSDANCKSCNJSNCJKSDCNKJSDNCKJANCDKNLSACNJDKCNJKNCAKJNACJKNKJANCKJSNC","badmksalmdlsamdlsamdlkasmdlasmdlsamdlksamdlkasmdlkasmdlksamdklasmdlkasmdlkasmdlkasmdcjkndscnksndcdkcnksdjcnksdnckjsdncsdnjkanckjanckjsnckjasncjkasnckjasncjkasnckdslnjcvlkjdncjsnjkasncjkasncjkasnckjasncjkasnckjsanckjsanckjasncjksancjksancjksancjkasncjasncjkasncklas;NKCNDJKVALNVJKSDANCKSCNJSNCJKSDCNKJSDNCKJANCDKNLSACNJDKCNJKNCAKJNACJKNKJANCKJSNC", "cadmksalmdlsamdlsamdlkasmdlasmdlsamdlksamdlkasmdlkasmdlksamdklasmdlkasmdlkasmdlkasmdcjkndscnksndcdkcnksdjcnksdnckjsdncsdnjkanckjanckjsnckjasncjkasnckjasncjkasnckdslnjcvlkjdncjsnjkasncjkasncjkasnckjasncjkasnckjsanckjsanckjasncjksancjksancjksancjkasncjasncjkasncklas;NKCNDJKVALNVJKSDANCKSCNJSNCJKSDCNKJSDNCKJANCDKNLSACNJDKCNJKNCAKJNACJKNKJANCKJSNCSDNSAJKDNAKLSMDLAKSDMKLASMDLKASMDLKASMDLKASMDLKASMDLKASMDLKASMDLKASMDLKASMDLKASMLAKSMDLASMDLAKSMDLKASMDLKASMDLKASMLKADSMLKASMDLKASMDLKMadmksalmdlsamdlsamdlkasmdlasmdlsamdlksamdlkasmdlkasmdlksamdklasmdlkasmdlkasmdlkasmdcjkndscnksndcdkcnksdjcnksdnckjsdncsdnjkanckjanckjsnckjasncjkasnckjasncjkasnckdslnjcvlkjdncjsnjkasncjkasncjkasnckjasncjkasnckjsanckjsanckjasncjksancjksancjksancjkasncjasncjkasncklas;NKCNDJKVALNVJKSDANCKSCNJSNCJKSDCNKJSDNCKJANCDKNLSACNJDKCNJKNCAKJNACJKNKJANCKJSNCishan","cool1","hsjndjksndjkasnd","nsjdnknkjandjknasdka"]
class ViewController: UIViewController,UITableViewDelegate {
let basicCellIdentifier = "BasicCell"
#IBOutlet weak var tableView: UITableView!
func configureTableView() {
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 160.0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier(basicCellIdentifier) as BasicCell
if ( indexPath.row == 0){
cell.userInteractionEnabled = false
cell.backgroundColor = UIColor.grayColor()
}
println(indexPath.row)
cell.titleLabel.text = items[indexPath.row]
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
override func viewDidLoad() {
super.viewDidLoad()
println(items.count)
//tableView.reloadData()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
As you are reusing your cells via dequeueReusableCell with identifier as-
let cell = tableView.dequeueReusableCellWithIdentifier(basicCellIdentifier) as BasicCell
according to your requirements you have the background color of the cell for index path 0 to gray color as
if ( indexPath.row == 0)
cell.backgroundColor = UIColor.grayColor()
When this cell is reused for the 4th row(index path.row = 3), than it already has the background color set earlier for row 0 (to gray color), that's why all the reused cell which are reusing the cell of 0th index will be shown with the gray color.
To resolve this issue just add the else case with the above if statement as
if ( indexPath.row == 0)
cell.backgroundColor = UIColor.grayColor()
else
cell.backgroundColor = UIColor.whiteColor()

Stuck understanding how to create a table with multiple columns in iOS Swift

I've spent the better half of the day so far researching and trying to understand how to make a table with multiple columns. Embarrassingly, I am still quite new to Swift and programming in general so a lot of the stuff I've read and found aren't helping me too much.
I have basically found exactly what I want to create with this gentleman's blo:
http://www.brightec.co.uk/blog/uicollectionview-using-horizontal-and-vertical-scrolling-sticky-rows-and-columns
However, even with his Github I'm still confused. It seems as if he did not use Storyboard at all (and for my project I've been using storyboard a lot). Am I correct in assuming this?
What I have so far is a UICollectionView embedded in a navigation controller. From here, I have created a new cocoa touch class file subclassed in the CollectionView. But from here is where I'm not entirely sure where to go.
If I can have some direction as to where to go from here or how to properly set it up that would be GREATLY appreciated.
Thanks so much in advance!
IOS 10, XCode 8, Swift 3.0
I found an awesome tutorial on this. thanks to Kyle Andrews
I created a vertical table which can be scrollable on both directions by subclassing UICollectionViewLayout. Below is the code.
class CustomLayout: UICollectionViewLayout {
let CELL_HEIGHT: CGFloat = 50
let CELL_WIDTH: CGFloat = 180
var cellAttributesDictionary = Dictionary<IndexPath, UICollectionViewLayoutAttributes>()
var contentSize = CGSize.zero
override var collectionViewContentSize: CGSize {
get {
return contentSize
}
}
var dataSourceDidUpdate = true
override func prepare() {
let STATUS_BAR_HEIGHT = UIApplication.shared.statusBarFrame.height
let NAV_BAR_HEIGHT = UINavigationController().navigationBar.frame.size.height
collectionView?.bounces = false
if !dataSourceDidUpdate {
let yOffSet = collectionView!.contentOffset.y
for section in 0 ..< collectionView!.numberOfSections {
if section == 0 {
for item in 0 ..< collectionView!.numberOfItems(inSection: section) {
let cellIndexPath = IndexPath(item: item, section: section)
if let attrs = cellAttributesDictionary[cellIndexPath] {
var frame = attrs.frame
frame.origin.y = yOffSet + STATUS_BAR_HEIGHT + NAV_BAR_HEIGHT
attrs.frame = frame
}
}
}
}
return
}
dataSourceDidUpdate = false
for section in 0 ..< collectionView!.numberOfSections {
for item in 0 ..< collectionView!.numberOfItems(inSection: section) {
let cellIndexPath = IndexPath(item: item, section: section)
let xPos = CGFloat(item) * CELL_WIDTH
let yPos = CGFloat(section) * CELL_HEIGHT
let cellAttributes = UICollectionViewLayoutAttributes(forCellWith: cellIndexPath)
cellAttributes.frame = CGRect(x: xPos, y: yPos, width: CELL_WIDTH, height: CELL_HEIGHT)
// Determine zIndex based on cell type.
if section == 0 && item == 0 {
cellAttributes.zIndex = 4
} else if section == 0 {
cellAttributes.zIndex = 3
} else if item == 0 {
cellAttributes.zIndex = 2
} else {
cellAttributes.zIndex = 1
}
cellAttributesDictionary[cellIndexPath] = cellAttributes
}
}
let contentWidth = CGFloat(collectionView!.numberOfItems(inSection: 0)) * CELL_WIDTH
let contentHeight = CGFloat(collectionView!.numberOfSections) * CELL_HEIGHT
contentSize = CGSize(width: contentWidth, height: contentHeight)
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var attributesInRect = [UICollectionViewLayoutAttributes]()
for cellAttrs in cellAttributesDictionary.values {
if rect.intersects(cellAttrs.frame) {
attributesInRect.append(cellAttrs)
}
}
return attributesInRect
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return cellAttributesDictionary[indexPath]
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}
Below is my CollectionViewController Code.
import UIKit
private let reuseIdentifier = "Cell"
class VerticalCVC: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.isScrollEnabled = true
}
// MARK: UICollectionViewDataSource
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 20
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! CustomCell
if indexPath.section == 0 {
cell.backgroundColor = UIColor.darkGray
cell.titleLabel.textColor = UIColor.white
} else {
cell.backgroundColor = UIColor.white
cell.titleLabel.textColor = UIColor.black
}
cell.titleLabel.text = "section: \(indexPath.section) && row: \(indexPath.row)"
return cell
}
}
To force CollectionView to use Custom Layout instead of UICollectionViwFlowLayout check below image.
Result:
Portrait mode
landscape mode
One approach is to use a custom cell in a tableviewcontroller. Your story board consists of a table in which the cell is a custom cell with UILabels for columns laid out next to each other (with properly defined constraints).
Example code for the controllers looks like:
import UIKit
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath) as TableViewCell
cell.column1.text = "1" // fill in your value for column 1 (e.g. from an array)
cell.column2.text = "2" // fill in your value for column 2
return cell
}
}
and:
import UIKit
class TableViewCell: UITableViewCell {
#IBOutlet weak var column1: UILabel!
#IBOutlet weak var column2: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
In IB I set up a tableview and added a stackview in the content view (can be done programmatically). The labels are setup programmatically since it allows me to set the width of each column as a fraction of the cell width. Also, I acknowledge that some of the calculations inside the table view cellForRow method should be moved out.
import UIKit
class tableViewController: UITableViewController {
var firstTime = true
var width = CGFloat(0.0)
var height = CGFloat(0.0)
var cellRect = CGRectMake(0.0,0.0,0.0,0.0)
let colors:[UIColor] = [
UIColor.greenColor(),
UIColor.yellowColor(),
UIColor.lightGrayColor(),
UIColor.blueColor(),
UIColor.cyanColor()
]
override func viewDidLoad() {
super.viewDidLoad()
// workaround to get the cell width
cellRect = CGRectMake(0, 0, self.tableView.frame.size.width ,44);
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 3
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
var cellWidth = CGFloat(0.0)
var cellHeight = CGFloat(0.0)
let widths = [0.2,0.3,0.3,0.2]
let labels = ["0","1","2","3"]
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
let v = cell.contentView.subviews[0] // points to stack view
// Note: using w = v.frame.width picks up the width assigned by xCode.
cellWidth = cellRect.width-20.0 // work around to get a right width
cellHeight = cellRect.height
var x:CGFloat = 0.0
for i in 0 ..< labels.count {
let wl = cellWidth * CGFloat(widths[i])
let lFrame = CGRect(origin:CGPoint(x: x,y: 0),size: CGSize(width:wl,height: cellHeight))
let label = UILabel(frame: lFrame)
label.textAlignment = .Center
label.text = labels[i]
v.addSubview(label)
x = x + wl
print("i = ",i,v.subviews[i])
v.subviews[i].backgroundColor = colors[i]
}
return cell
}
}

Resources