How to fix the Disappearing/Invisible/Empty UITableViewCells issue in Swift? - ios

I have two ViewControllers, one is GoalsViewController and the other one is AddNewGoalViewController.
The GoalsViewController is useful to delete goals (cells) and to add new goals (cells). There is a UITableView and a button, Add new Goal. When the user presses the Add new Goal button, it will pass to AddNewGoalViewController. In AddNewGoalViewController users will select workout, notifications (how many times they want to be notified), and how much they want to run, walk or do any other work.
I checked a tutorial (click on word "tutorial" to check it), and it was helpful. The problem is that it is implementing empty cells. Download my project to check it better.

EDIT: After spending A LOT of time looking into your project, I found the issue.
Click on your Main.Storyboard file. Click on the file inspector. Uncheck Size Classes. Done. Your project works !
This seems to be an XCode bug. If you check again Size Classes, your project should still work.
The fix is therefore to uncheck and then check Size Classes in the File Inspector of your Main.storyboard file.
NONETHELESS: My syntax advice is still valid, it makes for cleaner code:
Well, did you check the solution of the exercise?
There is a link at the end of the page ;)
1st Difference:
var workouts = [Workout]()
var numbers = [Workout]()
func loadSampleMeals() {
let workouts1 = Workout(name: "Run", number: "1000")!
let workouts2 = Workout(name: "Walk", number: "2000")!
let workouts3 = Workout(name: "Push-Ups", number: "20")!
workouts += [workouts1, workouts2, workouts3]
numbers += [workouts1, workouts2, workouts3]
}
should be:
var workouts = [Workout]()
func loadSampleMeals() {
let workouts1 = Workout(name: "Run", number: "1000")!
let workouts2 = Workout(name: "Walk", number: "2000")!
let workouts3 = Workout(name: "Push-Ups", number: "20")!
workouts += [workouts1, workouts2, workouts3]
}
2nd Difference:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "DhikrTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! GoalsTableViewCell
// Fetches the appropriate meal for the data source layout.
let dhikr = workouts[indexPath.row]
let number = numbers[indexPath.row]
cell.nameLabel.text = dhikr.name
cell.numberLabel.text = number.number
//cell.photoImageView.image = dhikr.photo
//cell.ratingControl.rating = dhikr.rating
return cell
}
should be:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "DhikrTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! GoalsTableViewCell
// Fetches the appropriate meal for the data source layout.
let dhikr = workouts[indexPath.row]
cell.nameLabel.text = dhikr.name
cell.numberLabel.text = dhikr.number
//cell.photoImageView.image = dhikr.photo
//cell.ratingControl.rating = dhikr.rating
return cell
}
P.S.:
class Workout {
// MARK: Properties
var name: String
//var notifications: Int
var number: Int
// MARK: Initialization
init?(name: String, number: Int) {
// Initialize stored properties.
self.name = name
//self.notifications = notifications
self.number = number
// Initialization should fail if there is no name or if the rating is negative.
if name.isEmpty || number < 0{
return nil
}
}
}
number will never be < 0, perhaps you meant == 0?.

Related

Error: Index out of Range UITableView

