How to update textLabel inside a tableview cell iOS Swift - ios

I have created a table view programatically, below is the code, when I tried to update the label using a completion handler the value is not displayed in the table view cell.
Could someone please suggest me how I could update the textLabel in the languagesSpokenCell in my tableview ? I have spent several hours trying to get this resolved, but I am still unable to.
Here is the completion Handler that is called after the user selects the languages that he speaks.
func showLanguagesSpoken(_ languagesSpoken: [String]?){
        languagesSpokenString = (languagesSpoken?.joined(separator: ", "))!
        languagesSpokenCell.textLabel?.text = languagesSpokenString
       //*** In the below print statement the value is printed correctly but the text label is not updated in the cell. 
print("languagesSpokenCell.textLabel?.text: \(languagesSpokenCell.textLabel?.text)")
        self.tableView.reloadData()
    }
Here I am Programatically creating table view cells
// FOR TABLE VIEW - Tableview cells
    var tableView: UITableView  =   UITableView()
    var firstNameCell: UITableViewCell = UITableViewCell()
    var lastNameCell: UITableViewCell = UITableViewCell()
    var languagesSpokenCell: UITableViewCell = UITableViewCell()
    
    
    // FOR TABLE VIEW - Textfields
    var firstName: UITextField = UITextField()
    var lastName: UITextField = UITextField()
override func loadView() {
        super.loadView()
        
        // construct first name cell, section 0, row 0
        self.firstNameCell.backgroundColor = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.5)
        self.firstName = UITextField(frame: self.firstNameCell.contentView.bounds.insetBy(dx: 15, dy: 0))
        self.firstName.placeholder = "First Name"
        self.firstNameCell.addSubview(self.firstName)
        
        // construct last name cell, section 0, row 1
        self.lastNameCell.backgroundColor = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.5)
        self.lastName = UITextField(frame: self.lastNameCell.contentView.bounds.insetBy(dx: 15, dy: 0))
        self.lastName.placeholder = "Last Name"
        self.lastNameCell.addSubview(self.lastName)
        
self.languagesSpokenCell.textLabel?.text = "Languages Spoken"
        self.languagesSpokenCell.backgroundColor = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.5)
        self.languagesSpokenCell.accessoryType = UITableViewCellAccessoryType.disclosureIndicator
  
    }
Below are the Table view methods
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
// Return the number of rows for each section in your static table
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch(section) {
case 0: return 2 // section 0 has 2 rows
case 1: return 1 // section 1 has 1 row
default: fatalError("Unknown number of sections")
}
}
// Return the row for the corresponding section and row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch(indexPath.section) {
case 0:
switch(indexPath.row) {
case 0: return self.firstNameCell // section 0, row 0 is the first name
case 1: return self.lastNameCell // section 0, row 1 is the last name
default: fatalError("Unknown row in section 0")
}
case 1:
switch(indexPath.row) {
case 0: return self.languagesSpokenCell
}
default: fatalError("Unknown section")
}
}
// Customize the section headings for each section
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
switch(section) {
case 0: return "Profile"
case 1: return "Languages Spoken"
default: fatalError("Unknown section")
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let row = indexPath.row
let section = indexPath.section
let currentCell = tableView.cellForRow(at: indexPath) as! UITableViewCell
if section == 4 && row == 0 {
// The user has clicked on languages spoken cell
self.performSegue(withIdentifier: "showLanguageSelectionTVC", sender: self)
}
print("Printing celll text label: \(currentCell.textLabel!.text)")
}
Below are the constraints that I have set in the ViewDidLoad() method.
tableView = UITableView(frame: CGRect.zero, style: UITableViewStyle.grouped)
tableView.delegate = self
tableView.dataSource = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
self.view.addSubview(self.tableView)
// Disabling automatic constraints
tableView.translatesAutoresizingMaskIntoConstraints = false
// Do any additional setup after loading the view.
let viewDict = [
"tableView" : tableView
]
// Setting Constraints for the table view
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[tableView]|", options: [], metrics: nil, views: viewDict))
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[tableView]|", options: [], metrics: nil, views: viewDict))

To update any cell in tableview you need to reload it. You can reload it per cell or or specific cells or whole tableview. Before reloading tableview make sure that you update your datasource.
In this showLanguagesSpoken method you are trying to change the text of cell without updating the tableView.
Also make sure you always changes the data in cellForRowAtIndexpath

