Creating UITableView Cells ONCE Before They are Visible | Swift - swift

Each of my EntryCell: UITableViewCells has an id number that get initialized and saved when the cell is created. My problem is that the cells can get initialized more than once, resulting in different cells being created with different cell ids and different details.
Here is my minimal, reproducible code for the cell initialization:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCellType", for: indexPath) as! MyCellType
cell.textLabel.text = myCellType.id
return cell
}
Here is my minimal, reproducible code for the MyCellType.swift file:
class EntryCell: UITableViewCell {
#IBOutlet weak var textLabel: UILabel!
var id = 0
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
id = UserDefaults.standard.integer(forKey: "nextID")
UserDefaults.standard.set(UserDefaults.standard.integer(forKey: "nextID") + 1, forKey: "nextID")
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
When you scroll down far enough that some cells aren't visible, it essentially deletes them ( I think from my research so far ), so when you scroll back up it creates a new, higher id for the new cell.
My question is: is there a way to not have the cells initialize more than once because they are becoming visible multiple times? Or can I have them all initialize at the beginning and never get deleted after that?

Related

How to update different cells with different identifiers in swift 5

I am new to swift and I have this app in mind that basically needs to update all the cells based on the data received from one of the cells.
I am using dynamic cells and each one has a textfield inside.
TableViewController.swift:
import UIKit
class MyCells: UITableViewCell{
#IBOutlet weak var value_textfield: UITextField!
}
class TableViewController: UITableViewController {
var units_length: [String] = ["Centimeter", "Meter", "Foot"]
override func viewDidLoad() {
super.viewDidLoad()
units_length = units_length.sorted()
self.tableView.keyboardDismissMode = .onDrag
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return units_length.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = units_length[indexPath.row]
cell.textLabel?.textColor = UIColor.white
cell.textLabel?.font = UIFont(name:"DINAlternate-Bold", size:30)
return cell
}
override func viewWillAppear(_ animated: Bool){
tableView.rowHeight = 100
}
}
Which function should I use to access all the cells with different identifiers and simultaneously update all the textfield inside each cell?
I do have access to the textfield by having another class called MyCells, how should I implement it?
(I will not include the base ViewController class here as there's nothing inside)
Thanks!
Normally you use the function you already showed: cellForRowAt. You update your model data, tell the table to reloadData, cellForRowAt is called for all visible cells, and their values become the new values.
However if you are updating after every character, I would set up a notification broadcast situation and update all visible text fields and the model together without a reload. Just make sure the model and the table stay in sync in case the user scrolls and cellForRow is called.

How to keep an object's reference when using UITableView?

The problem I'm facing is probably some lack of understanding on the concept of reusable cells. I have, let's say, 30 rows to be created and each of them has a UISwitch.
When I toggle one of the switches, it's behavior should affect the other 29. The point is: as far as I know, iOS doesn't create all of them at once, but rather wait to reuse the cells when the TableView is scrolled up and down.
How can I keep a copy of those reused objects and tell iOS to set the proper value to the switches?
I've thought on having the cells appended to a [UISwitch] but I can't manage to have all the 30 cells in there, look:
...
var switches = [UISwitch]()
...
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Field10Cell", for: indexPath) as! Field10TableViewCell
...
//cell.value is a UISwitch
if !switches.contains(cell.value) {
switches.append(cell.value)
}
return cell
}
You could create a set which stores the indexes of the cells whose switches have been pressed.
var activeSwitches = Set<IndexPath>()
Whenever a user presses the switch on a cell, you store it on the set like this:
activeSwitches.insert(indexPath)
If you need to check if a switch was activated, just check if its container cell's indexPath is in active switches like so:
if activeSwitches.contains(indexPath) {
// do something
}
In order to know when a user pressed a specific switch I recommend the folliwing:
In cellForRowAtIndexPath save the current indexPath into your Field10TableViewCell.
Create a protocol on Field10TableViewCell and add a delegate.
protocol Field10Delegate {
func didChangeSwitch(value: Bool, indexPath: IndexPath)
}
class Field10TableViewCell {
var delegate: Field10Delegate?
var indexPath: IndexPath?
#IBOutlet weak var fieldSwitch: UISwitch! // Can't use 'switch' as a variable name
#IBAction func switchValueChanged(_ sender: UISwitch) {
if let indexPath = indexPath {
delegate?.didChangeSwitch(value: sender.isOn, indexPath: indexPath)
}
}
When you create a cell, set the view controller as a delegate
let cell = tableView.dequeueReusableCell(withIdentifier: "Field10Cell", for: indexPath) as! Field10TableViewCell
cell.delegate = self
cell.indexPath = indexPath
Make your view controller comply with the protocol:
extension ViewController: Field10Delegate {
/* Whenever a switch is pressed on any cell, this delegate will
be called. This is a good place also to trigger a update to
your UI if it has to respond to switch changes.
*/
func didChangeSwitch(value: Bool, indexPath: IndexPath) {
if value {
activeSwitches.insert(indexPath)
} else {
activeSwitches.remove(indexPath)
}
updateUI()
}
}
With the above, at any point you will know which switches are active or not and you can process the dequeued cells with this information.

Label moves to the left when table cell was reused

I have a table with a custom table cell in it. That's how it looks at startup:
Now, when I scroll so that the cell disappears and I let go, the cell comes back to the screen, but it now looks like this:
The table data is fetched using NSFetchedResultsController. I've done exactly this multiple times before, this never happened.
My cellForRowAtmethod:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "bitCell", for: indexPath) as! BitCell
let entry = fetchedResultsController.object(at: indexPath)
cell.configure(cellWithText: entry.text!)
return cell
}
My custom BitCell class
class BitCell: UITableViewCell {
#IBOutlet weak var bitTextLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
//selectionStyle = .none
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func configure(cellWithText text: String) {
//bitTextLabel.text = text
}
}
And my constraints for the label:
Let me know, if you need anything more (I will edit the question with additional information)
Thanks!

