Array has append, but no value inside - ios

I've declared a String array in my Swift file.
var postTitleArray : [String] = []
In my viewDidLoad method, I've append values into my array...
RestApiManager.sharedInstance.makeGetRequest("/development/vw_posts?filter=parent_term_id%20%3D%20%22%5B82%5D%22", onCompletion: { json in
for result in json["record"].arrayValue
{
let postTitle = result["post_title"].stringValue
print(postTitle)
self.postTitleArray.append(postTitle)
}
})
Other than that, I've use the count of array into the numberOfRowsInSection to set the rows number of my tableView...
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return self.postTitleArray.count
}
However, the numberOfRowsInSection has return 0, why could it happened, I've assigned value into my postTitleArray, anyone can help me take a look on this? Appreciated...

Your API call is likely not finished before numberOfRowsInSection is called.
Try calling reloadData on your UITableView instance after you have populated your postTitleArray. (I'm assuming here you have a reference to your UITableView and it's called tableView)
RestApiManager.sharedInstance.makeGetRequest("/development/vw_posts?filter=parent_term_id%20%3D%20%22%5B82%5D%22", onCompletion: { json in
for result in json["record"].arrayValue
{
let postTitle = result["post_title"].stringValue
print(postTitle)
self.postTitleArray.append(postTitle)
}
self.tableView.reloadData()
})

You should check :
if let postTitle = result["post_title"]?.stringValue{
self.postTitleArray.append(postTitle)
self.tableView.reloadData()
}

Related

Unexpected non-void return value in void function in Swift using numberOfRowsInSection

This question has already been asked numerous times, but nothing seems to be working in my particular case. As you can see in my code below, I have created an extension of my View Controller for my UITableViewDataSource. I am trying to pull information from my Firebase Realtime Database and depending on what I get, return a certain Int. The issue is that whenever I try and return an Int from within the Firebase Snapshot, I receive this error: "Unexpected non-void return value in void function." I understand the reason for this, but I am not sure how I can make my code work. Any solutions?
My code:
extension LikeOrDislikeViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let uid = Auth.auth().currentUser?.uid
Database.database().reference().child("Num Liked").child(uid!).observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let numMoviesLiked = ((dictionary["Number of Movies Liked"] as? Int))!
if numMoviesLiked%4 == 0 {
return numMoviesLiked/4
}
}
})
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.backgroundColor = UIColor.red
tableView.rowHeight = 113
cell.textLabel?.text = "\(indexPath.row)"
return cell
}
}
Your problem is this return numRows inside the database block , it doesn't consider to return any values , also the call is asynchronous so don't expect it to finish before the return 10 of numberOfRowsInSection, completely remove this part and put it inside viewDidLoad
Database.database().reference().child("Num Liked").child(uid!).observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let numMoviesLiked = ((dictionary["Number of Movies Liked"] as? Int))!
if numMoviesLiked/4 == 0 {
numRows = numMoviesLiked/4
self.tableView.reloadData()
}
}
})
logically if you do if only when you compare numMoviesLiked/4 == 0 then numRows will be always 0

Returning a variable outside closure

I want to check the number of posts in my database and return it as the numberOfRows in my tableView. However, the below code does not work. It returns 1 everytime. I know it's because I'm setting the var requestPostCount inside a closure, but I'm not sure on how to fix it.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var requestPostcount: Int = 1
if segmentOutlet.selectedSegmentIndex == 0 {
// This is temporary
return 1
}
else {
// Query the database to check how many posts are there
ref.child("Request Posts").observe(.value, with: { (snapshot) in
var requestPostCount = Int(snapshot.childrenCount)
// If database is empty, only 1 row is needed to display error message
if requestPostCount == 0 {
requestPostCount = 1
}
})
}
return requestPostcount
}
numberOfRowsInSection is the wrong place to be querying your database.
At any rate, the method will return requestPostcount with your default value before the closure finishes executing.
You need to find a better place to query your database so that when numberOfSections is called, the data is already available.
You misunderstand how async methods work. You can't do an async query of a remote database in your numberOfRows method and return a value.
It's not possible to have a function that calls an async method return the results of the async method as a function result.
You need to set up your model to have no data in it, send the database query, parse the results, and then, in the completion closure, update your model and then, on the main thread, tell the table view to update.
You need to load data in ViewDidLoad . after getting data then reload tableview,
var requestPostcount: Int = 1 // declared variable
override func viewDidLoad() {
super.viewDidLoad()
ref.child("Request Posts").observe(.value, with: { (snapshot) in
var requestPostCount = Int(snapshot.childrenCount)
// If database is empty, only 1 row is needed to display error message
if requestPostCount == 0 {
requestPostCount = 1
}
tblView.reloadData()
})
}
Now numberOfRowsInSection as below
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if segmentOutlet.selectedSegmentIndex == 0 {
return 1
}
else {
return requestPostcount
}