Remove reloadData from following method.
func showLanguagesSpoken(_ languagesSpoken: [String]?){
languagesSpokenString = (languagesSpoken?.joined(separator: ", "))!
languagesSpokenCell.textLabel?.text = languagesSpokenString
//*** In the below print statement the value is printed correctly but the text label is not updated in the cell.
print("languagesSpokenCell.textLabel?.text: \(languagesSpokenCell.textLabel?.text)")
}

Label languagesSpokenCell.textLabel text update process should be on main thread.
func showLanguagesSpoken(_ languagesSpoken: [String]?) {
DispatchQueue.main.async {
languagesSpokenString = (languagesSpoken?.joined(separator: ", "))!
languagesSpokenCell.textLabel?.text = languagesSpokenString
print("languagesSpokenCell.textLabel?.text: \(languagesSpokenCell.textLabel?.text)")
}
}

Related

How to show/hide label in a different view when UISwitch isOn in swift?

I have a UISwitch component in my CreateSomethingViewController. This component is on a xib file.
In my SomethingTableViewCell, I have a label called existsLabel.
When I create my something, I can select as Existent (if I turn my UISwitch component on) or not (if Switch is off).
If my existsLabel was in my CreateSomethingViewController, I would do something like this:
#IBAction func changeSomethingExistence(_ sender: UISwitch) {
let isExistent = sender.isOn
existsLabel.isHidden = false
if isExistent {
existsLabel.isHidden = true
}
}
How can I do this (show my existsLabel on my SomethingTableViewCell) when my UISwitch isOn? Using swift.
I think, you already knew the index or position of your updated objects. So We can reload only visible cells row after updating on particular objects to the index position of your cell.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? YourTableViewCell
cell?.yourSwitch.isOn = yourList[indexPath.row].switchIsOne
cell?.yourSwitch.tag = indexPath.row
cell?.yourSwitch.addTarget(self, action: #selector(changeSomethingExistence), for:UIControl.Event.valueChanged)
cell?.existsLabel.isHidden = !yourList[indexPath.row].switchIsOne
return cell!
}
Here is your Switch update actions:
#objc func changeSomethingExistence(mySwitch: UISwitch) {
yourList[mySwitch.tag].switchIsOne = mySwitch.isOn
self.updateCell(indexRow: mySwitch.tag)
}
Call this function from anywhere with your selected index and update the same.
func updateCell(indexRow: Int) {
let updatedIndexPath = IndexPath(row: indexRow, section: 0)
self.tableView.reloadRows(at: [updatedIndexPath], with: .automatic)
}
Here's an example. Instead of hiding and showing a view, I set the background color of the cells. The basic ideas are the same.
Essentially you need an object to store the value that the switch controls. In this case I store that data in the same object that I used as the UITableViewDataSource. When the switch is flipped, you tell that object to change the value. It will broadcast the change to all the cells that are currently listening for the change.
There are lots of ways you could observe the change. You could use the Target Action pattern, you could broadcast the change using the NSNotificationCenter. You could use key/value observers, etc. In this case the object holding the value has an #Published property and the cells subscribe to that property.
One critical thing to do is implement prepareForReuse. When a cell is scrolled off the view, it is put in a reuse queue. Rather than create a new cell the system might hand you one out of the reuse buffer. If it does that, you want to be sure the cell is listening to the right source of information for things that change dynamically.
You should be able to copy/paste this code into an iOS Playground:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
import Combine
class CustomCell : UITableViewCell {
var cancelBackgrounds : AnyCancellable?
override func prepareForReuse() {
cancelBackgrounds?.cancel()
cancelBackgrounds = nil
// ALWAYS call super... this can cause hard to identify bugs
super.prepareForReuse()
}
func observeFancyBackground(dataSource: TableData) {
// Set up to observe when the fanch Background value changes
// If this cell was listening to someone else, stop listening to them
// and start listeneing to the new guy.
// This may not be necessary - its a safety check.
cancelBackgrounds?.cancel()
cancelBackgrounds = nil
// Start listening to the new information source
cancelBackgrounds = dataSource.$showFancyBackgrounds.sink(receiveValue: {
isOn in
self.setBackground(isOn)
})
}
private func setBackground(_ showFancy: Bool) {
if showFancy {
self.backgroundConfiguration?.backgroundColor = UIColor.yellow
} else {
self.backgroundConfiguration?.backgroundColor = UIColor.white
}
}
}
class TableData : NSObject, UITableViewDataSource {
let tableData = (1...1000).map { "\(Int($0))" }
#Published var showFancyBackgrounds = false
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.textLabel?.text = tableData[indexPath.row]
cell.observeFancyBackground(dataSource: self)
return cell
}
}
class MyViewController : UIViewController {
let switchView = UISwitch()
let tableView = UITableView(frame: CGRect(x: 0, y: 200, width: 320, height: 100), style: .plain)
let tableData = TableData()
// This is the action called when the switch is toggled.
#objc func switchFlipped(sender: UISwitch) {
tableData.showFancyBackgrounds = sender.isOn
}
// This just sets things up to be pretty.
override func loadView() {
let view = UIView()
switchView.translatesAutoresizingMaskIntoConstraints = false
switchView.addTarget(self, action: #selector(switchFlipped), for: .valueChanged)
tableView.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
tableView.dataSource = tableData
view.addSubview(switchView)
view.addSubview(tableView)
self.view = view
let viewIDs = ["switch" : switchView,
"table" : tableView]
let constraints = [
NSLayoutConstraint.constraints(
withVisualFormat: "V:|-8-[switch]-[table]-|",
options: [],
metrics: nil,
views: viewIDs),
NSLayoutConstraint.constraints(
withVisualFormat: "|-[switch]-|",
options: [],
metrics: nil,
views: viewIDs),
NSLayoutConstraint.constraints(
withVisualFormat: "|-0-[table]-0-|",
options: [],
metrics: nil,
views: viewIDs),
].flatMap { $0 }
view.addConstraints(constraints)
}
}
let myViewController = MyViewController()
PlaygroundPage.current.liveView = myViewController
You can do this by reloading the tableView when the switch is changed.
var isExistent: Bool
#IBAction func changeSomethingExistence(_ sender: UISwitch) {
isExistent = sender.isOn
//reload the table
tableView.reloadData()
}
In your UITableViewDataSource you can check which cell.label need to be hidden or not and accordingly hide/show the label of those cells
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//decide which cells needs to be hide/show based on the indexPath and switchValue
//then you can call cell.existsLabel.isHidden = isExistent
}