I am creating an app where it uses custom cells. I also have these UITextView's where if you input a word, that word should then go to one of the four labels I created in the custom cell. I am still coding it however I got an error saying "Error: Index Out of Range".
Here is the code, and I also commented where it is giving that error
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) ->
UITableViewCell{
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! TableView
cell.lbl.text = todolist[indexPath.row]
cell.lbl2.text = todolist2[indexPath.row] // This is the error code
cell.lbl3.text = todolist3[indexPath.row]
cell.lbl4.text = todolist4[indexPath.row]
return cell
}
Here is where I append my texts
#IBAction func ClickedforSelection(sender: AnyObject) {
todolist.append(txt.text!)
todolist2.append(txt1.text!)
todolist3.append(txt2.text!)
todolist4.append(txt3.text!)
self.view.endEditing(true)
txt.text = ""
txt1.text = ""
txt2.text = ""
txt3.text = ""
NSUserDefaults.standardUserDefaults().setObject(todolist, forKey: "list")
NSUserDefaults.standardUserDefaults().setObject(todolist2, forKey: "list2")
NSUserDefaults.standardUserDefaults().setObject(todolist3, forKey: "list3")
NSUserDefaults.standardUserDefaults().setObject(todolist4, forKey: "list4")
Here is my NumberofRowsInSection
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return todolist.count
}
I have a conjecture that it may be the reuse of indexPath.row. Any solution?
If numberOfRowsInSection is returning todolist.count, you are accessing todolist2 in your cell. If todolist has 2 items and todolist2 has 1 item, it will do this because you are trying to access an item in a list that doesn't exist. Put a breakpoint at the first call of cell.lbl.text and check each array (todolist, todolist1, etc...). You should see that todolist2 does not have have a record at whatever "row" it's calling. If that is the case, you should just test it prior to calling it. (verify todolist2.count has enough items in it - or better yet, change the code to not have 4 arrays tracking 1 row (convert to a struct of some type with all 4 values, or something similar).
First, change the following code by commenting out lines:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) ->
UITableViewCell{
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! TableView
cell.lbl.text = todolist[indexPath.row]
// cell.lbl2.text = todolist2[indexPath.row] // This is the error code
// cell.lbl3.text = todolist3[indexPath.row]
// cell.lbl4.text = todolist4[indexPath.row]
return cell
}
And test to verify existing code (should work but of course it will not update the labels.)
Then add code to print the number of items in each array:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) ->
UITableViewCell{
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! TableView
cell.lbl.text = todolist[indexPath.row]
// cell.lbl2.text = todolist2[indexPath.row] // This is the error code
// cell.lbl3.text = todolist3[indexPath.row]
// cell.lbl4.text = todolist4[indexPath.row]
print("Row: \(indexPath.row)")
print("List 1: \(todolist.count)") //this will print to the console
print("List 2: \(todolist2.count)")
print("List 3: \(todolist3.count)")
print("List 4: \(todolist4.count)")
return cell
}
What you will likely see is that they don't have the same number of items, and as soon as it his a "row" that is equal to or greater than the number of items, it will break. Remember that Row's start at Zero, while count starts at 1.
If this is what you find, then there is problem something wrong with the code where you are adding the values to the todolist arrays. If you want to see how to convert that to a struct, I can post that for you.
Converting to struct
The code that is executing when something is clicked:
#IBAction func ClickedforSelection(sender: AnyObject) {
shows that a value is written to each of the 4 todolists every time. While I don't have the full requirements, if this is what you want to do, then you could implement a struct. Put this code in it's own ToDoList.swift file (ideally):
struct ToDoListItem {
var listItem: String?
var list1Item: String?
var list2Item: String?
var list3Item: String?
}
Then replace where you define your todolislt arrays (all 4 of them) with a single:
var listItems = [ToDoListItem]() //creates an array of ToDoListItems and initializes it with no values
Then in the ClickedForSelection function, change it to:
let listItem = ToDoListItem(listItem: txt.text, list1Item: txt1.text, list2Item: txt2.text, list3Item: txt3.text)
listItems.append(listItem) //add it to your array
//todolist.append(txt.text!)
//todolist2.append(txt1.text!)
//todolist3.append(txt2.text!)
//todolist4.append(txt3.text!)
self.view.endEditing(true)
txt.text = ""
txt1.text = ""
txt2.text = ""
txt3.text = ""
// This routine will need to be updated. Leaving that for you to figure out :)
// NSUserDefaults.standardUserDefaults().setObject(todolist, forKey: "list")
// NSUserDefaults.standardUserDefaults().setObject(todolist2, forKey: "list2")
// NSUserDefaults.standardUserDefaults().setObject(todolist3, forKey: "list3")
// NSUserDefaults.standardUserDefaults().setObject(todolist4, forKey: "list4")
...then numberOfRowsInSection changes to:
return listItems.count
...then cellForRowAtIndexPath changes to:
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! TableView
let listItem = listItems[indexPath.row]
cell.lbl.text = listItem.listItem ?? "" // Since listItems.listItem is an optional value, ?? unwraps it safely. If it is nill, it uses "" instead
cell.lbl2.text = listItem.list1Item ?? ""
cell.lbl3.text = listItem.list2Item ?? ""
cell.lbl4.text = listItem.list3Item ?? ""
return cell
Again...I would strongly consider how you are storing a value for a todolist for all 4 lists every time (if it is a todo list app, it seems like this may not be ideal?)

