First cell of custom UITableViewCell is blank - ios

I am querying parse for some information and displaying the info in a UITableViewCell, there is only one entry in the table, a test entry with fictional information but the table is displaying the first cell blank and then the second cell with the information in the table. I have attempted to google this and also add more rows of testing info but either way the table displays one cell at the beginning with no info
Here is my code:
var query = PFQuery(className: "marathons")
query.orderByAscending("end")
query.findObjectsInBackgroundWithBlock { (marathons, error:NSError?) -> Void in
if(error == nil ){
//success
for marathon in marathons! {
self.Name.append(marathon["Name"] as! String)
self.entryNumber.append(marathon["Number"] as! Int)
self.totalEntries.append(marathon["entries"] as! Int)
self.runnerDistance.append(marathon["distance"] as! Int)
}
self.TableView.reloadData()
}else {
print(error)
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//MARK: TableView
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Name.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let singleCell: TableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell") as! TableViewCell
singleCell.Name.text = names[indexPath.row]
singleCell.entryNumber.text = "\(entryNumbers[indexPath.row])"
singleCell.totalEntries.text = "\(entires[indexPath.row])"
singleCell.runnerDistance.text = "\(distance[indexPath.row])"
return singleCell
}
Name, entryNumber, totalEntries, and runnerDistance defined as:
#IBOutlet var Name: UILabel!
#IBOutlet var entryNumber: UILabel!
#IBOutlet var totalEntries: UILabel!
#IBOutlet var runnerDistance: UILabel!
Any advice? I am using swift, parse as my back end, and XCODE 7

Where are the model objects defined in your code? You're appending to objects named Name, entryNumber, totalEntries, and runnerDistance, but you're trying to assign values from properties named entryNumbers entries and distance.
Edit:
I see what's going on here. Based off your comment, you're initializing your arrays like this:
var names = [String()]
That actually creates an array with one empty string in it [""]. When you call append, you're adding another element to the array, which makes you wind up with ["","John"]. if you want an empty array you should be creating it like this:
var names = [String]()

Related

Notify UITable from an other Thread

I've got a simple UITable implementation and an array which is called fruits. Taken out of this example. The only difference is that I am using a UIViewController for the UITable insted of a UITableViewController, but this should be irrelevant for now.
class MainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var fruits : [String] = Array()
let thread : GetDataForFruitsArrayThread
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fruits.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "LabelCell", for: indexPath)
cell.textLabel?.text = fruits[indexPath.row]
return cell
}
}
Now I have got a viewDidLoad function in this class also. In the function I am calling a thread which is getting the array as a parameter. The thread is filling up the array with new values asynchronously. Means I don't know if there will be any values or when do the value appear and are set into the array. So you can imagine what I want to ask on this point: How do I signal the UITableView that I have changed the content of the array once the view has loaded the array into the UITable. In c# there is the NotifyAll() function which notfies all the Listener to an attribute. Is there an easy and performant way to do this in swift, optionally without importing any non-included libraries?
override func viewDidLoad() {
super.viewDidLoad()
self.thread = GetDataForFruitsArrayThread(array: &fruits)
self.thread.StartAsynchronously()
}
EDIT - adding GetDataForFruitsArrayThread
class GetDataForFruitsArrayThread : MyThread {
var array : [String]
init(array: inout [String] ) {
self.array = array
}
override func main() {
// init socket
while(true){
_ = withUnsafeMutablePointer(to: &rcvaddr){
$0.withMemoryRebound(to: sockaddr.self, capacity: 1){
recvfrom(m_socket, &pData, bufferlength, 0, UnsafeMutablePointer($0),
&socketAddressLength)
}
}
var fruit = String(cString:inet_ntoa(rcvaddr.sin_addr), encoding: .ascii)
self.array.append(fruit)
}
}
}
EDIT - 21.11.2017:
Following Sandeep Bhandari's answer I changed the following code in the ViewController:
#IBOutlet weak var tableView1: UITableView!
var fruits : [String] = Array() {
didSet {
self.tableView1.reloadData()
}
}
PROBLEM: I am setting a breakpoint at array.append(fruit) in the GetDataForFruitsArrayThread-class. After that there is a breakpoint in the didSet method. But this breakpoint does not get called after the append()-call! NOTE: The parameter on the init function of the GetDataForFruitsArrayThread-class is an in-out parameter!
UPDATE: I found out that if I do the array.append(fruit)-call on the GetDataForFruitsArrayThread.array in the GetDataForFruitsArrayThread-thread, the Fruits-array in ViewController-class does not get changed, therefore didSet did not get called. How can I change this behaviour?
Because you said you are running a endless process to keep updating your array and you want the UI update once the array changes you can use the below solution.
var fruits : [String] = Array() {
didSet {
self.tableView.reloadData()
}
}
What am I doing ? Simple added a setter to array and whenever value changes I reload the tableView.
EDIT:
The above solution will work only if you modify the inout fruits array you pass to GetDataForFruitsArrayThread
self.thread = GetDataForFruitsArrayThread(array: &fruits)
So don't create one more array property in GetDataForFruitsArrayThread rather directly modify the array passed to GetDataForFruitsArrayThread.