UITableView Section Titles

I am attempting to create a UITableView with sections from my Parse objects. I have the Parse objects successfully. However, I am not sure how or what the best way to get the dates out of the objects to create my sections titles would be.
If I have an array of the objects should I just for through and create a date list? How would I relate that back to the date and that the object should be under that date
I was thinking of using a struct with Date and Item vars to hold the data cleaner, but I don't know I can do it that way either
Any suggestions / examples of what is the best way to do this?
EDIT:
func getExpenses() {
let query = ExpenseItem.query()
query?.order(byDescending: "expenseDate")
query?.findObjectsInBackground(block: { (expenseResult, error) in
if error == nil {
var sectionArray = [ExpenseItem]()
for object in expenseResult as! [ExpenseItem] {
if let firstObject = sectionArray.first {
if Calendar.current.compare(firstObject.expenseDate, to: object.expenseDate, toGranularity: .day) != .orderedSame {
self.tableData.append(sectionArray)
sectionArray = [ExpenseItem]()
}
} else {
sectionArray.append(object)
}
}
if sectionArray.count != 0 {
self.tableData.append(sectionArray)
}
}
})
}
Sort your array data from Parse into ascending order by date.
Declare an array of arrays of PFObject (or your PFObject subclass if you have one): var tableData: [[PFObject]]()
Loop through your Parse data, splitting it by date:
var sectionArray = [PFObject]()
// pfObjects contains the objects you retrieved from Parse, ordered by date
for object in pfObjects {
if let firstObject = sectionArray.first {
if object["dateField"] != firstObject["dateField"] {
self.tableData.append(sectionArray)
sectionArray = [PFObject]()
}
}
sectionArray.append(object)
}
if sectionArray.count != 0 {
self.tableData.append(sectionArray)
}
Now you have the data structure you need. The number of sections is the count of the outer array and the number of items in each section is the number of items in each inner array:
override func numberOfSections(in tableView: UITableView) -> Int {
return self.tableData.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tableData[section].count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = self.tableData[indexPath.section][indexPath.row]
// deque a cell and configure it with item
}
To get the section header for each section, you can just use the date of the first object in that section's array, since all objects in the section have the same date.

Proper way to load data to TableView