Reloading Table View to Create Dynamic Label Changes iOS Swift

I am creating an app that I want to display different strings inside of a label that is within a UITableViewCell. However, I cannot use indexPath.row because the strings are in not particular order.
let one = "This is the first quote"
let two = "This is the second quote"
var numberOfRows = 1
var quoteNumber = 0
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let items = [one, two]
myTableView = tableView
let cell = tableView.dequeueReusableCellWithIdentifier("customcell", forIndexPath: indexPath) as! cellTableViewCell
let cellWidth = cell.frame.width
let cellHeight = cell.frame.height
cell.QuoteLabel.frame = CGRectMake(cellWidth/8, cellHeight/4, cellWidth/1.3, cellHeight/2)
let item = items[quoteNumber]
cell.QuoteLabel.text = item
cell.QuoteLabel.textColor = UIColor.blackColor()
if quoteNumber == 0 {
quoteNumber = 1
}
if numberOfRows == 1 {
numberOfRows = 2
}
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
var timer = NSTimer.scheduledTimerWithTimeInterval(5, target: self, selector: "update", userInfo: nil, repeats: true)
}
func update() {
dispatch_async(dispatch_get_main_queue()) {
self.myTableView.reloadData()
}
This code builds and runs, but when the timer I have set expires and the update function is ran, both of the two available cells change to the same label. What I am trying to do is make it so that the first cell can remain static while the newly added cell can receive the new element from the 'items' array without using indexpath.row (because the quotes that I am going to be using from the items array will be in no particular order). Any help would be appreciated.
Instead of reloadData try using reloadRowsAtIndexPaths: and pass in the rows you want to reload. This does use indexPath, however. The only way I'm aware of to reload specific rows as opposed to the whole tableView is to specify the rows by index path.

Use of didSet property observer in custom UITableCell to update TextView

I have a table that displays a list of pupils and their chosen subjects. The pupil's name are displayed as table section headers and each chosen subject is displayed as table rows for each section. I have a simple class to store the pupil's name as a string and their subjects as an array as follows:
import UIKit
class TableText: NSObject {
var name: String
var subject: [String]
init(name: String, subject: [String]) {
self.name = name
self.subject = subject
}
}
In my custom TableViewCell I have a didSet property observer to track any changes to each pupil's chosen subjects (i.e. I have a textView that the user can click on and modify or change a subject. My didSet is currently as follows:
var tableText: TableText? {
didSet {
let mySubject = myTextView.text
if ((tableText?.subject.contains(mySubject)) != nil) {
}
}
}
My cellForRowAtIndexPath in my TableViewController is as follows:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! myTableViewCell
cell.delegate = self
let pupil = tableTexts[indexPath.section]
cell.myTextView.text = pupil.subject[indexPath.row]
cell.subject = pupil.subject[indexPath.row]
cell.name = pupil.name
print("name: \(cell.name) subject: \(cell.subject)")
return cell
}
I'm puzzled at how to take the data from the tableView of each pupil and pupil's subjects and use didSet to set the pupil's name and list of subjects so I can monitor the textViews and update changes to the model as required. I much appreciate any assistance.

Repeated cells while scrolling uitableview

I am developing news feed and I am using uitableview to display data. I am loading each cell data synchronically in other thread and use protocol method to display loaded data:
func nodeLoaded(node: NSMutableDictionary) {
for var i = 0; i < nodesArray.count; ++i {
if ((nodesArray[i]["id"] as! Int) == (node["id"] as! Int)) {
nodesArray[i] = node
}
}
}
The problem is that when I scroll my uitableview (while data synchronically loading), some of my cells repeats (8 row has same content like first, or 6 row has the same content like second row). When I scroll after some time (I suppose after data is loaded) then all become normal.
I looking for answers and found that I have to check if cell is nill at cellForRowAtIndexPath, but in swift my code is different then in objective C:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: NewsCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! NewsCell
var node = nodesArray[indexPath.row] as! NSDictionary
if (node["needLoad"] as! Bool) {
dbHelper.getNode(node["id"] as! Int, hash: node["id"] as! Int, tableName: DbHelper.newsTableName, callback: self)
} else {
cell.id = node["id"] as! Int
cell.titleLabel.text = node["title"] as? String
cell.descriptionLabel.text = node["description"] as? String
cell.imgView.image = WorkWithImage.loadImageFromSD((node["image"] as! String))
}
return cell
}
Also I can't check if cell == nil bcs of binary error (NewsCell can't be nil).
What should I do? Thx.
you seem to have created a separate class for UITableViewCell. The problem with your code is that you are not resetting the labels when reuse happens.
Oveeride prepareForReuse method in your custom UITableviewCell class and reset your interfaces there. That should fix the issue.