Expanding Custom TableviewCell not working properly and displays incorrect view

I have a table view controller with a section cell and on click it expands and other subsections are showed. A section cell will always have title and a button and it may or may not have a description, on expanding the cell with no description, I am applying a centerYanchor on the title of the section cell so that it's aligned accordingly to the expand icon.
On expanding the cells which have a description it works as expected, also the section with no description has centerYanchor applied to it and works properly.
Now the problem that I am facing is as soon as I expand a cell with no description, the cells with description starts to behave weirdly on expanding.
As you can see the first two cells with description opened properly and other cells with no description is also aligned with the button.
In this case I opened the third cell first and on opening the first cell, even though it had description the centerYanchor and hiden logic is being applied to it.
Here is the code for tableViewController
override func numberOfSections(in tableView: UITableView) -> Int {
return tableData.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableData[section].opened == true{
return tableData[section].sectionData.count + 1
} else{
return 1
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0{
guard let cell = tableView.dequeueReusableCell(withIdentifier: "anotherCell", for: indexPath) as? tableCell else {
fatalError("The dequeued cell has thrown some error.")
}
cell.cellTitle.text = tableData[indexPath.section].title
cell.cellDescription.text = tableData[indexPath.section].description
cell.setData = tableData[indexPath.section].opened
return cell
} else{
guard let cell = tableView.dequeueReusableCell(withIdentifier: "subSectionCell", for: indexPath) as? subSectionTableViewCell else {
fatalError("The dequeued cell has thrown some error.")
}
cell.subSectionTitle.text = tableData[indexPath.section].sectionData[indexPath.row - 1]
return cell
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0 {
if tableData[indexPath.section].opened == true {
tableData[indexPath.section].opened = false
let sections = IndexSet.init(integer: indexPath.section)
tableView.reloadSections(sections, with: .none)
}
else{
tableData[indexPath.section].opened = true
let sections = IndexSet.init(integer: indexPath.section)
tableView.reloadSections(sections, with: .none)
}
}
}
Here is the code for hiding and applying the centerYanchor to the cell
var setData: Bool = false {
didSet{
setupCell()
}
}
func setupCell() {
if (cellDescription.text == "") {
cellDescription.isHidden = true
cellTitle.centerYAnchor.constraint(equalTo: button.centerYAnchor, constant: 4).isActive = true
}
if setData{
button.setImage(UIImage(named: "down"), for: .normal)
} else{
button.setImage(UIImage(named: "right"), for: .normal)
}
}
Please suggest me on how I should fix this, and if you have any doubts ask in the comments.
Cell Data Struct
struct cData {
var title = String()
var description = String()
var identifier = Int()
var opened = Bool()
var sectionData = [String]()
}
Cells constraints
Here is my suggested layout.
Yellow is the cell's contentView; orange is the View the contains the other elements; labels have cyan background.
Embed the labels in a UIStackView:
Give the "arrow button" a centerY constraint to the Description label, with Priority: 751 AND give it a centerY constraint to the Title label, with Priority: 750. That will automatically center it on the Description label when it is visible, and on the Title label when Description is hidden.
Then change your cell's setupCell() func as follows:
func setupCell() {
// set all subviews background colors to white
//[contentView, view, cellTitle, cellDescription, button].forEach {
// $0?.backgroundColor = .white
//}
// hide if no text, otherwise show
cellDescription.isHidden = (cellDescription.text == "")
if setData{
button.setImage(UIImage(named: "down"), for: .normal)
} else{
button.setImage(UIImage(named: "right"), for: .normal)
}
}
During dev, I like to use contrasting colors to make it easy to see the layout. If you un-comment the .forEach block, everything will get a white background. After I have my layout correct, I go back to Storyboard and set the background colors to white (or clear, or however I really want them) and remove the color setting from the code.
Looks like a cell reuse issue here:
if (cellDescription.text == "") {
cellDescription.isHidden = true
cellTitle.centerYAnchor.constraint(equalTo: button.centerYAnchor, constant: 4).isActive = true
}
Your cells are being reused, but you're never actually showing the cellDescription if it's available:
if (cellDescription.text == "") {
cellDescription.isHidden = true
cellTitle.centerYAnchor.constraint(equalTo: button.centerYAnchor, constant: 4).isActive = true
} else {
cellDescription.isHidden = false
cellTitle.centerYAnchor.constraint(equalTo: button.centerYAnchor, constant: 4).isActive = false
}

How do I reload just the tableView section headers?

So I'm trying to get the UITableView section headers to change in background and text color when switching from one segment to another on a segmented control. To so so I need to reload the tableView data but I only want to reload the section headers. When using
tableView.reloadData()
It works but it also resets the rest of the data in the tableView which I'm not trying to do.
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let stringForThemeHeader = "Theme Settings"
let stringForToggleHeader = "Password Settings"
if section == 0 {
return stringForThemeHeader
} else {
return stringForToggleHeader
}
}
That's the code for setting up the titles.
override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
let header = view as! UITableViewHeaderFooterView
if darkModeSegmentedControlSwitcher.selectedSegmentIndex == 1 || darkModeSegmentedControlSwitcher.selectedSegmentIndex == 2 {
view.tintColor = UIColor.red
header.textLabel?.textColor = UIColor.white
} else {
view.tintColor = UIColor.init(red: 247.0/255.0, green: 247.0/255.0, blue: 247.0/255.0, alpha: 1.0)
header.textLabel?.textColor = UIColor.black
}
}
and that's the code to change the text and background. After a segment is selected it doesn't change unless the data of the entire tableView is reloaded using
tableView.reloadData()
So I just need to know how to reload only the section headers and my problem will be solved.
You can just manually iterate the section headers.
for section in 0..<tableView.numberOfSections {
guard let header = tableView.headerView(forSection: section) else {
continue
}
// do something with the header
}
In case you have a lot of the sections you can iterate only visible headers. Just use tableView.visibleCells to collect the visible sections indexes.

