Ambiguous reference to member '(_:numberOfRowsInSection:)' - ios

I'm trying to GET gists from Github and pop them in a table view,
here's the entire code, Gist is a class defined elsewhere:
var gists = [Gist]()
override func viewDidAppear(animated: Bool) {
loadGists()
}
func loadGists() {
GithubAPIManager.sharedInstance.fetchPublicGists() { result in
guard result.error == nil else {
print("Error 1")
return
}
if let fetchedGists = result.value {
self.gists = fetchedGists
}
self.tableView.reloadData()
//Error here.
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return gists.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell")!
let gist = gists[indexPath.row]
cell.textLabel?.text = gist.description
cell.detailTextLabel?.text = gist.ownerLogin
return cell
}

So, the problem is I didn't add an outlet of the table view to the View Controller.swift.
Just dragged the table view to the .swift file to create it.

Related

Single table view through two different NSFetchedResultsControllers with sections

Good morning to everyone. I am using Swift 3.1.1, Xcode 8.3.2. I need to connect a single Table View to two different tables (entities) in Core Data through two different NSFetchedResultsControllers. I have created two NSFetchedResultsControllers, and even fetched data from table but I faced problem how to tell Table View that first controller should response for section one and second controller should be responsible for section two.
I can show you the code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var tv: UITableView!
#IBAction func pressed(_ sender: UIButton) {
ModelA.read { table1 in
ModelB.read { table2 in
if table1.isEmpty {
ModelA.save(recordToSave: [(a: 1, b: "a"), (a: 2, b: "b"), (a: 3, b: "c")]) {
ModelB.save(recordToSave: [(a: 4, b: 5.0, c: true), (a: 6, b: 7.0, c: false)]) {
self.tvReload()
}
}
} else {
self.tvReload()
}
}
}
}
var fetchedResultsControllerForModelA = CoreDataFunctions.fetchedResultsController
var fetchedResultsControllerForModelB = CoreDataFunctions.fetchedResultsController
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tvReload()
}
func modelOfTableA(indexPath: IndexPath) -> (forLabel1: String, forLabel2: String, forLabel3: String)? {
if let fetchedResultsControllerForModelA = CoreDataFunctions.getNSManagedObjectForIndexPathOfTable(fetchedResultsController: fetchedResultsControllerForModelA, indexPath: indexPath) {
if let model = ModelA.read(nsmanagedobject: fetchedResultsControllerForModelA) {
return (forLabel1: "\(model.a)", forLabel2: model.b, forLabel3: "")
}
}
return nil
}
func modelOfTableB(indexPath: IndexPath) -> (forLabel1: String, forLabel2: String, forLabel3: String)? {
if let fetchedResultsControllerForModelB = CoreDataFunctions.getNSManagedObjectForIndexPathOfTable(fetchedResultsController: fetchedResultsControllerForModelB, indexPath: indexPath) {
if let model = ModelB.read(nsmanagedobject: fetchedResultsControllerForModelB) {
return (forLabel1: "\(model.a)", forLabel2: "\(model.b)", forLabel3: "\(model.c)")
}
}
return nil
}
func tvReload() {
fetchedResultsControllerForModelA = CoreDataFunctions(tableName: .a).fetchedResultsController(keyForSort: ModelA.a.rawValue, searchParameters: nil)
fetchedResultsControllerForModelB = CoreDataFunctions(tableName: .b).fetchedResultsController(keyForSort: ModelB.a.rawValue, searchParameters: nil)
do {
try fetchedResultsControllerForModelA?.performFetch()
try fetchedResultsControllerForModelB?.performFetch()
DispatchQueue.main.async {
self.tv.reloadData()
}
} catch {
print("Error")
}
}
func numberOfSectionsInTableView(_ tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections1 = fetchedResultsControllerForModelA?.sections {
if let sections2 = fetchedResultsControllerForModelB?.sections {
return sections1[section].numberOfObjects + sections2[section].numberOfObjects
}
return sections1[section].numberOfObjects
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
if indexPath.section == 0 {
if let modelOfTable = modelOfTableA(indexPath: indexPath) {
cell.l1.text = modelOfTable.forLabel1
cell.l2.text = modelOfTable.forLabel2
cell.l3.text = modelOfTable.forLabel3
}
} else {
if let modelOfTable = modelOfTableB(indexPath: indexPath) {
cell.l1.text = modelOfTable.forLabel1
cell.l2.text = modelOfTable.forLabel2
cell.l3.text = modelOfTable.forLabel3
}
}
return cell
}
}
I could not find any tutorial on this theme, so I am asking question there. I do not want to use inheritance from single entity in Core Data, because, in real life it would be impossible.
Thank you for any help or advice!
OK - I downloaded your code, and there are a couple issues...
1) If you want your data to fill two sections in the table, the table must have two sections - currently, you are just returning 1, so use this (although you may want/need different handling based on data retrieved):
func numberOfSectionsInTableView(_ tableView: UITableView) -> Int {
if fetchedResultsControllerForModelA == nil || fetchedResultsControllerForModelB == nil {
return 0
}
return 2
}
2) For number of rows in each section, your code was close but not quite right...
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
if let sections = fetchedResultsControllerForModelA?.sections {
return sections[0].numberOfObjects
}
} else {
if let sections = fetchedResultsControllerForModelB?.sections {
return sections[0].numberOfObjects
}
}
return 0
}
3) For the actual cellForRowAtIndexPath data, again your code was close but you need to keep track of which data to get for each section...
func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
if indexPath.section == 0 {
if let modelOfTable = modelOfTableA(indexPath: indexPath) {
cell.l1.text = modelOfTable.forLabel1
cell.l2.text = modelOfTable.forLabel2
cell.l3.text = modelOfTable.forLabel3
}
} else {
// Each "Table data model" has only one section... since this is the 2nd table section, we need
// to change the section of the index path for our call to the data model
let dataIndexPath = IndexPath(row: indexPath.row, section: 0)
if let modelOfTable = modelOfTableB(indexPath: dataIndexPath) {
cell.l1.text = modelOfTable.forLabel1
cell.l2.text = modelOfTable.forLabel2
cell.l3.text = modelOfTable.forLabel3
}
}
return cell
}
That should get you on your way.

