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)")
}
Related
I am trying to populate a UITableView using an array and I am unable to do so. Here is what I have so far. This code is for retrieving data and storing it in the array that I am using to populate the UITableView:
func prepareForRetrieval() {
Database.database().reference().child("UserCart").child(Auth.auth().currentUser!.uid).observe(.value, with: {
(snapshot) in
for snap in snapshot.children.allObjects {
let id = snap as! DataSnapshot
self.keyArray.append(id.key)
}
self.updateCart()
})
}
func updateCart() {
for key in keyArray {
Database.database().reference().child("UserCart").child(Auth.auth().currentUser!.uid).child(key).observeSingleEvent(of: .value, with: {
(snapshot) in
let value = snapshot.value as? NSDictionary
let itemName = value?["Item Name"] as! String
let itemPrice = value?["Item Price"] as! Float
let itemQuantity = value?["Item Quantity"] as! Int
self.cartArray.append(CartData(itemName: itemName, itemQuantity: itemQuantity, itemPriceNumber: itemPrice))
print(self.cartArray.count)
})
}
}
The data is properly appending into the array and when I print the count of the array, it prints the correct count. This means that the data is there. However, when I try to populate a UITableView, it doesn't detect any data. I have the following code to make sure that there is data in the array before trying to populate the UITableView:
override func viewDidLoad() {
super.viewDidLoad()
cartBrain.prepareForRetrieval()
if cartBrain.cartArray.isEmpty == false{
tableViewOutlet.dataSource = self
tableViewOutlet.reloadData()
}
else {
tableViewOutlet.isHidden = true
tableViewOutlet.isUserInteractionEnabled = false
purchaseButtonOutlet.isEnabled = false
cartEmptyLabel.text = "Your cart is empty. Please add items and check back later."
}
}
When I open the View Controller, the TableView is disabled because it doesn't detect any data. I have already set the data source to self and the thing is that when the count of the array is printed, it again prints the correct amount. I have already set the data source to self for the UITableView. Here is my code for the UITableView:
extension CartViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cartBrain.cartArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cartcustomcell", for: indexPath)
cell.textLabel?.text = cartBrain.cartArray[indexPath.row].itemName
cell.detailTextLabel?.text = String(cartBrain.cartArray[indexPath.row].itemQuantity)
return cell
}
}
I don't understand why the count of the array prints the correct amount meaning that there is data stored in it but when the View Controller is loaded, it detects that the array is empty. Thanks for the help and I'm sorry if the question is a bit unclear.
After appending data to cartArray in updateCart you should reloadData(), like this:
weak var tableViewOutlet: UITableView?
func updateCart() {
for key in keyArray {
Database.database().reference().child("UserCart").child(Auth.auth().currentUser!.uid).child(key).observeSingleEvent(of: .value, with: {
(snapshot) in
let value = snapshot.value as? NSDictionary
let itemName = value?["Item Name"] as! String
let itemPrice = value?["Item Price"] as! Float
let itemQuantity = value?["Item Quantity"] as! Int
self.cartArray.append(CartData(itemName: itemName, itemQuantity: itemQuantity, itemPriceNumber: itemPrice))
DispatchQueue.main.async {
self.tableViewOutlet.reloadData()
}
})
}
}
The updateCart doesn't seem to have any connection to the tableViewOutlet so you need to pass in a reference to it in your viewDidLoad like this:
override func viewDidLoad() {
super.viewDidLoad()
cartBrain.tableViewOutlet = tableViewOutlet
cartBrain.prepareForRetrieval()
Note: Since you're using a for loop to trigger the async call multiple times you can use the array count to check if all the items are appended to do the reload to avoid multiple reloads.
Recently got stuck on a problem of assigning freshly downloaded JSON data to table view datasource variable. I suppose the problem is something obvious but my skill is not enough to gather the big picture. Let me share a bunch of code.
(1) A function retrieves the data from Open Weather Map API (defined in the separate class 'GetWeather').
func getMowForecast(completion: #escaping ((WeatherForecast?, Bool)) -> Void) {
let url = URL(string: "http://api.openweathermap.org/data/2.5/forecast?id=524901&APPID=b3d57a41f87619daf456bfefa990fce4&units=metric")!
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
do {
let json = try JSONDecoder().decode(WeatherForecast.self, from: data)
completion((json, true))
} catch {
print(error)
completion((nil, false))
}
} else {
print(error)
}
}
task.resume()
}
Everything works fine here. JSON loads correctly and fits the data model.
Here's a link to JSON data to be displayed in tableView: https://pastebin.com/KkXwxYgS
(2) A controller handles the display of retrieved JSON data in tableView format
import UIKit
class ForecastViewController: UITableViewController {
#IBOutlet weak var tableV: UITableView! // tableView outlet in the IB
let weatherGetter = GetWeather() // object to handle the JSON retrieval
var tableData: WeatherForecast? // tableView data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tableData?.list.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TableVCCell
cell.dateLabel.text = "\(self.tableData?.list[indexPath.row].dt)"
cell.tempLabel.text = "\(self.tableData?.list[indexPath.row].main.temp)"
cell.feelsLikeLabel.text = "\(self.tableData?.list[indexPath.row].main.feels_like)"
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.prefersLargeTitles = true
tableV.delegate = self
tableV.dataSource = self
weatherGetter.getMowForecast { (data, status) in
if let data = data, status {
} else if status {
print("-------- Ошибка разбора данных прогноза погоды --------")
} else {
print("-------- Ошибка получения данных прогноза погоды --------")
}
self.tableData = data
print(self.tableData)
}
print(self.tableData?.list.count) // returns nil
self.tableData = weatherGetter.getMowForecast(completion: ((tableData, true))) // error - Cannot convert value of type '(WeatherForecast?, Bool)' to expected argument type '((WeatherForecast?, Bool)) -> Void'
}
}
The problem is - the table view gets nil datasource so it is unable to load the data and shows the blank screen.
I suppose the mistake is in scope - I try to retrieve the JSON data inside a function and it does not go anywhere else. What I am wondering about is - how comes that assigning the data to self.tableData does not makes any effect?
Could you please help.
Thank you!
Regards
First of all delete
print(self.tableData?.list.count) // returns nil
self.tableData = weatherGetter.getMowForecast(completion: ((tableData, true))) // error - Cannot convert value of type '(WeatherForecast?, Bool)' to expected argument type '((WeatherForecast?, Bool)) -> Void'
The error occurs because the method does not return anything and the completion handler syntax is wrong. Both lines are pointless anyway due to the asynchronous behavior of getMowForecast
Secondly I recommend to declare the data source array as a non-optional array of the type which represents List. Then you get rid of all those unnecessary optionals.
var tableData = [List]()
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.tableData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TableVCCell
let weatherData = self.tableData[indexPath.row]
cell.dateLabel.text = "\(weatherData.dt)"
cell.tempLabel.text = "\(weatherData.main.temp)"
cell.feelsLikeLabel.text = "\(weatherData.main.feels_like)"
return cell
}
To be able to display the data – as already mentioned by others – you have to reload the table view in the completion handler. And assign the data only if status is true.
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.prefersLargeTitles = true
tableV.delegate = self
tableV.dataSource = self
weatherGetter.getMowForecast { [weak self] (data, status) in
if let data = data, status {
self?.tableData = data.list
DispatchQueue.main.async {
self?.tableV.reloadData()
}
} else if status {
print("-------- Ошибка разбора данных прогноза погоды --------")
} else {
print("-------- Ошибка получения данных прогноза погоды --------")
}
}
}
And consider that the message Ошибка разбора данных прогноза погоды will be never displayed.
You need to reload the table inside the callback as it's asynchronous
self.tableData = data
print(self.tableData)
DispatchQueue.main.async { self.tableV.reloadData() }
The problem is really, that while offline the UItableview is not populating. Basically while online it will read from a php coded website in json and parse its data to NSUserdefaults and It will display data using the defaults set. This works very well when online.
I tested it like this. first I run the code while online( wifi connected ) to first populate the defaults, then exit the tableview, turn wifi off, and then go back in. Nothing shows. I put a breakpoint/print text where the code should had run, but it breakpoint never got excuted, the print text never got printed.
is there a reason why the code isnt running when offline? am i missing a setting i should add?
var messagesArray:[String] = [String]()
var dateArray:[String] = [String]()
class Singleton {
static let sharedInstance: UserDefaults = {
let instance = UserDefaults.standard
// setup code
return instance
}()
}
//let defaults = UserDefaults.standard
let defaults = Singleton.sharedInstance
override func viewDidLoad() {
super.viewDidLoad()
//removeDefaults()
if (isInternetAvailable() == true)
{
self.retrieveMessages("")
//storeLocal()
}
else {
// TODO data is available but not displayed ??
for (key, value) in defaults.dictionaryRepresentation() {
print("\(key) = \(value) \n")
}
}
//display current notification
//nRead()
self.notificationTable.dataSource = self
self.notificationTable.delegate = self
// Do any additional setup after loading the view.
}
func tableView(_ tableView:UITableView, numberOfRowsInSection section: Int) -> Int{
return messagesArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// this code does not run when offline
//test
let nCell = tableView.dequeueReusableCell(withIdentifier: "nCell") as UITableViewCell!
//let myLabelTitle = nCell?.viewWithTag(1) as! UILabel
let myLabelDate = nCell?.viewWithTag(2) as! UILabel
let myLabelDescription = nCell?.viewWithTag(3) as! UILabel
//messagesArray ["nContent":["Test1", "Test2"]]
myLabelDescription.text = defaults.string(forKey: "nDescription\(indexPath.row + 1)")
myLabelDate.text = defaults.string(forKey: "nDate\(indexPath.row + 1)")
//print(defaults.string(forKey:"nDate1"))
print("this code runs even while offline")
let readValue = defaults.string(forKey: "nRead\(indexPath.row + 1)")
if (readValue == "1" )
{
myLabelDate.textColor = UIColor.black
}
else
{
myLabelDate.textColor = UIColor.red
}
return nCell!
}
func tableView(_ tableView:UITableView, numberOfRowsInSection section: Int) -> Int{
return messagesArray.count
}
messagesArray.count prints 0, thus the code isn't running. fixed my own issue
Hi guys I have been stuck with the error 'array index out of range'. I am getting JSON data from the internet and converting it into an array. I have been loading comments (Note : I have posted this question as i couldnt find any question related to my problem)
So I have been loading comments in a jsonData. The code i've been using to load comments is this.
api.loadComments(shot.commentsUrl, completion: didLoadComments)
The code for completion is this.
func didLoadComments(comments : [Comment]){
self.comments = comments
self.tableView.reloadData()
}
It is defined in the tableView...
let cell = tableView.dequeueReusableCellWithIdentifier("Cell10", forIndexPath: indexPath) as! CommentCell
//This is where the error occurs
let comment = comments[indexPath.row]
cell.nameLabel.text = comment.user.name
cell.commentLabel.text = comment.body
cell.avatarImageView.sd_setImageWithURL(NSURL(string: comment.user.avatarUrl), placeholderImage: UIImage(named: "2"))
return cell
and the comments variable is defined like this.
var comments : [Comment] = Comment
The loadComments method is returning comments as shown in the log
The log which shows comments is not nil
The code for loadComments is this.
func loadComments(commentsUrl: String, completion: (([Comment]) -> Void)!) {
let urlString = commentsUrl + "?access_token=" + Config.ACCESS_TOKEN
let session = NSURLSession.sharedSession()
let url = NSURL(string: urlString)
let task = session.dataTaskWithURL(url!) {
(data, response, error) -> Void in
if error != nil {
print(error!.localizedDescription)
} else {
var comments = [Comment]()
do {
let commentsData = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as! NSArray
for commentData in commentsData {
let comment = Comment(data: commentData as! NSDictionary)
comments.append(comment)
}
}
catch {
}
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
dispatch_async(dispatch_get_main_queue()) {
completion(comments)
}
}
}
task.resume()
}
The numberOfRowsInSection looks like this.
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 9 + comments.count
}
Feel free to ask me for any more code.
Thanks in advance
Aryan
The reason of that problem is :
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return comments.count
}
This method return your items count. And your method
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! CommentCell
return cell
}
will run in cycle for each item in this list.
You should check items count in method "numberOfRowsInSection" and items count in "cellForRowAtIndexPath"
EDITED :
Of course you did.
return 9 + comments.count
if your comments count will be 0 your fun cellForRowAtIndexPath will call 9 times!!!
Use
comments.count
or add some checks in your code.
Here is how you can proceed with it.
Create a Comment class:
class Comment {
var user: User?
var commentLabel: String?
}
Create a User class:
class User {
var name: String?
var avatarUrl: UIImageView?
}
Then in your for loop parse the Json into Comment object and then add it to comments array:
for commentData in commentsData {
let commentDictionary = Comment(data: commentData as! NSDictionary)
var temp:NSArray = commentDictionary["user"] as! NSArray
var user: User?
user.name = temp["name"]
user.avatarUrl = temp["avatarUrl"]
var comment: Comment?
comment.commentLabel = commentDictionary["body"]
comments.append(comment)
}
This should be it.
I'm trying to get search results to display on a tableView. I believe I have correctly parsed the JSON, the only problem is that the results won't display on my tableView.
Here is the code:
var searchText : String! {
didSet {
getSearchResults(searchText)
}
}
var itemsArray = [[String:AnyObject]]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.reloadData()
}
// MARK: - Get data
func getSearchResults(text: String) {
if let excapedText = text.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()) {
Alamofire.request(.GET, "https://api.duckduckgo.com/?q=\(excapedText)&format=json")
.responseJSON { response in
guard response.result.error == nil else {
// got an error in getting the data, need to handle it
print("error \(response.result.error!)")
return
}
let items = JSON(response.result.value!)
if let relatedTopics = items["RelatedTopics"].arrayObject {
self.itemsArray = relatedTopics as! [[String:AnyObject]]
}
if self.itemsArray.count > 0 {
self.tableView.reloadData()
}
}
}
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 6 // itemsArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("SearchResultCell", forIndexPath: indexPath) as! SearchResultCell
if itemsArray.count > 0 {
var dict = itemsArray[indexPath.row]
cell.resultLabel?.text = dict["Text"] as? String
} else {
print("Results not loaded yet")
}
return cell
}
If I had a static API request I think this code would work because I could fetch in the viewDidLoad and avoid a lot of the .isEmpty checks.
When I run the program I get 6 Results not loaded yet (from my print in cellForRowAtIndexPath).
When the completion handler is called response in, it goes down to self.items.count > 3 (which passes) then hits self.tableView.reloadData() which does nothing (I checked by putting a breakpoint on it).
What is the problem with my code?
Edit
if self.itemsArray.count > 0 {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
}
Tried this but the tableView still did not reload even though its reloading 6 times before the alamofire hander is called...
Here is the strange thing, obviously before the hander is called my itemsArray.count is going to be 0 so that's why I get Results not loaded yet. I figured out why it repeats 6 times though; I set it in numberOfRowsInSection... So #Rob, I can't check dict["Text"] or cell.resultLabel?.text because they're never getting called. "Text" is correct though, here is the link to the JSON: http://api.duckduckgo.com/?q=DuckDuckGo&format=json&pretty=1
Also, I do have the label linked up to a custom cell class SearchResultCell
Lastly, I am getting visible results.
Two problems.
One issue is prepareForSegue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let searchResultTVC = SearchResultsTVC()
searchResultTVC.searchText = searchField.text
}
That's not using the "destination" view controller that was already instantiated, but rather creating a second SearchResultsTVC, setting its searchText and then letting it fall out of scope and be deallocated, losing the search text in the process.
Instead, you want:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let searchResultTVC = segue.destination as? SearchResultsTVC {
searchResultTVC.searchText = searchField.text
}
}
You shouldn't rely on didSet in the destination view controller to trigger the search, because that property is getting set by source view controller before the table view has even been instantiated. You do not want to initiate the search until view has loaded (viewDidLoad).
I would advise replacing the didSet logic and just perform search in viewDidLoad of that SearchResultsTVC.
My original answer, discussing the code provided in the original question is below.
--
I used the code originally provided in the question and it worked fine. Personally, I might streamline it further:
eliminate the rid of the hard coded "6" in numberOfRowsInSection, because that's going to give you false positive errors in the console;
the percent escaping not quite right (certain characters are going to slip past, unescaped); rather than dwelling on the correct way to do this yourself, it's better to just let Alamofire do that for you, using parameters;
I'd personally eliminate SwiftyJSON as it's not offering any value ... Alamofire already did the JSON parsing for us.
Anyway, my simplified rendition looks like:
class ViewController: UITableViewController {
var searchText : String!
override func viewDidLoad() {
super.viewDidLoad()
getSearchResults("DuckDuckGo")
}
var itemsArray: [[String:AnyObject]]?
func getSearchResults(text: String) {
let parameters = ["q": text, "format" : "json"]
Alamofire.request("https://api.duckduckgo.com/", parameters: parameters)
.responseJSON { response in
guard response.result.error == nil else {
print("error \(response.result.error!)")
return
}
self.itemsArray = response.result.value?["RelatedTopics"] as? [[String:AnyObject]]
self.tableView.reloadData()
}
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemsArray?.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SearchResultCell", for: indexPath) as! SearchResultCell
let dict = itemsArray?[indexPath.row]
cell.resultLabel?.text = dict?["Text"] as? String
return cell
}
}
When I did that, I got the following:
The problem must rest elsewhere. Perhaps it's in the storyboard. Perhaps it's in the code in which searchText is updated that you didn't share with us (which triggers the query via didSet). It's hard to say. But it doesn't appear to be a problem in the code snippet you provided.
But when doing your debugging, make sure you don't conflate the first time the table view delegate methods are called and the second time they are, as triggered by the responseJSON block. By eliminating the hardcoded "6" in numberOfRowsInSection, that will reduce some of those false positives.
I think you should edit :
func getSearchResults(text: String) {
if let excapedText = text.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()) {
Alamofire.request(.GET, "https://api.duckduckgo.com/?q=\(excapedText)&format=json")
.responseJSON { response in
guard response.result.error == nil else {
// got an error in getting the data, need to handle it
print("error \(response.result.error!)")
return
}
let items = JSON(response.result.value!)
if let relatedTopics = items["RelatedTopics"].arrayObject {
self.itemsArray = relatedTopics as! [[String:AnyObject]]
// if have result data -> reload , & no if no
if self.itemsArray.count > 0 {
self.tableView.reloadData()
}
}else{
print("Results not loaded yet")
}
}
}
}
And
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("SearchResultCell", forIndexPath: indexPath) as! SearchResultCell
// i 'm sure: itemsArray.count > 0 in here if in numberOfRowsInSection return itemsArray.count
var dict = itemsArray[indexPath.row]
cell.resultLabel?.text = dict["Text"] as? String
return cell
}
And you should share json result(format) ,print dict in cellForRowAtIndexPath, so it s easy for help