UITableView reloadData() overwrites previous cell data - swift

I'm dynamically creating a UITableView and populating it with an Array, which is populated my a Dictionary. Issue is that when I make some changes in Dictionary, re-populate the Array accordingly and reload tableview data, previous cell datas remain and new data is written over the previous ones. I tried lots of workarounds, like changing the order of method calls, using viewWithTag on subviews, removing subviews from superviews before adding again etc. None worked, since most of the answers were old and had to do with "(cell == nil)" issue whereas I'm not taking that path. Here is the code:
var sepetDict: [String: Int] = [:]
var sepetDictCopy = [String]()
override fun viewDidLoad() {
sepetTableView.frame = CGRectMake(0, 0, sepetDialogView.bounds.size.width, sepetDialogView.bounds.size.height)
sepetTableView.delegate = self
sepetTableView.dataSource = self
sepetTableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "sepetCell")
}
func sepetClick() {
sepetDictCopy.removeAll()
sepetDictCopy = Array(sepetDict.keys)
sepetTableView.reloadData()
sepetTableView.removeFromSuperview()
self.view.addSubview(sepetDialogView)
sepetDialogView.addSubview(sepetTableView)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let sepetCell = sepetTableView.dequeueReusableCellWithIdentifier("sepetCell", forIndexPath: indexPath)
let sepetNameLabel = UILabel()
sepetNameLabel.frame = CGRectMake(0, 0, sepetDialogView.bounds.size.width / 2, 60)
sepetNameLabel.center = CGPointMake(25 + sepetDialogView.bounds.size.width / 4 , 30)
sepetNameLabel.font = sepetNameLabel.font.fontWithSize(13)
sepetNameLabel.numberOfLines = 0
sepetNameLabel.textAlignment = NSTextAlignment.Left
sepetNameLabel.text = sepetDictCopy[indexPath.row]
sepetCell.contentView.addSubview(sepetNameLabel)
return sepetCell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sepetDictCopy.count
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 60
}
In this case, I click on a UIButton, it pops up a UIView and UITableView is added to this UIView as a subView. At first click, everything works great. When I close that UIView and re-click on the button, this time tableview loads with new data (correct data as well) but it overwrites the previous data. In the provided code, I know I don't add any data to the array, but issue is the same since for example I had "Pizza" written in UILabel and when I reloaded the table, there still was "Pizza", but this time darker (which tells me that label is overwritten on previous label). Say I add "Burger" to the array, tableview shows "Burger" and "Pizza", but this time "Burger" is written over the previous "Pizza" at index 0 and "Pizza" is at index 1 alone. I can also provide additional information or some pictures of the issue if not understood correctly.

Looks like I didn't quite understand the concept of viewWithTag method. I rearranged my code and it worked. For those who are looking for an up to date answer and code, here it is:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var sepetCell: UITableViewCell! = sepetTableView.dequeueReusableCellWithIdentifier("")
if (sepetCell == nil) {
sepetCell = sepetTableView.dequeueReusableCellWithIdentifier("sepetCell")!
let sepetNameLabel = UILabel()
sepetNameLabel.tag = 1
sepetNameLabel.frame = CGRectMake(0, 0, sepetDialogView.bounds.size.width / 2, 60)
sepetNameLabel.center = CGPointMake(25 + sepetDialogView.bounds.size.width / 4 , 30)
sepetNameLabel.font = sepetNameLabel.font.fontWithSize(13)
sepetNameLabel.numberOfLines = 0
sepetNameLabel.textAlignment = NSTextAlignment.Left
sepetCell.contentView.addSubview(sepetNameLabel)
}
let sepetNameLabel = sepetCell.contentView.viewWithTag(1) as! UILabel
sepetNameLabel.text = sepetDictCopy[indexPath.row]
return sepetCell
}

Related

Add a label to a section of tableView when it's empty

Almost the same question many times has been asked here, but my question is a bit different, for example here, a users shows a really good way to handing an empty tableview with a label
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.numberOfRow == 0{
var emptyLabel = UILabel(frame: CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height))
emptyLabel.text = "No Data"
emptyLabel.textAlignment = NSTextAlignment.Center
self.tableView.backgroundView = emptyLabel
self.tableView.separatorStyle = UITableViewCellSeparatorStyle.None
return 0
} else {
return self.numberOfRow
}
}
It works fine when there is a section, but my problem is, I have two section that users can move cells between them and I want, when one of them become empty, a label appear in the section to say it's empty.
Could anyone modify this way to do that? also it should reload data to show this label, is it right?
Many thanks
The easiest way is to show a regular cell in the section if there are no other rows in the section.
Here's some rough pseudocode:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionData = data[section]
return sectionData.isEmpty ? 1 : sectionData.count
}
tableView(tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let sectionData = data[section]
if sectionData.isEmpty {
// create plain cell
cell.textLabel.text = "Nothing to see here"
return cell
} else {
// create and return your normal data cell from the data for the index path
}
}
Another option is to show a section header or footer for any section that has no rows.

Cannot Assign to Value: Function call returns immutable value