Some table view cells become invisible after I added more cells. Why?

I am using a variation of the technique mentioned in this post to add and remove table view cells dynamically.
Initially, the table view cells looks like this:
Then, I add a new cell to section 1. Section 1 is the section above the "RESULTS" section. So I expect the new cell to appear below the cell with the name "h". But no! It turns into this!
The new cell is added in section 2 (The "RESULTS" section) and is added below the cell with the name "b". What's even more surprising is that the second cell in section 2 has disappeared!
Here is how I add the cell:
I have an array of cells here:
var cells: [[UITableViewCell]] = [[], [], []]
each subarray in the array represents a section. In viewDidLoad, I added some cells to sections 0 to 2 by calling:
addCellToSection(1, cell: someCell)
addCellToSection is defined as
func addCellToSection(section: Int, cell: UITableViewCell) {
cells[section].append(cell)
tableView.insertRowsAtIndexPaths([NSIndexPath(forRow: cells[section].endIndex - 1, inSection: section)], withRowAnimation: .Top)
}
And the table view data source methods are defined in the same way as the aforementioned post.
I tried to print the number of cells in each section when I add the cell:
print("no. of rows in section 1: \(self.tableView(tableView, numberOfRowsInSection: 1))")
print("no. of rows in section 2: \(self.tableView(tableView, numberOfRowsInSection: 2))")
And the printed values are consistent i.e. when I add a new cell, the no. of rows increase by 1. But the weird thing is that it keeps placing rows in the wrong position.
Extra info: how I create the cell:
I first dequeue the cells from the prototype cells. I then call viewWithTag to get the text fields that are in the cell and add them to a [(UITextField, UITextField)]. Don't know whether this matters.
Okay so first of all, you should never store UITableView cells in some custom collection. This is and should be done by iOS, not you.
The data you are using to populate the cells are stored in some model I presume?
Your tableView should register cells using either:
func registerClass(cellClass: AnyClass?, forCellReuseIdentifier identifier: String)
or
func registerNib(nib: UINib?, forCellReuseIdentifier identifier: String)
or using Prototype cells in the Xib/Storyboard.
I recommend this setup, or similar:
class MyModel {
/* holds data displayed in cell */
var name: String?
var formula: String?
init(name: String, formula: String) {
self.name = name
self.formula = formula
}
}
class MyCustomCell: UITableViewCell, UITextFieldDelegate {
static var nibName = "MyCustomCell"
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var formulaTextField: UITextField!
weak var model: MyModel?
override func awakeFromNib() {
super.awakeFromNib()
nameTextField.delegate = self
formulaTextField.delegate = self
}
func updateWithModel(model: MyModel) {
/* update labels, images etc in this cell with data from model */
nameTextField.text = model.name
formulaTextField.text = model.formula
self.model = model
}
/* This code only works if MyModel is a class, because classes uses reference type, and the value
of the name and formula properies are changed in the model stored in the dictionary */
func textFieldShouldEndEditing(textField: UITextField) -> Bool {
let newText = textField.text
switch textField {
case nameTextField:
model?.name = newText
case formulaTextField:
model?.formula = newText
default:
print("Needed by compiler..")
}
}
}
class MyController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableVieW: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
/* This is not needed if you are using prototype cells in the Xib or Storyboard.
Convenient to use nib name as cell identifier */
tableVieW.registerNib(UINib(nibName: MyCustomCell.nibName, bundle: nil), forCellReuseIdentifier: MyCustomCell.nibName)
tableVieW.delegate = self
tableVieW.dataSource = self
}
private var dictionaryWithModelsForSection: Dictionary<Int, [MyModel]>!
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
let sectionCount = dictionaryWithModelsForSection.keys.count
return sectionCount
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let models: [MyModel] = modelsForSection(section) else {
return 0
}
let rowCount = models.count
return rowCount
}
private func modelsForSection(section: Int) -> [MyModel]? {
guard section < dictionaryWithModelsForSection.count else {
return nil
}
let models = dictionaryWithModelsForSection[section]
return models
}
private func modelAtIndexPath(indexPath: NSIndexPath) -> MyModel? {
guard let models = modelsForSection(indexPath.section) where models.count > indexPath.row else {
return nil
}
let model = models[indexPath.row]
return model
}
func addRowAtIndexPath(indexPath: NSIndexPath, withModel model: MyModel) {
add(model: model, atIndexPath: indexPath)
tableVieW.insertRowsAtIndexPaths([indexPath], withRowAnimation: .None)
}
private func add(model model: MyModel, atIndexPath indexPath: NSIndexPath) {
guard var models = modelsForSection(indexPath.section) where indexPath.row <= models.count else { return }
models.insert(model, atIndex: indexPath.row)
dictionaryWithModelsForSection[indexPath.section] = models
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(MyCustomCell.nibName, forIndexPath: indexPath)
return cell
}
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
guard let
cell = cell as? MyCustomCell,
model = modelAtIndexPath(indexPath) else { return }
cell.updateWithModel(model)
}
}
If you want to insert a cell you can use the method addRowAtIndexPath:withModel i wrote in MyController above, you need to call that from some function creating the corresponding model...