Parse and Swift. Set tableViewCell accessory type from Parse relation

So, I have got a tableView which shows courses. The user is able to set Checkmarks on these courses (cells) and save them in his PFUser object as a relation to the Courses class (where all courses are stored).
My question is, how do I checkmark the courses a user has already saved at some point before.
This is my attempt, but I don’t know how to continue. How do I get the cells with a specific Label? (Or is there a better way?)
let courseRel = PFUser.currentUser()?.relationForKey("usercourses")
let query = courseRel!.query()
let qObjects :Array = query!.findObjects()!
println(qObjects)
for var qObjectsCount = qObjects.count; qObjectsCount > 0; --qObjectsCount {
var qAnObject: AnyObject = qObjects[qObjectsCount - 1]
var courseName = qAnObject["coursename"]
println(courseName)
if let cell: AnyObject? = tableView.dequeueReusableCellWithIdentifier("courseCell"){
}
}
EDIT: that code is in my override viewDidLoad
EDIT2:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("courseCell") as! PFTableViewCell!
if cell == nil {
cell = PFTableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "courseCell")
}
let courseRel = PFUser.currentUser()?.relationForKey("usercourses")
let query = courseRel!.query()
let qObjects :Array = query!.findObjects()!
// Extract values from the PFObject to display in the table cell
if let courseName = object?["coursename"] as? String {
cell?.textLabel?.text = courseName
if contains(qObjects, object) {
cell?.accessoryType = UITableViewCellAccessoryType.Checkmark
}
}
return cell
}
Error in line ‚if contains(qObjects, object) {'
Generic parameter 'S.Generator.Element’ cannot be bound to non-#objc protocol type 'AnyObject'
EDIT3:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("courseCell") as! PFTableViewCell!
if cell == nil {
cell = PFTableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "courseCell")
}
let courseRel = PFUser.currentUser()?.relationForKey("usercourses")
let query = courseRel!.query()
let qObjects :Array = query!.findObjects()!
// Extract values from the PFObject to display in the table cell
if let courseName = object?["coursename"] as? String {
cell?.textLabel?.text = courseName
cell.tintColor = UIColor.blackColor()
}
if contains(qObjects, { $0 === object }) {
cell?.accessoryType = UITableViewCellAccessoryType.Checkmark
self.selectedRows.addIndex(indexPath.row)
}else{
cell?.accessoryType = UITableViewCellAccessoryType.None
}
return cell
}
EDIT4: (Working code)
In the class:
// Initializing qObject variable
var qObjects :Array<AnyObject> = []
In my objectsDidLoad:
// Get PFObjects for the checkmarks on courses the currentUser has already selected before
let courseRel = PFUser.currentUser()?.relationForKey("usercourses")
let query = courseRel!.query()
qObjects = query!.findObjects()!
In my tableView(cellForRowAtIndexPath):
// Extract values from the PFObject to display in the table cell
if contains(qObjects, { $0 === object }) {
cell.accessoryType = UITableViewCellAccessoryType.Checkmark
self.selectedRows.addIndex(indexPath.row)
} else {
cell.accessoryType = UITableViewCellAccessoryType.None
}
Don't try to search for a cell. Well, you can, but not like you're trying to - I'll come back to that.
Your current code is creating new cells, not finding existing cells, so that won't work.
What you should really be doing is storing the array of returned objects, qObjects, and then when you're configuring the cell for display checking if that array contains the object for the current cell. If it does, tick it, otherwise remove the tick.
Now, if the load of qObjects happens after the view is shown you have 2 options:
reload the table view
update just the visible items
Option 2. is obviously better, especially if the user might be scrolling the list. To do that you want to use the array returned by calling indexPathsForVisibleRows on the table view. Then, iterate that list, get the associated object and check if it's in qObjects, then get the cell on display with cellForRowAtIndexPath: and update it.

Resources