Populating Cells in a tableView from API Call

I am forced to use an asynchronous call (I guess closure in swift) to get Data I need using an SDK (APIWrapper). I'm finding that the view is being initalized before I am able to get the data that I need.
So my 1st question to y'all is, how can I get my cells to bring in the data that I need to the table view before the view loads? Then, why would I want to use an asyncronous call at this point
import APIWrapper
import UIKit
class MyViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let provider = APIWrapper
var categories = [String]()
//define number of cells
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
categories = []
self.getCells()
print("count " , self.categories.count)
return(self.categories.count)
}
//get number of cells
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "categories")
cell.textLabel?.text = categories[indexPath.row]
return(cell)
}
private func getCells(){
provider?.getCategoriesWithCallback { (response, error) -> () in
if error == nil {
print("response ", response)
self.updateTableViewWithCategories(categories: response as! [APIWrapperCategory])
}
else {
print("FUCKK")
}
}
}
private func updateTableViewWithCategories(categories: [APIWrapperCategory]){
for category in categories{
print("category obj " , category)
print("category name " , category.name)
}
}
}
The output from my console looks like
count 0
count 0
count 0
count 0
response Optional([<APIWrapperCategory: 0x6000002a0300>])
category obj <ZDKHelpCenterCategory: 0x6000002a0300>
category name General
response Optional([<ZDKHelpCenterCategory: 0x6180002a30c0>])
category obj <ZDKHelpCenterCategory: 0x6180002a30c0>
category name General
response Optional([<ZDKHelpCenterCategory: 0x6180002a30c0>])
category obj <ZDKHelpCenterCategory: 0x6180002a30c0>
category name General
response Optional([<ZDKHelpCenterCategory: 0x6180002a3300>])
category obj <ZDKHelpCenterCategory: 0x6180002a3300>
category name General
You are getting data for your table view from the data source method of the tableview.
To get data from an API call, call self.getCells() method in viewDidLoad() method of your view controller like this:
override func viewDidLoad() {
//your code here
//get cells data
self.getCells()
}
And add your api response data to table view data source as:
private func updateTableViewWithCategories(categories: [APIWrapperCategory]){
self. categories = []
for category in categories{
print("category obj " , category)
print("category name " , category.name)
self. categories.append(category.name)
}
//reload table view here
DispatchQueue.main.async {
self.yourTableView.reloadData()
}
}
and change the delegate method as:
//define number of cells
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("count " , self.categories.count)
return(self.categories.count)
}
So I ended up using viewWillAppear and changed a few things on the way data is returned to make the cells populate so here's the code, I hope this can help someone else out
#IBOutlet weak var tableView: UITableView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.getCategoriesFromZendesk()
}
//define number of cells
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("count " , self.categories.count)
return self.categories.count
}
//get number of cells
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//Add Label to the Prototype Cell
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "categories")
cell.textLabel?.text = categories[indexPath.row]
return(cell)
}
private func getCategories(){
self.categories = []
provider?.getCategoriesWithCallback { (response, error) -> () in
if error == nil {
print("response ", response?.map{($0 as AnyObject as? APIWrapperCategory)?.name ?? ""} ?? "empty")
self.updateTableViewWithCategories(categories: response as? [APIWrapperCategory])
}
else {
print("FUCKK")
}
}
}
private func updateTableViewWithCategories(categories: [APIWrapperCategory]?){
self.categories = categories?.flatMap{$0.name} ?? []
tableView.reloadData()
}