TableView cell selection issue

So I selected a cell in tableview.
it expands, shows detail text of the that cell then I started scrolling down till this selected cell goes out of view.
because of reuse identifier other cell in the the view get some the property of the selected cell automatically.
Is there any way to handle this ?
var selectedIndex = -1
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
UIView.animate(withDuration: 1) {
self.labelViewHeightConstraint.constant = 60
self.labelLeadingConstraint.constant = 136
self.view.layoutIfNeeded()
}
let cell = tableView.cellForRow(at: indexPath) as! CustomCell
if(selectedIndex == indexPath.row) {
selectedIndex = -1
print("deselect")
UIView.animate(withDuration: 0.4) {
cell.secondView.isHidden = true
cell.firstView.backgroundColor = UIColor(red: 0.8588, green: 0.84705, blue: 0.8745, alpha: 1.0)
}
} else {
cell.secondView.isHidden = false
}
self.expandTableView.beginUpdates()
//self.expandTableView.reloadRows(at: [indexPath], with: UITableViewRowAnimation.automatic )
self.expandTableView.endUpdates()
}
And i have deselect tableview function as
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPathaddres , animated: true)
if let cell = tableView.cellForRow(at: indexPath) as? customCell {
cell.firstView.backgroundColor = UIColor(red: 0.8588, green: 0.84705, blue: 0.8745, alpha: 1.0)
print("deselected row")
}
}
I have already disabled multiple selection.
I am attaching the 2 screenshot.
I 1 is select first cell of tableview then scroll down https://drive.google.com/file/d/1RZlya_eVVjDzj02GKV9h0qJU8F29xMin/view?usp=drivesdk once that cell goes out of scope.
Jump server 3 (as seen in this screenshot gets selected) https://drive.google.com/file/d/15k4gLUPkgB6jGZ7AWR6km0Jajst9KKxM/view?usp=drivesdk get selected
Since tableview reuses its cells, you need to do some extra checks if you want your one cell to be different from the others.
Reset the cell to its default in prepareForReuse method. Like
hiding your view and reset the arrow direction in your case.
Check for selected index in your cellForRow method and expand your view like you do in your didSelectRow method and hide it if its not selected just as you do in your didDeselect method.

