I'm trying to parse my JSON response to my TableView. The problem is that I don't get any result in my TableView.
import UIKit
import Alamofire
import SwiftyJSON
class MenuViewController: UITableViewController {
var products: [Product] = []
// MARK: View Controller Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request(.GET, Urls.menu).responseJSON { request in
if let json = request.result.value {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let data = JSON(json)
var product: [Product] = []
for (_, subJson): (String, JSON) in data {
product += [Product(id: subJson["id"].int!, name: subJson["name"].string!, description: subJson["description"].string!, price: subJson["price"].doubleValue)]
}
dispatch_async(dispatch_get_main_queue()) {
self.products += product
self.tableView.reloadData()
}
}
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - UITableViewDataSource
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.products.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}
private struct Storyboard {
static let CellReuseIdentifier = "Product"
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(Storyboard.CellReuseIdentifier, forIndexPath: indexPath)
let product = self.products[indexPath.row]
cell.textLabel?.text = product.name
cell.detailTextLabel?.text = product.description
return cell
}
I debugged my numberOfSectionsInTableView and I got this result:
0
0
0
0
0
0
5
Five is the total of items in my JSON request, but my View is never updated. I was watching some classes about Swift, and in one of them, I see the teacher showing this method self.tableView.reloadData() but didn't work for me, or at least, I'm doing it wrong.
The rest of the code, I believe is correct, but without the reload of the data, I can't show it in my tableView.
Thank you.
You mixed up numberOfSectionsInTableView and numberOfRowsInSection
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.products.count
}
You've mixed up numberOfRowsInSection with numberOfSections. Return 1 for numberOfSections (or just delete that function entirely, it's optional) and products.count for numberOfRows.
Related
I'm looking to group and sort a list of users from backendless, similar to iPhone contacts. I want to add sectionIndexTitlesForTableView(_:), titleForHeaderInSection(_:), and sectionForSectionIndexTitle(_:). I haven't found a tutorial on how to do this, and I have been stuck for weeks.
So far, I'm able to retrieve users and populate the table view. I also implemented UISearchBarDelegate.
var users: [BackendlessUser] = []
var filteredUsers : [BackendlessUser] = []
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == self.tableView {
return users.count
} else {
return self.filteredUsers.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
if tableView == self.tableView {
let user = users[indexPath.row]
cell.textLabel?.text = user.name
} else {
let filteredUser = filteredUsers[indexPath.row]
cell.textLabel?.text = filteredUser.name
}
return cell
}
You must have a dictionary of array (name 'data' for example)
data["A"] = ["Ananas", "Anaconda", "Apple"]
data["B"] = ["Banana", "Baby"]
...
data["Z"] = ["Zoro"]
begin:
let letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
var headers: [String] = []
var data : [String: [String]] = [:] // Choose your type
override func viewDidLoad(){
// Do your stuff...
headers = letters.keys.sort()
// init your data var
data = ...
tableView.reloadData()
}
for header:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return headers.count
}
func sectionHeaderTitlesForTableView(tableView: UITableView) -> [String]?{
return headers
}
func tableView: UITableView, titleForHeaderInSection section: Int) -> String?{
return headers[section];
}
cell
func tableView(tableView: UITableView, numberOfRowInSection section: Int) -> Int {
// Exemple
return data[section].count
}
I m trying to populate my tableview with the json data that i m getting on url using swiftyjson .The problem i m facing is , i m apprehending the city in the array but when i use it on the table view the data does not show .How can i fix it ?(you could check the data structure of my json by clicking on the url ).
import UIKit
import SwiftyJSON
import Alamofire
class ViewController: UIViewController , UITableViewDelegate ,UITableViewDataSource {
#IBOutlet var tableview: UITableView!
var data = [String]()
var numberofRows = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
Alamofire.request(.GET, "http://android.goidx.com/search")
.responseJSON {( response) -> Void in
if let value = response.result.value {
let json = JSON(value)
for (index,json):(String, JSON) in json {
//Do something you want
self.numberofRows = json["city"].count
let city = json["city"].string! as String
self.data.append(city)
print(json["city"].stringValue)
}
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numberofRows
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell" , forIndexPath: indexPath) as UITableViewCell
if data.count != 0 {
cell.textLabel?.text = data[indexPath.row]
}
return cell
}
}
After you create your array of cities, you need to reload the table view to refresh the contents
tableView.reloadData()
Also change your datasource, numberOfRowsInSection method to return
self.data.count
I noticed that the cities key in the json returned does not map to an array.
I'm loading about 150 elements from an array of arrays of dictionaries (tasks) and I can get all of the data into my tableview but when I scroll its stupid slow. When I print out the information of one of my functions to the console, it looks like I am getting all of the data back every time I scroll. Is this something I am not loading well (i.e. asynchronously) or do I need to change my functions?
func querySections() -> [String] {
var sectionsArray = [String]()
for task in tasks {
let dueTimes = task.dueTime
sectionsArray.append(dueTimes)
}
let uniqueSectionsArray = Array(Set(sectionsArray.sort()))
// print(uniqueSectionsArray)
return uniqueSectionsArray
}
func queryDueTimes(section:Int) -> [Task] {
var sectionItems = [Task]()
for task in tasks {
let dueTimes = task.dueTime
if dueTimes == querySections()[section] {
sectionItems.append(task)
}
}
print(sectionItems)
return sectionItems
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return querySections()[section]
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return querySections().count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return queryDueTimes(section).count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("TaskCell", forIndexPath: indexPath) as! TaskCell
// Configure the cell...
cell.selectionStyle = .None
let times = queryDueTimes(indexPath.section)
let task = times[indexPath.row]
cell.label.text = task.title
if task.done == true {
cell.checkBox.image = UIImage(named: "checkedbox")
cell.detailLabel.text = "Completed By: \(task.completedBy)"
}
else {
cell.checkBox.image = UIImage(named: "uncheckedbox")
cell.detailLabel.text = ""
}
cell.delegate = self
return cell
}
Basically, in querySections, I'm iterating through all of the dueTimes for each task and then changing them into an array of a set so I can filter out all of the duplicates. This is giving me all of my sections. For queryDueTimes, I'm iterating through the tasks and matching them to a section.
I had a thought about calling the functions in viewDidLoad but that isn't working (it keeps giving me an empty array when I try to pass it to another empty array thats more accessible outside of the function) and I can't access section (for queryDueTimes) in viewDidLoad (as far as what I know how to do).
Update 1:
I think the mistake is on my end. I said that I tasks is an array of arrays when its just an array of Tasks (a struct with all of the properties of each task). When I load the app, I append all of the tasks from my backend to a local array ("tasks"). Should I have an array of arrays for this to work or can I amend my code somehow and get it to work?
Update 2:
I'm getting sectionTimes and tasksInSectionArray as empty arrays when I print them.
var sectionTimes = [String]()
var tasksInSectionArray = [[Task]]()
var tasks = [Task]() {
didSet {
tableView?.reloadData()
}
}
func updateTableView() {
sectionTimes = Set(tasks.map{$0.dueTime}).sort()
tasksInSectionArray = sectionTimes.map{section in tasks.filter{$0.dueTime == section}}
print(sectionTimes)
print(tasksInSectionArray)
tableView.reloadData()
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sectionTimes[section]
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sectionTimes.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tasksInSectionArray[section].count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("TaskCell", forIndexPath: indexPath) as! TaskCell
// Configure the cell...
cell.selectionStyle = .None
let task = tasksInSectionArray[indexPath.section][indexPath.row]
Like you guessed, the data is being loaded and sorted over and over again, instead of only once. Save the results of querySelections and queryDueTimes and use that inside the table view data source methods.
You can do this in viewDidLoad - call both functions once and assign the results to a variable at the class level, and then call tableView.reloadData() (assuming you have a reference to the table view).
var sections: [String] = []
var data: [[Tasks]] = []
func updateTableView() {
sections = Set(tasks.map { $0.dueTime }).sort()
data = sections.map { section in tasks.filter { $0.dueTime == section } }
tableView.reloadData()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sections.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data[section].count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let task = data[indexPath.section][indexPath.row]
// Cell configuration
}
This is basically what DMan said, but I've made an example for you.
Need help to correct this code. I have a coredata app with restaurant names and address, have a search Bar and it is working. What I want to add is a Index and a IndexTitle as showed in the picture below (arrows). Any help is more than welcome. Thanks in advance.
import UIKit
import CoreData
class DictionaryTableViewController: UITableViewController, NSFetchedResultsControllerDelegate, UISearchResultsUpdating
{
var searchController:UISearchController!
var searchResults:[Dictionary] = []
private var dictionaryItems:[Dictionary] = []
var fetchResultController:NSFetchedResultsController!
override func viewDidLoad() {
super.viewDidLoad()
if let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext {
let fetchRequest = NSFetchRequest(entityName: "DictionaryEntity")
do {
dictionaryItems = try managedObjectContext.executeFetchRequest(fetchRequest) as! [Dictionary]
} catch {
print("Failed to retrieve record")
print(error)
}
}
searchController = UISearchController(searchResultsController: nil)
tableView.tableHeaderView = searchController.searchBar
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
tableView.estimatedRowHeight = 30.0
tableView.rowHeight = UITableViewAutomaticDimension
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 26
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchController.active {
return searchResults.count
} else {
return dictionaryItems.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! DictionaryTableViewCell
// Configure the cell...
cell.wordLabel.text = dictionaryItems[indexPath.row].word
cell.definitionSmallLabel.text = dictionaryItems[indexPath.row].definition
let dictionary = (searchController.active) ? searchResults[indexPath.row]: dictionaryItems[indexPath.row]
// Configure the cell...
cell.wordLabel.text = dictionary.word
cell.definitionSmallLabel.text = dictionary.definition
return cell
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String?
{
return "dictionaryItems\(section)"
}
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
if searchController.active{
return false
}else{
return true
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDictionaryDetail" {
if let indexPath = tableView.indexPathForSelectedRow {
let destinationController = segue.destinationViewController as! DictionaryDetailViewController
destinationController.dictionary = (searchController.active) ? searchResults[indexPath.row] : dictionaryItems[indexPath.row]
searchController.active = false
}
}
}
func updateSearchResultsForSearchController(searchController:
UISearchController) {
if let searchText = searchController.searchBar.text {
filterContentForSearchText(searchText)
tableView.reloadData()
}
}
func filterContentForSearchText(searchText: String) {
searchResults = dictionaryItems.filter({ (dictionary:Dictionary) -> Bool in
let wordMatch = dictionary.word!.rangeOfString(searchText, options:
NSStringCompareOptions.CaseInsensitiveSearch)
return wordMatch != nil
})
}
}
What I am trying to have in my table View is the Index (Arrows in the left) and the Index Title (Arrows in the right side).
You have typos in your override function for titleForHeaderInSection
Here is the corrected one, note that v in tableView is capital: (copy paste my code)
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
}
I'd recommend using the auto-complete feature of Xcode. that way, you don't get stuck in insidious errors like this.
UPDATE:
You also need to provide both of these methods to see section header and section index titles.
override func sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? {
return ["A", "B", "C"]
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "A title for section: \(section)"
}
Try the following code:
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 9
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 9
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
// Configure the cell...
cell.textLabel?.text = "\(indexPath.section+1)\(indexPath.row+1)"
return cell
}
override func sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? {
return ["A", "C", "B"]
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "SECTION \(section+1)"
}
}
One way to do this is using built-in properties on the NSFetchedResultsController. The most important part of this is setting up the sectionNameKeyPath (which should be the managed object attribute holding the title).
private var frc: NSFetchedResultsController? = nil
private lazy var fetchedResultsController:NSFetchedResultsController = {
if self.frc != nil {
return self.frc!
}
//grab your managed object context
let moc = dbStore.sharedInstance.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "Transaction")
fetchRequest.fetchBatchSize = 30
fetchRequest.resultType = NSFetchRequestResultType.ManagedObjectResultType
let sortDescriptorByTimeStamp = NSSortDescriptor(key: "timeStamp", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptorByTimeStamp]
//in-memory cache when cachename is nil, delegate is non-nil
//note the sectionNameKeyPath requires that I have a related category object with the categoryName attribute on my Transaction managed object
let nfrc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: moc, sectionNameKeyPath: "category.categoryName", cacheName: nil)
self.frc = nfrc
do
{
try self.frc!.performFetch()
} catch let e as NSError {
fatalError(e.localizedDescription)
}
return self.frc!
}()
Section Titles
To get the section titles you implement this method.
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let sectionTitle = self.fetchedResultsController.sections?[section].name ?? "No Category Titles"
return sectionTitle
}
Table Index
To setup the index you implement the following method.
func sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? {
return self.fetchedResultsController.sectionIndexTitles ?? [""]
}
Note the Quick Help Documentation when you click on
sectionIndexTitles. The array of section index titles. The default
implementation returns the array created by calling
sectionIndexTitleForSectionName: on all the known sections. You should
override this method if you want to return a different array for the
section index. You only need this method if you use a section index.
Source
If you want to see this in context, to get the gist of how to use the NSFetchedResultsController in this way you can checkout my sample at this location.
Alternates (Arrays/Dictionaries)
There are a few other tutorials about how to build an index from arrays and dictionaries (but I recommend the method above as simplest way to proceed).
Alternate Tutorial #1
Alternate Tutorial #2
Alternate Implementation
Note: Code in this section is from Alternate Tutorial #1
// `UIKit` convenience class for sectioning a table
let collation = UILocalizedIndexedCollation.currentCollation()
as UILocalizedIndexedCollation
....
/* section headers
appear above each `UITableView` section */
override func tableView(tableView: UITableView,
titleForHeaderInSection section: Int)
-> String {
// do not display empty `Section`s
if !self.sections[section].users.isEmpty {
return self.collation.sectionTitles[section] as String
}
return ""
}
/* section index titles
displayed to the right of the `UITableView` */
override func sectionIndexTitlesForTableView(tableView: UITableView)
-> [AnyObject] {
return self.collation.sectionIndexTitles
}
override func tableView(tableView: UITableView,
sectionForSectionIndexTitle title: String,
atIndex index: Int)
-> Int {
return self.collation.sectionForSectionIndexTitleAtIndex(index)
}
I am working on a table view project I've seen in a tutorial, then I came across this piece of code that gives me the **error: Definition conflicts with previous value.**
The piece of code is:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int, titleForHeaderInSection section:Int) -> String? {
// Return the number of rows in the section.
return animalSelectionTitles[section]
}
I have tried to change the String? into String or Int, but String gives me the same error and Int gives me an error on the return line.
Here's my complete code:
import UIKit
class AnimalTableViewController: UITableViewController {
var animalsDict = [String: [String]] ()
var animalSelectionTitles = [String] ()
let animals = ["Bear", "Black Swan", "Buffalo", "Camel", "Cockatoo", "Dog", "Donkey", "Emu", "Giraffe", "Greater Rhea", "Hippopotamus", "Horse", "Koala", "Lion", "Llama", "Manatus", "Meerkat", "Panda", "Peacock", "Pig", "Platypus", "Polar Bear", "Rhinoceros", "Seagull", "Tasmania Devil", "Whale", "Whale Shark", "Wombat"]
func createAnimalDict() {
for animal in animals {
let animalKey = animal.substringFromIndex(advance(animal.startIndex, 1))
if var animalValues = animalsDict[animalKey] {
animalValues.append(animal)
animalsDict[animalKey] = animalValues
} else {
animalsDict[animalKey] = [animal]
}
}
animalSelectionTitles = [String] (animalsDict.keys)
animalSelectionTitles.sort({ $0 < $1})
animalSelectionTitles.sort( { (s1:String, s2:String) -> Bool in
return s1 < s2
})
}
override func viewDidLoad() {
super.viewDidLoad()
createAnimalDict()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// Return the number of sections.
return animalSelectionTitles.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int, titleForHeaderInSection section:Int) -> String? {
// Return the number of rows in the section.
return animalSelectionTitles[section]
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
// Configure the cell...
cell.textLabel?.text = animals[indexPath.row]
// Convert the animal name to lower case and
// then replace all occurences of a space with an underscore
let imageFilename = animals[indexPath.row].lowercaseString.stringByReplacingOccurrencesOfString(" ", withString: "_", options: nil, range: nil)
cell.imageView?.image = UIImage(named: imageFilename)
return cell
}
Two things:
1)
You should change your line
let animalKey = animal.substringFromIndex(advance(animal.startIndex, 1))
Currently it substrings from the 2nd character, which means that for the input Black Swan then animalKey would be equal to lack Swan. Instead you should use the following line:
let animalKey = animal.substringToIndex(advance(animal.startIndex, 1))
2)
There is no method in the UITableViewDataSource Protocol which is called tableView:numberOfRowsInSection:titleForHeaderInSection. Instead you need to split it into the following two methods:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let title = animalSelectionTitles[section]
return animalsDict[title]!.count
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return animalSelectionTitles[section]
}
UPDATE 1:
In your tableView:cellForRowAtIndexPath, you should also update the retrieving of the animal name to reflect what is stored in the dictionary like so:
// Configure the cell...
let secTitle = animalSelectionTitles[indexPath.section]
let animalName = animalsDict[secTitle]![indexPath.row]
cell.textLabel?.text = animalName