Slow/Stuttered scroll in tableview

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.

search display controller update table view

I'm making an app, which destination iOS is 7.0. So I use search display controller. When i try to search, i make an api request, and it's results are coming later, than search display controller updates table view. So it's empty, though i have results of search. I've tried something like
self.searchDisplayController?.searchResultsTableView.reloadData()
straight after reaching data from request, but it's not working.
Here is my logic:
func filterContextForSearchText(searchText: String) {
BooksWorker.searchForBooks(searchText) { foundBooks in
self.foundBooks = foundBooks
if BooksWorker.books != nil {
self.filteredBooks = BooksWorker.books.filter { book in
return (book.name?.lowercaseString.containsString(searchText.lowercaseString))!
}
}
self.searchDisplayController?.searchResultsTableView.reloadData()
}
}
func searchDisplayController(controller: UISearchDisplayController, shouldReloadTableForSearchString searchString: String?) -> Bool {
isSearch = true
filterContextForSearchText(searchString!)
return true
}
I update my tableView in such a way:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isSearch {
tableView.separatorStyle = UITableViewCellSeparatorStyle.SingleLine
return filteredBooks.count
} else {
if BooksWorker.books != nil {
tableView.separatorStyle = UITableViewCellSeparatorStyle.SingleLine
return (BooksWorker.books?.count)!
} else {
showEmptyTableView()
tableView.separatorStyle = UITableViewCellSeparatorStyle.None
return 0
}
}
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 100
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if isSearch {
let cell = tableView.dequeueReusableCellWithIdentifier(AppData.CellIdentifiers.UndownloadedBookCell) as! UndownloadedBookCell
print("making cell")
cell.setBook(foundBooks[indexPath.row])
cell.delegate = self
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier(AppData.CellIdentifiers.BookCell) as! BookCell
cell.setBook(BooksWorker.books![indexPath.row])
return cell
}
}
Does anybody having an idea?
you should update tableView not searchDisplay self.tableView.reloadData()

Creating TableViews in Swift with an Array

I'm attempting to use the result of one Rest call as an input for my TableView.
I've got an array named GamesList[String] that is synthesized in the viewDidLoad() function. This is the viewDidLoad() fuction:
override func viewDidLoad() {
super.viewDidLoad()
getState() { (json, error) -> Void in
if let er = error {
println("\(er)")
} else {
var json = JSON(json!);
print(json);
let count: Int = json["games"].array!.count
println("found \(count) challenges")
for index in 0...count-1{
println(index);
self.GamesList.append(json["games"][index]["game_guid"].string!);
}
}
}
}
The problem is that the functions for filling the TableView get executed before my GamesList array is filled up. These are the functions that fill the TableView:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return GamesList.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Game", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel?.text = GamesList[indexPath.row]
cell.detailTextLabel?.text = GamesList[indexPath.row]
return cell
}
How do I force the tables to get filled up (refreshed) after my array has been filled?
use self.tableView.reloadData() after you append your values
getState() { (json, error) -> Void in
if let er = error {
println("\(er)")
} else {
var json = JSON(json!);
print(json);
let count: Int = json["games"].array!.count
println("found \(count) challenges")
for index in 0...count-1{
println(index);
self.GamesList.append(json["games"][index]["game_guid"].string!);
}
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
}

Resources