I have an application that loads list of questions from JSON data and shows them on TableView.
Everything is working fine most of the time but it seems to be that I am doing something wrong and that is why - app crashes.
It happens rarely so it is hard to detect but I am sure that there must a problem with the logic.
So I have model class for question and array for question items :
class questionItem {
var id = 0
var title : String = ""
var question : String = ""
}
var questions = [questionItem]()
Inside my ViewController I have IBOutlet for TableView and I placed data loading inside viewDidLoad
class QuestionsListVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var questionsTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
get_questions()
}
func get_questions()
{
let request = NSMutableURLRequest(URL:myURL!)
request.HTTPMethod = "POST"
let postString = ""
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request)
{
data, response, error in
if error != nil {
print("error=\(error)")
return
}
//clearing array for new items
questions.removeAll(keepCapacity: false)
dispatch_async(dispatch_get_main_queue(),{
var json = JSON(data: data!)
if let items = json["questions"].array {
for item in items {
let question = questionItem()
question.id = item["id"].int!
question.title = item["title"].string!;
question.question = item["question"].string!
questions.append(question)
}
}
self.questionsTableView.reloadData()
});
}
task.resume()
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return questions.count
}
Error is shown inside cellForRowAtIndexPath
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell : QuestionsListCell = self.questionsTableView.dequeueReusableCellWithIdentifier("QuestionsListCell") as! QuestionsListCell
//error happens here - Index out of range
print(questions[indexPath.row].title)
It happens one time in six cases and there is no errors in other 5 of 6 tests - but I don't understand why.
This points to a problem with the
numberOfSectionsInTableView
and/or
numberOfRowsInSection
Can you post your current implementation of these?
If you only displaying one continuous list, the numberOfSectionsInTableView should always return 1, and you need to check numberOfRowsInSection is accurately returning the number of items in the datasource.
Edit:
Can you try clearing the existing datasource on the main thread immediately before updating with the new items as in the code below:
dispatch_async(dispatch_get_main_queue(),{
questions.removeAll(keepCapacity: false)
var json = JSON(data: data!)
if let items = json["questions"].array {
for item in items {
let question = questionItem()
question.id = item["id"].int!
question.title = item["title"].string!;
question.question = item["question"].string!
questions.append(question)
}
}
self.questionsTableView.reloadData()
});
The call to questions.removeAll in your code makes the following sequence of events possible:
numberOfRowsInSection is called before questions.removeAll, returning the old non-zero capacity
questions.removeAll clears questions
cellForRowAtIndexPath is called before questions are re-populated, causing index out of range exception
One way to fix is is relatively straightforward: make a newQuestions array, populate it in get_questions, and swap it in when numberOfRowsInSection is called:
// Add this to your class
var newQuestions : [questionItem]
// Change get_questions:
dispatch_async(dispatch_get_main_queue(), {
var json = JSON(data: data!)
if let items = json["questions"].array {
var tmpQuestions = [questionItem]()
for item in items {
let question = questionItem()
question.id = item["id"].int!
question.title = item["title"].string!;
question.question = item["question"].string!
tmpQuestions.append(question)
}
newQuestions = tmpQuestions
self.questionsTableView.reloadData()
}
});
// Change numberOfRowsInSection
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
if newQuestions != nil {
questions = newQuestions
newQuestions = nil
}
return questions.count
}
Note how get_questions does not populate newQuestions directly. Instead, it builds tmpQuestions, and sets it to newQuestions only when it is fully built.
Try with below code,
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell : QuestionsListCell = self.questionsTableView.dequeueReusableCellWithIdentifier("QuestionsListCell") as! QuestionsListCell
if let objModel : questionItem = questions[indexPath.row] as? questionItem
{
print(objModel.title)
}
}
Change your cellForRow with this method. Hope this helps you.
Just to avoid crash, I would have added following three safe checks.
1. Check Array count before clearing out.
if questions.count > 0
{
questions.removeAll()
}
2. Check array count before table Reload
if questions.count > 0
{
self.questionsTableView.reloadData()
}
3. In cellForRowAtIndex method Check for value in array of object, before putting on Cell
if let objModel : questionItem = questions[indexPath.row] as? questionItem
{
print(" title is \(objModel.title)")
}

Can't retain array data outside of method in SWIFT

I am storing the category name from a JSON in an Array using alamofire .
The array has values only when it is called from this Method CategoryNameFunc.
If i call the the array from the tableview or any other method it always returns 0
CODE
var CategoryNameArray : [String] = []
override func viewDidLoad() {
super.viewDidLoad()
Network()
tester()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return CategoryNameArray.count // This returns 0
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell : UITableViewCell = self.TableView.dequeueReusableCellWithIdentifier("cell") as! UITableViewCell
println(self.CategoryNameArray[indexPath.row])
cell.textLabel?.text = "Hello"
return cell
}
func Network(){
Alamofire.request(.GET, "http://www.wive.com/index.php/capp/category_list")
.responseJSON { (_, _, data, _) in
let json = JSON(data!)
let count = json.count
self.CategoryNameFunc(json, Count: count) }
}
func CategoryNameFunc(Json: JSON, Count: Int)
{
for index in 0...Count-1 {
let name = Json[index]["CATEGORY_NAME"].string
CategoryNameArray.append(name!)
}
// This returns 23 (The correct value)
println(self.CategoryNameArray.count)
}
When you called Network() function it creates a new thread (Alamofire start an asynchronous request) and your tester() function is not waiting for your Network() function to finish before you count your CategoryNameArray().But your CategoryNameFunc() function waits for network operation to finish.
I am not sure (didn't use Almofire) but it think, this happens because the method Network, more precisly the Almofire request is fired asynchronously.
So, the methods Network() and tester() are running simultaneously, but because Network() needs to fetch data first, tester() is faster and is executed first.
The proper way to execute tester() and Network() one after another would be:
func CategoryNameFunc(Json: JSON, Count: Int)
{
for index in 0...Count-1 {
let name = Json[index]["CATEGORY_NAME"].string
CategoryNameArray.append(name!)
}
// run tester, AFTER you have the data.
tester()
}

Resources