Trying to place labels on a table but getting an error which I haven't seen before. Other cannot assign to property error questions don't seem to match what I'm my situation so posting the question here. Any pointers would really be appreciated as I'm learning by Stack Overflow. Thanks!
import UIKit
class SeventhViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
internal func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 15
}
internal func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as UITableViewCell
var newData = [String]()
newData = ["Impressions","Image Tapped","Description Tapped","Biography Tapped","Purchase Link Tapped","Added to Collection","Removed from Collection"]
//Programmatically create label
var impressionsLabel = UILabel(frame: CGRect(x: 280.0, y: 14.0, width: 300.0, height: 30.0))
impressionsLabel.text = newData[indexPath.row]
impressionsLabel.tag = 1
impressionsLabel.font = UIFont(name: "Impressions", size: 17.0)
impressionsLabel.textColor = UIColor.darkGray
cell.addSubview(impressionsLabel)
**cell.contentView.viewWithTag(1) = newData[indexPath.row]**
impressionsLabel.tag = 1
return cell
The error is with the line:
cell.contentView.viewWithTag(1) = newData[indexPath.row]
Two problems. viewWithTag returns an optional UIView. You then attempt to assign a String to the UIView.
Assuming the view with tag 1 is a UILabel, you need something like this:
if let label = cell.contentView.viewWithTag(1) as? UILabel {
label.text = newData[indexPath.row]
}
This safely deals with accessing a possibly nil view (incase there is no actual view with a tag of 1) and attempting to cast that view to a UILabel (incase the view isn't actually a UILabel). If those conditions are met and you actually get a label, then you set the label's text property.
Unrelated to your issue (it will cause a new issue when you run your app), but it makes no sense to have your data array declared and setup in the cellForRowAt method and it makes no sense to hardcode a specific count in your numberOfRowsInSection method.
The proper thing to do would be to make newData a property of your view controller and set it up once instead of every time a cell is accessed. And then return its count in numberOfRowsInSection.

Swift two table view cell get the same value

I have used a tableview with 16 cells on a view controller. Each cell has a textfield and a picker view as a inputview for textfield. The odd thing is that When I choose the value for the first cell, it's fine. When I scrolled down to the last cell, the value is same as the first one. But I have never touched the last cell. Why would this happened?
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int)
{
// selected value in Uipickerview in Swift
answerText.text = pickerDataSource[row]
answerText.tag = row
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = myTable.dequeueReusableCellWithIdentifier("addFollowCell", forIndexPath: indexPath) as! AddFollowTableViewCell
cell.questionView.text = listQuestion1[indexPath.row]
cell.pickerDataSource = dictPicker[indexPath.row]!
cell.answerText.addTarget(self, action: #selector(AddFollowUpViewController.textFieldDidChange(_:)), forControlEvents: UIControlEvents.EditingDidEnd)
return cell
}
func textFieldDidChange(sender: UITextField){
let rowIndex: Int!
let selectValue = sender.tag
if let txtf = sender as? UITextField {
if let superview = txtf.superview {
if let cell = superview.superview as? AddFollowTableViewCell {
rowIndex = myTable.indexPathForCell(cell)?.row
dictAnswer[rowIndex] = selectValue - 1
}
}
}
}
After two days, it solved by thousands of trials:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var cell = myTable.dequeueReusableCellWithIdentifier("addFollowCell") as! AddFollowTableViewCell
if(cell.identifier == true){
cell.answerText.text = selectedAnswerForRow[indexPath.row]
}
cell.questionView.text = listQuestion1[indexPath.row]
cell.pickerDataSource = dictPicker[indexPath.row]!
dictAnswer[indexPath.row] = cell.pickerValue
cell.answerText.addTarget(self, action: #selector(AddFollowUpViewController.textFieldDidChange(_:)), forControlEvents: UIControlEvents.EditingDidEnd)
cell.identifier = true
return cell
}
func textFieldDidChange(sender: UITextField){
let rowIndex: Int!
let cell = sender.superview?.superview as! AddFollowTableViewCell
rowIndex = myTable.indexPathForCell(cell)?.row
selectedAnswerForRow[rowIndex] = cell.answerValue
print(selectedAnswerForRow[rowIndex])
cell.answerText.text = sender.text
cell.identifier = true
}
It might have some performance issue need to be optimised , but it shows exactly what i want. LOL
You're basically recycling your views and not clearing them. That's the whole point of -dequeueReusableCellWithIdentifier:indexPath:.
Allocating and deallocating memory is very power consuming, so the system recycles every cell that goes out of viewport bounds.
You don't set the text inside answerText (I assume it's the text field that causes trouble) so its content will be kept when recycled.
Assuming you'll store user selection inside a dictionary var selectedAnswerForRow: [IndexPath:String]:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = myTable.dequeueReusableCellWithIdentifier("addFollowCell", forIndexPath: indexPath) as! AddFollowTableViewCell
cell.questionView.text = listQuestion1[indexPath.row]
cell.pickerDataSource = dictPicker[indexPath.row]!
cell.answerText.addTarget(self, action: "textFieldDidChange:"), forControlEvents: UIControlEvents.EditingDidEnd)
cell.answerText.text = self.selectedAnswerForRow[indexPath] ?? "" // add this
return cell
}
self.selectedAnswerForRow[indexPath] ?? "" returns the result or an empty string if it's not present in the dictionary.
Also, you're adding several times the action for edition control event. You have to check first if it isn't already bound.
Because the cell is reused. So you have to implement prepareForReuse() in your custom cell class and reset all the changing variables
UPDATE
See :
class MyCell : UITableViewCell {
#IBOutlet weak var myTextField : UITextField!
//Add the following
override func prepareForReuse() {
myTextField.text = nil
myTextField.inputView = myPickerView
super.prepareForReuse()
}
}