Can the same UITableviewcell be used in multiple UITableViews?

What I want to ask you is "Can one UITableviewcell be used for multiple tableview like viewholder that can use anytime for recyclerview in android?" what I used to do is in one viewcontroller I have a tableview with a custom Cell and gave its identifier as normal but if I trying to use another uitableview in another Viewcontroller with that cell that inside the previous tableview, it always gives me a blank cell with white background. is there a way to use it like that?
EDIT: Here is what my tableview look like when i've already set cellforrow for it already.
Click to view and here what my cell look like Click to view cell and here are my code for different cell in a tableview, It'll work if i use use those 2 cell in current tableview
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0{
let cell = self.mytable.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! HistoryItemTableCell
let view = UIView(frame: CGRect(x: 0, y: cell.frame.maxY, width: cell.frame.size.width, height: cell.frame.size.height))
view.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.3)
cell.selectedBackgroundView = view
let order = OrderItemObj
cell.num_of_day.text = "\(order.ticket_type.name)"
cell.ref_num.text = order.order_tx_number
cell.quantity.text = order.number_tickets
cell.price.text = "$\(order.ticket_type.price).00 USD"
if order.status == "unpaid"{
cell.ic_status.image = UIImage(named: "ic_status_unpaid")
}else{
cell.ic_status.image = UIImage(named: "ic_status_paid")
}
cell.start_date.text = "\(order.start_date)"
cell.end_date.text = "\(order.expired_date)"
return cell
}else{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! OrderDetailTicketCell
let t = listTicket[indexPath.row]
cell.dob.text = t.dob
cell.gender.text = t.sex
cell.nation.text = t.nationality
let url = URL(string: t.photo)
cell.imageN.kf.setImage(with: url)
return cell
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 3
}else{
return self.listTicket.count
}
}
override func viewDidLoad(){
super.viewDidLoad()
mytable.register(HistoryItemTableCell.self, forCellReuseIdentifier: "cell")
ViewHistoryItem()
mytable.dataSource = self
mytable.delegate = self
}
Yes you can. You have to register it again for the new tableView. It is just like how you create variables using the same type. This is also a class which can be used to create objects. Doesn't matter where you want to use it.
On the other hand if you are asking if instances of the same cell which are present in a tableView can be reused in another tableView, then the answer is no, because they have only been registered for that particular tableView.

Resources