I want to query using Parse to add strings to an array. Then I want to put those strings into the cells of my UITableView. However, every time I run the app nothing seems to appear on my table. Here is my code if someone could help explain some of the reasons that it may not be appearing
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var friendsArray: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
var usrname = currentUser?.username
var query = PFQuery(className:"Relation")
query.whereKey("Sender", equalTo : usrname!)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
//println("Successfully retrieved \(objects) scores.")
// Do something with the found objects
if let objects = objects as? [PFObject] {
for object in objects {
var friendName = object["Friend"] as! String
println(friendName)
self.friendsArray.append(friendName)
}
}
}
else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return friendsArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("Cell") as! UITableViewCell
cell.textLabel?.text = self.friendsArray[indexPath.row]
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}
You need to call [self.tableView reloadData]; in the completion block of the findObjectsInBackground: call.
At the end of your if error == nil{...} statement, load your table data like this:
if error == nil{
//All that other stuff...
tableView.reloadData()
}
Also make sure your tableView has its delegate and datasource connected to your view controller. This may require putting this in viewDidLoad:
tableView.delegate = self
tableView.datasource = self
I also find it peculiar that your tableView is declared as weak, but that may not be relevant.
Try these things and let us know if you're still having trouble. Good luck! :)
The app does not know when your data will be returned so that you have to explicitly to refresh the UITableView once you have successfully received the data. Use [self.tableView reloadData] to reload the UITableView later on.
The UITableView will only load once when UIViewController gets loaded. Unless, you have already have data the time when UIViewController loads. Otherwise, the number of rows will be 0 at the first time and the delegate method cellForRowAtIndexPath will not be called.
findObjectsInBackgroundWithBlock is an async call when you take a look at the documentation. It's not like its literal meaning that it's running in the background. It means that it won't block the current thread so that users can still have interaction with the application.
When you call findObjectsInBackgroundWithBlock its doing exactly what it name says, specifically its running in the background.
Meanwhile while its running in the background your table view code continues to run in the foreground, effectively in parallel.
So your viewDidLoad function will exit and numberOfRowsInSection will be called next, but at that time if findObjectsInBackgroundWithBlock has not yet finished then friendsArray.count will be 0.
just try these things. it will help you
after you get array of element make reload the uitableView.
if you use storyboard check the datasource outlet
it you did not use storyboard or xib, then you set datasource in coding wisse
hi you can use like this
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell : UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell")! as UITableViewCell
cell.textLabel?.text = app.str[indexPath.row]
print(" cell \(cell.textLabel?.text!)")
return cell
}
TableOutLet.reloadData()
Conform your tableView to its delegate and datasource by written below two lines in your viewDidLoad() after super.viewDidLoad():
tableView.datasource = self
tableView.delegate = self
Then update this code as below :
if let objects = objects as? [PFObject] {
for object in objects {
var friendName = object["Friend"] as! String
println(friendName)
self.friendsArray.append(friendName)
}
tableView.reloadData()
}
After that in tableView delegate methods return self.friendsArray.count for numberOfRows() method and also put your code in cellForRow also.
Just place your line after appending elements in array.
override func viewDidLoad() {
super.viewDidLoad()
var usrname = currentUser?.username
var query = PFQuery(className:"Relation")
query.whereKey("Sender", equalTo : usrname!)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
//println("Successfully retrieved \(objects) scores.")
// Do something with the found objects
if let objects = objects as? [PFObject] {
for object in objects {
var friendName = object["Friend"] as! String
println(friendName)
self.friendsArray.append(friendName)
}
}
}
else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
Or you can reload table once your final array up-to-date
As mentionded by another hint you need to call reloadData but you need to call it in main thread to see result as soon as possible
dispatch_async(dispatch_get_main_queue()) {self.tableView.reloadData()}
Related
I am seeing some very odd behavior with Core Data using Swift and Xcode 7.2. Below is an animation illustrating the issue.
I created a simple example with an entity named 'Person' that has only a 'name' attribute. I fetch all of the 'Person' entities and display them in a table view. If I force a cell to be reloaded then the textLabel that holds the name and NSManagedObject objectID displays without the name. The objectID, however, remains unchanged. This happens both on the device and in the simulator.
I'm printing the value of the Person entity to the console in "cellForRowAtIndexPath" so that I can show the value when the cell is initially loaded vs. when it's reloaded.
As you can see, it appears that the NSManagedObject is still present despite the fact that it seems to lose all references to its 'Person' subclass.
Here is the code for the affected ViewController:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let cellIdentifier = "PersonCell"
#IBOutlet weak var tableView: UITableView!
var people: NSArray = []
override func viewDidLoad() {
super.viewDidLoad()
people = fetchPeople()
}
func fetchPeople() -> NSArray {
let context = AppDelegate().managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "Person")
let sort = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sort]
do {
let results = try context.executeFetchRequest(fetchRequest)
return results
} catch {
let error = error as NSError
print("\(error.domain) \(error.code): \(error.localizedDescription)")
return []
}
}
// MARK: - UITableView methods
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return people.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath)
let person = people[indexPath.row]
print("in cellForRowAtIndexPath:")
print(people[0])
cell.textLabel?.text = "\(person.name) - \(person.objectID)"
return cell
}
}
Any help is appreciated. Thank you.
The problem is here:
let context = AppDelegate().managedObjectContext
You create a new instance of the application delegate, and with it
a managed object context. As soon as program control returns from fetchPeople(), there is no reference to the context anymore and it is
deallocated. The effect is that accessing properties of managed objects
which were created in this context returns nil.
What you want is to get a reference to the (one and only) application delegate
and its managed object context, something like
let context = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
Alternatively, pass the context to the view controller in
the didFinishLaunchingWithOptions method or in
prepareForSegue or whatever is suitable in your app.
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!
There is a UITableView which its cells will be filled by data got with HTTP post request. But UITableView functions executed before the data comes. When the application starts, all three tableView methods executed and then application throws a runtime error. I guess it's because in cellForRowAtIndexPath, messageList is still empty.
Here is the code:
class messageViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
var authData : NSDictionary = [:]
var funcLib = functionLibrary()
var messagesList : NSArray = []
var messageCount: Int = 0
#IBOutlet weak var messageTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
var authCode = self.authData["auth"] as! String
var userID = self.authData["user_id"] as! String
var messageRequsetBodyData: AnyObject = ["op":"users","op2":"getThisWeekMessages","id":"\(userID)","id2":"","id3":"","authCode":"\(authCode)"] as AnyObject
funcLib.HTTPPostRequest("http://asdasd.asdasdasd.com/services/index.php", bodyData: messageRequsetBodyData){data in
dispatch_async(dispatch_get_main_queue()){
if let data = data{
var messaggesListDic = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as! NSDictionary
println("------MESSAGGES---------")
self.messageCount = messaggesListDic["count"] as! Int
//self.messages = messaggesListDic["messages"] as! NSDictionary
self.messagesList = messaggesListDic["messages"] as! NSArray
println("\(self.messagesList)")
self.messageTableView.reloadData()
}
}
}
self.messageTableView.delegate = self
self.messageTableView.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func dismissMessageVC(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
println("asdasd")
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
println("asdasd")
println("\(self.messageCount)")
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
println("bdbsdbsdb")
var cell = self.messageTableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as? UITableViewCell
let row = indexPath.row
cell!.textLabel!.text = self.messagesList[0]["content"] as? String
return cell!
}
Runtime error description:
Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 0 beyond bounds for empty array'
I tried assign the cell label with using cell.textLabel?.text = "asdasd" command and it works. So I think there is no problem with outlets or methods.
How can I assign the data to messageList before cellForRowAtIndexPath executed with using different way?
Yes, if you have a table view that needs to make an asynchronous call to retrieve the data, you should expect the table view data source methods to be called before the asynchronous request is done. But, when the asynchronous request is done, when you simply call tableView.reloadData(), the table view data sources methods will be called a second time. This is a very common pattern.
The issue here, though, is that this code is not gracefully handling the situation that there is no data to display when the table view data sources methods are called the first time. If numberOfRowsForSection returned 0 until data was retrieved (as described by the others, notably by returning messagesList.count(), as suggested by John and Yedidya, rather than returning a fixed number), all would be good.
You return constant value for number of rows even if your message count is zero. Better to return message count.
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
println("asdasd")
println("\(self.messageCount)")
return self.messageCount;
}
Replace the numberOfRows function return value with messagesList.count.
I have a function called loadData
func loadData(){
feedData.removeAllObjects()
var findFeedData:PFQuery = PFQuery(className: "userQuotes")
findFeedData.findObjectsInBackgroundWithBlock{
(objects:[AnyObject]?, error:NSError?)->Void in
if error == nil{
if let objs = objects{
for object in objs{
let quote:PFObject = object as! PFObject
self.feedData.addObject(quote)
}
let array:NSArray = self.feedData.reverseObjectEnumerator().allObjects
self.feedData = NSMutableArray(array: array)
self.tableView.reloadData()
}
}
}
}
and then in the cellForRowAtIndexPath I actually get the data and display it. I self.loadData() in the viewDidLoad function.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:QuoteTableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! QuoteTableViewCell
let quote:PFObject = self.feedData.objectAtIndex(indexPath.row) as! PFObject
cell.contentTextView.text = quote.objectForKey("content") as! String
// Configure the cell...
return cell
}
I am getting no compiler errors and everything seems to be working. The content gets pushed to parse but it doesn't load with these functions. All that runs is an empty table view cells When it should be displaying content from Parse. I feel I am missing something simple logically here... I am a beginner so pardon any silly mistakes. I am following a tutorial but that is a bit outdated.
If you are using a mutable array, you will need to initialize it before adding in objects.
You are changing UI on an asynchronous thread.
The call to parse is async. When this call completes you are reloading your table view.
You should instead dequeu your UI change to the main thread
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
Verify these methods:
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1 //or more if you want
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.feedData.count
}
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.