Making Simple Accordion TableView in swift?

Is there any way that I can make simple Accordion View in swift like the one at Calendar Event Create? I don't want to use other third party library as well as other code.
I found many answer at github and over google. But,still don't meet my requirement.
Actually I want to add two table view.
The first one is section which show City such as (New York,Los Angles,Las Vegas,etc)
When I tapped one of the city,it will show store address in tableview which mean there are many stores.
All the store and data will got from json.
The accordion view that i want to do is as simple as the one at Calendar App on iOS. But,the data that I gonna insert into two tableView (Section Header & Inner Records inside each section) which is dynamic.
Any Help? Please Guide me,Help me out.
UPDATE : Please take a look
The answer provided by #TechBee works fine using sections for those interested in not using sections and use cells.
The implementation of an Accordion Menu in Swift can be achieved using UITableView in a very simple way, just having two cells one for the parent cells and another for the childs cells and in every moment keep the track for the cells expanded or collapsed because it can change the indexPath.row every time a new cell is expanded or collapsed.
Using the functions insertRowsAtIndexPaths(_:withRowAnimation:) and deleteRowsAtIndexPaths(_:withRowAnimation:) always inside a block of call to tableView.beginUpdates() and tableView.endUpdates() and updating the total of items in the data source or simulating it changes we can achieve the insertion of deletion of new cells in the UITableView in a very easy way with animation included.
I've implemented myself a repository in Github with all the explained above AccordionMenu using Swift and UITableView in a easy and understandable way. It allows several cells expanded or only one at time.
Try this:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
arrayForBool = ["0","0","0"]
sectionTitleArray = ["Pool A","Pool B","Pool C"]
var tmp1 : NSArray = ["New Zealand","Australia","Bangladesh","Sri Lanka"]
var string1 = sectionTitleArray .objectAtIndex(0) as? String
[sectionContentDict .setValue(tmp1, forKey:string1! )]
var tmp2 : NSArray = ["India","South Africa","UAE","Pakistan"]
string1 = sectionTitleArray .objectAtIndex(1) as? String
[sectionContentDict .setValue(tmp2, forKey:string1! )]
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sectionTitleArray.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
if(arrayForBool .objectAtIndex(section).boolValue == true)
{
var tps = sectionTitleArray.objectAtIndex(section) as! String
var count1 = (sectionContentDict.valueForKey(tps)) as! NSArray
return count1.count
}
return 0;
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "ABC"
}
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 1
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if(arrayForBool .objectAtIndex(indexPath.section).boolValue == true){
return 100
}
return 2;
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 40))
headerView.backgroundColor = UIColor.grayColor()
headerView.tag = section
let headerString = UILabel(frame: CGRect(x: 10, y: 10, width: tableView.frame.size.width-10, height: 30)) as UILabel
headerString.text = sectionTitleArray.objectAtIndex(section) as? String
headerView .addSubview(headerString)
let headerTapped = UITapGestureRecognizer (target: self, action:"sectionHeaderTapped:")
headerView .addGestureRecognizer(headerTapped)
return headerView
}
func sectionHeaderTapped(recognizer: UITapGestureRecognizer) {
println("Tapping working")
println(recognizer.view?.tag)
var indexPath : NSIndexPath = NSIndexPath(forRow: 0, inSection:(recognizer.view?.tag as Int!)!)
if (indexPath.row == 0) {
var collapsed = arrayForBool .objectAtIndex(indexPath.section).boolValue
collapsed = !collapsed;
arrayForBool .replaceObjectAtIndex(indexPath.section, withObject: collapsed)
//reload specific section animated
var range = NSMakeRange(indexPath.section, 1)
var sectionToReload = NSIndexSet(indexesInRange: range)
self.tableView .reloadSections(sectionToReload, withRowAnimation:UITableViewRowAnimation.Fade)
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let CellIdentifier = "Cell"
var cell :UITableViewCell
cell = self.tableView.dequeueReusableCellWithIdentifier(CellIdentifier) as! UITableViewCell
var manyCells : Bool = arrayForBool .objectAtIndex(indexPath.section).boolValue
if (!manyCells) {
// cell.textLabel.text = #"click to enlarge";
}
else{
var content = sectionContentDict .valueForKey(sectionTitleArray.objectAtIndex(indexPath.section) as! String) as! NSArray
cell.textLabel?.text = content .objectAtIndex(indexPath.row) as? String
cell.backgroundColor = UIColor .greenColor()
}
return cell
}

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
}