SWIFT: Data model for "Like" button functionality in iOS app

I'm creating a playlist app (Mock-Up Below) using Swift and Parse. Each user has the ability add a song (or multiple songs) to the TableView and "Like" or "Dislike" (toggle style) as many songs as they like, including their own. This functionality is very similar to the Instagram "Like" button behaviour.
I'm having a bit of trouble conceptualizing the data model. As shown below, I have a User table and a Playlist table (with all of the added songs):
User Table:
Playlist Table:
I'm having trouble with the next step, which is storing the "Like" data for each user and each song.
UPDATED: I've added a column in the 'PlaylistData' table in Parse called "userVotes". As you can see, I'm appending all of the data into an array called 'voters'. For some reason, I'm getting a fatal run time error - 'fatal error: unexpectedly found nil while unwrapping an Optional value'.
import UIKit
import Parse
class MusicPlaylistTableViewController: UITableViewController {
var usernames = [String]()
var songs = [String]()
var voters = [String]()
var numVotes = 0
override func viewDidLoad() {
super.viewDidLoad()
tableView.separatorColor = UIColor.grayColor()
let query = PFQuery(className:"PlaylistData")
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
if let objects = objects! as? [PFObject] {
self.usernames.removeAll()
self.songs.removeAll()
self.voters.removeAll()
for object in objects {
let username = object["username"] as? String
self.usernames.append(username!)
let track = object["song"] as? String
self.songs.append(track!)
self.voters = object["userVotes"] as! [String]
print(self.voters)
}
self.tableView.reloadData()
}
} else {
print(error)
}
}
}
override func viewWillAppear(animated: Bool) {
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return usernames.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CellTrack", forIndexPath: indexPath) as! TrackTableViewCell
cell.username.text = usernames[indexPath.row]
cell.songTitle.text = songs[indexPath.row]
cell.votes.text = "\(numVotes)"
cell.selectionStyle = UITableViewCellSelectionStyle.None
return cell
}
override func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
}
}
I've also created a TableViewCell swift file for the prototype cell:
import UIKit
import Parse
class TrackTableViewCell: UITableViewCell {
var numVotes = 0
#IBOutlet weak var songTitle: UILabel!
#IBOutlet weak var username: UILabel!
#IBOutlet weak var votes: UILabel!
#IBAction func voteButton(sender: UIButton) {
self.numVotes = self.numVotes + 1
self.votes.text = "\(self.numVotes)"
}
}
At this point, all it does is when the heart is clicked, it increases the vote count by 1 on screen (the user can do this as many times as possible, no data is stored in the database). What I need help with is the best way to implement table(s) which allow me to keep track of which users have voted for each song (or not). I know I need a TRUE / FALSE column somewhere, but I can't seem to figure out how to model it. Any suggestions or links to SWIFT tutorials would be greatly appreciated. A simple explanation on what tables to create (or what columns to add to existing tables) would help the best. Thanks!

Connecting TableView within ViewController - self.tableView.reloadData() not working in Swift