UISegment value changing when tableview get scrolled

I am using UISegmentControl to display objective type questions in table view. But, if I select one segment in any one of cell, then if I scroll, some segment values are changed. I dont know how to solve that.
Kindly guide me.
Cell size : 160px
Segment tint color : blue color
Coding
//UIViewController
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = segTblVw.dequeueReusableCellWithIdentifier("segment", forIndexPath: indexPath) as! segmentTblCell
return cell
}
//UITableViewCell CLASS
class segmentTblCell: UITableViewCell {
#IBOutlet weak var segMent: UISegmentedControl!
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
}
}
Screen shot below:
You're having this problem because of how dequeueReusableCellWithIdentifier: works. Each time a cell get scrolled out of screen, it enters a cache area where it will be reused.
Let's say you have 100 cells. All their segmentedControl objects are set to first. You tap on one to change it's value. As the cell moves out of view, it enters the cache, where it will be dequeued if you scroll down further.
It's important to understand this, because the segmentedControl object is not actually changing. It looks like it's changing because of the dequeue behaviour.
To solve this problem, you will need to implement a dataSource that stores the segmentedControl object's value so you can reinitialize it correctly every time a cell is dequeued.
Method 1: Prevent reusability of cells by, Holding all cell objects in an array
var arraysCells : NSMutableArray = []//globally declare this
in viewDidLoad()
for num in yourQuestionArray//this loop is to create all cells at beginning
{
var nib:Array = NSBundle.mainBundle().loadNibNamed("SegmentTableViewCell", owner: self, options: nil)
var cell = nib[0] as? SegmentTableViewCell
arraysCells.addObject(cell!);
}
in tableViewDelegate,
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return arraysCells.objectAtIndex(indexPath.row) as! UITableViewCell
}
you can find the selected segment values (answer) by iterating arraysCells
NOTE: Method 1 will be slow, if you have big number of cells
Method 2: Reuse the cell as normal, but save the states(enterd values) Using Delegate and arrays.
in custom UITableViewCell
#objc protocol SegmentTableViewCellDelegate {
func controller(controller: SegmentTableViewCell, selectedSegmentIndex:Int, indexPath : NSIndexPath)
}
class SegmentTableViewCell: UITableViewCell {
var delegate: AnyObject?
var indexPath : NSIndexPath?
#IBOutlet weak var segment: UISegmentedControl! //outlet of segmented Control
#IBAction func onSegmentValueChanged(sender: UISegmentedControl/*if the parameter type is AnyObject changed it as UISegmentedControl*/)//action for Segment
{
self.delegate?.controller(self, selectedSegmentIndex: sender.selectedSegmentIndex, indexPath: indexPath!)
}
in viewController
class MasterViewController: SegmentTableViewCellDelegate{
var selectedAnswerIndex : NSMutableArray = [] //globally declare this
var selectedSegmentsIndexPath : NSMutableArray = [] //globally declare this
func controller(controller: SegmentTableViewCell, selectedSegmentIndex:Int, indexPath : NSIndexPath)
{
if(selectedSegmentsIndexPath.containsObject(indexPath))
{
selectedAnswerIndex.removeObjectAtIndex(selectedSegmentsIndexPath.indexOfObject(indexPath))
selectedSegmentsIndexPath.removeObject(indexPath)
}
selectedAnswerIndex.addObject(selectedSegmentIndex)
selectedSegmentsIndexPath.addObject(indexPath)
}
in cellForRowAtIndexPath (tableView Delegate)
if(selectedSegmentsIndexPath.containsObject(indexPath))
{
cell?.segment.selectedSegmentIndex = selectedAnswerIndex.objectAtIndex(selectedSegmentsIndexPath.indexOfObject(indexPath)) as! Int
}
cell?.delegate = self
cell?.indexPath = indexPath
you can get the result by
for index in selectedSegmentsIndexPath
{
var cellIndexPath = index as! NSIndexPath
var answer : Int = selectedAnswerIndex.objectAtIndex(selectedSegmentsIndexPath.indexOfObject(cellIndexPath)) as! Int
NSLog("You have enterd answer \(answer) for question number \(cellIndexPath.row)")
}
#KelvinLau's is perfect
you can do that by using var segmentedTracker : [NSIndexPath:Int] = [:]
on segmentedValueChanged set the value of the selectedIndex ie: segmentedTracker[indexPath] = valueOf the selected index
then in cellForRowAtIndexPath check for the value let selected = [segmentedTracker]
cell.yourSegmentedControlReference.selectedIndex = selected
please note this is a pseudocode I don't remember the properties name. From here you can figure it out by urself
I think to use UISegmentControl in UITableViewCell may be wrong.
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UISegmentedControl_Class/
I have never seen the kind of that in iOS application.
The problem is that UITableViewCell is reused by dequeueReusableCellWithIdentifier method. So some UISegmentControl values are changed when scrolling.
Although it is not best solution, you can use Static Cells. What you need to do is that only switch Static cells. And If so, you don't write code of UITableViewCell.
Year 2018: Updated Answer
Find my easiest answer in this UISegement inside UITableViewCell
=======================================================
Year 2015
I have tested in my own way. My coding is below. Kindly guide me, whether it is right way or wrong way? My problem get solved. This code stops reusable cell.
My Coding Below:
//UIViewController
var globalCell = segmentTblCell() //CUSTOM UITableViewCell Class
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = segTblVw.dequeueReusableCellWithIdentifier("segment", forIndexPath: indexPath) as! segmentTblCell
globalCell = segTblVw.dequeueReusableCellWithIdentifier("segment", forIndexPath: indexPath) as! segmentTblCell //THIS LINE - STOPS REUSABLE TABLE.
return cell
}

Issue with Swift TableViewCell: change background color for selected row

I have a strange issue with my tableView.
I have a List of audio tracks and a segue to an audio player in order to play the selected track at a specific row. Everything works fine!
I wanted to change the background color for the selected row in the table so that, once the user play the audio and come back to the list of tracks (my Table View Controller) , he can see which are the previously selected rows.
But when I run It change me the color not only for the row at index path I selected but also to the item at index path + 10.
If I select the First Row it change me the color for the row at the index: 0, 10, 20, 30...
In order to change the color of the selected cell I did the follow:
// MARK: - Navigation
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("audioPlayer", sender: tableView)
var selectedCell:UITableViewCell = tableView.cellForRowAtIndexPath(indexPath) as CustomTableViewCell
selectedCell.contentView.backgroundColor = UIColor.greenColor()
selectedCell.backgroundColor = UIColor.blueColor()
Please find a screenshot of my issue, I have selected just three rows: 1, 3, 5 but I get selected 1,3,5,11,13,15,21,23 and so on... :
https://www.dropbox.com/s/bhymu6q05l7tex7/problemaCelleColore.PNG?dl=0
For further details - if can help - here it is my Custom Table View class:
import UIKit
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var artista: UILabel!
#IBOutlet weak var brano: UILabel!
var ascoltato = false
#IBOutlet weak var labelRiproduciAscoltato: 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
}
func setCell(artista: String, brano: String){
self.artista.text = artista
self.brano.text = brano
}
} // END MY CUSTOM TABLE VIEW CELL
Here it is the method cellForRowAtIndexPath in my TableViewController:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("tableCell", forIndexPath: indexPath) as CustomTableViewCell
var tracks : Brani //Brani is my custom Object for handle my tracks
cell.setCell(tracks.title!, brano: tracks.author!)
return cell
}
I am running on iPad Air with iOS 7.1.
Thank you in advance for any suggestion or advice related to my issue.
This is probably because UITableViewCells are recycled. This means the formerly selected tableViewCell gets reused by the cells at the lower indexes. This is expected behavior of a UITableView and makes sense, as it saves memory usage. To fix the issue, you will need to have your datasource keep a track of which cell is selected, and updated the cell's background color accordingly.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("audioPlayer", sender: tableView)
//datasource is updated with selected state
//cell is updated with color change
}
Then in your cellForRowAtIndexPath method:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("tableCell", forIndexPath: indexPath) as CustomTableViewCell
var tracks : Brani //Brani is my custom Object for handle my tracks
cell.setCell(tracks.title!, brano: tracks.author!)
//update cell style here as well (by checking the datasource for selected or not).
return cell
}