I'm a newbie learning iOS and Swift so apologies ahead of time. Currently I'm trying to setup a tableView within a viewController and display data in the cells in a portion of the screen. My current problem seems to be in reloading the tableView data after the Alamofire HTTP request in viewDidLoad() is called for numberOfRowsInSection(). Here's the code:
import UIKit
import Alamofire
import SwiftyJSON
class CourseDetailViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var titleLabel: UILabel?
#IBOutlet weak var creditsLabel: UILabel?
#IBOutlet weak var descriptionLabel: UILabel?
#IBOutlet weak var tableView: UITableView!
var detailCourse: Course? {
didSet {
configureView()
}
}
var course: Course!
func configureView() {
self.title = detailCourse?.abbr
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "SectionCell")
tableView.delegate = self
tableView.dataSource = self
if let theCourse: Course = self.detailCourse as Course! {
var abbr: String = theCourse.abbr!
APIService.getCourseByAbbr(abbr) { (data) -> Void in
self.course = Course(courseJSON: data)
// Set labels
self.titleLabel?.text = self.course.title!
self.descriptionLabel?.text = self.course.description!
if let creditsArray = self.course.credits {
let minimumCredit = creditsArray[0] as Int
self.creditsLabel?.text = String(minimumCredit)
}
self.tableView.reloadData()
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// Return the number of sections.
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows in the section.
return course.sections.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Configure the cell...
let cell = tableView.dequeueReusableCellWithIdentifier("SectionCell", forIndexPath: indexPath) as! SectionTableViewCell
let sectionCell = course.sections[indexPath.row]
cell.termLabel?.text = sectionCell.term
cell.timeLabel?.text = sectionCell.startTime
cell.instructorLabel?.text = sectionCell.instructor
return cell
}
}
When I run, I get the following error:
fatal error: unexpectedly found nil while unwrapping an Optional value
I believe that the reason may be that I set up the tableView within the viewController incorrectly.
For the full project, here is a link to the repo: https://github.com/classmere/app/tree/develop
The problem is that you're trying to unwrap an optional whose value is nil. When you declare the course property, since its an optional, its initial value is nil. Usually, optionals are declared with ? and the compiler will prevent you from accessing the underlying value without checking if the value is still nil. In this case however, you've made the course property an expected optional:
var course: Course!
This is like saying "I know that course will always have a value and will never be nil". We don't know that however, since its value is nil until the Alamofire callback successfully completes.
To fix this problem, start by making course a standard optional:
var course: Course?
Now Xcode will complain that you're accessing course without unwrapping it, since your declaration of course no longer unwraps it.
Fix this by forcibly unwrapping everything in the Alamofire callback:
APIService.getCourseByAbbr(abbr) { (data) -> Void in
println("APIService()")
self.course = Course(courseJSON: data)
// Notice we can access self.course using ! since just assigned it above
self.titleLabel?.text = self.course!.title!
self.descriptionLabel?.text = self.course!.description!
if let creditsArray = self.course!.credits {
let minimumCredit = creditsArray[0] as Int
self.creditsLabel?.text = String(minimumCredit)
}
self.tableView.reloadData()
}
Then in cellForRowAtIndexPath, we will use optional chaining to ensure we only access course's properties if they exist:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Configure the cell...
let cell = tableView.dequeueReusableCellWithIdentifier("SectionCell", forIndexPath: indexPath) as! SectionTableViewCell
if let section = course?.sections[indexPath.row] {
cell.termLabel?.text = section.term
cell.timeLabel?.text = section.startTime
cell.instructorLabel?.text = section.instructor
}
return cell
}
Finally in numberOfRowsForSection make sure to get the actual number of sections instead of always returning 50. We'll use the nil-coalescing operator to return 0 if course is nil:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return course?.sections.count ?? 0
}
That should fix your problem!

Not populating UiTableView with Parse.com results in SWIFT

I am hoping someone can help, as I am trying to debug, but am going round in circles.
I have a table in Parse.com and can query and retrieve data successfully.
I did a test with a println and the correct values of the strings are displayed in the output.
What I was trying to do was put these values into a UITableView, but this has taken me down some pretty frustrating paths (I am still trying to learn this as best as I can and sometimes some concepts are hard to comprehend).
My last attempt (see code below) I thought by writing the values to a struct I could use this as I have done in the past, given that I can see the values I need to populate. I don't think this is the right way but I thought it should work.
My code when I put a breakpoint in doesn't get to even defining the tableview :(
I know I am missing something but maybe just need a fresh pair of eyes to help me see what I am missing.
Any help would be greatly appreciated:
#IBOutlet weak var navlabel: UILabel!
var TopicPassed:String!
var storedsentences=[getsentences]()
#IBOutlet weak var sentencetableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
navlabel.text = TopicPassed
var query = PFQuery(className:"TalkToMeSentences")
query.whereKey("Topic", equalTo:TopicPassed)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
// query successful - display number of rows found
println("Successfully retrieved \(objects.count) sentences")
// print sentences found
for object in objects {
let retrievedsentences = object["Sentence"] as NSString
self.storedsentences = [getsentences(parsesentence: "\(retrievedsentences)")]
println("\(retrievedsentences) ")
}
self.sentencetableview.reloadData()
} else {
// Log details of the failure
println("Error: \(error) \(error.userInfo!)")
}
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return storedsentences.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
var sentence : getsentences
// Configure the cell...
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
cell.textLabel!.lineBreakMode = NSLineBreakMode.ByWordWrapping
sentence = storedsentences[indexPath.row]
cell.textLabel!.text = sentence.parsesentence
cell.textLabel?.numberOfLines = 0
return cell
}
Resolved it, I think.
My problem was I had not assigned outputs for the the datasource or the delegates.
Once I did I could get the table to populate.

Resources