Programmatically insert rows to UITableViewController - ios

I have a CoreData entity which I need to populate the value of each objects into a table view cell. In the storyboard, I can add rows and change the style of my cells individually, but in this case I'm dealing with cells based on my number of objects and I have to programmatically insert rows and change the cell style.
I'm guessing that I need a foreach loop with the code to insert rows programmatically. Does anyone know how to achieve this?

You have to implement UITableViewDataSource. See the API Reference for more information.
When using Core Data with UITableViews you might also want to take a look at NSFetchedResultsController.

You need to use UITableViewDataSource methods. Add the dataSource protocol to your class and conform to it. The method you are looking for is:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
You probably will also want to conform to UITableViewDelegate protocol.
Do something like this:
class MyClass: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//Do your thing
}
//Conform to rest of the delegate and datasource methods too. Click on UITableViewDataSource to see the documentation

You need to implement override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int and return your objects array's count, then set a reuse identifier for the cell and add the code:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") //replace "Cell" with your identifier
cell.textLabel = yourTitleArray[indexPath.row]
cell.detailTextLabel = yourSubtitleArray[indexPath.row]
return cell!
}
Don't forget to replace yourTitleArray and yourSubtitleArray with your arrays.

Related

Why does Xcode auto-fix create two methods with the same name `func tableView`?

I am new to swift programming language. I've seen that in creating table in Swift, you have to implement two methods in ViewController class that extends UITableViewDelegate, UITableViewDataSource. What I don't understand is, why does Xcode's auto-fix create two methods with the same name func tableView in this class?
Is this not going to create method overloading or cause bug down the road?
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
let dataArray = ["firt", "second", "third", "four", "five", "six"]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let videoCell = tableView.dequeueReusableCell(withIdentifier: "video title", for: indexPath)
return videoCell
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
tableView.dataSource = self
tableView.dataSource = self
}
}
Even though they have the same function name tableView
They are very different functions.
They both conform to the UITableView delegate and based on its protocol method will affect different functionalities of the tableView.
didSelectRowAt
is not the same as
cellForRowAt
Did Select row at is only triggered when you obviously select a cell
Cell for row at is considered the 'main' tableView function, as this function populates your tableView data cells.
--EDIT
Based on Duncan C comment below.
" the name of your example function is not tableView, The name of of the function is tableView(_:cellForRowAt:) (The parameters are actually part of the function's name, or rather it's function "signature.") "
This is an excellent way to describe the answer.
Edit 2----
Furthermore,
This is very common among programming in swift. The most direct example would be collectionView. It uses almost the identical naming convention.
cellForRowAt
and
didSelectRowAt
There are many other delegate methods that you will encounter with the same situation as you describe in your question.
They are actually different methods. Each one overrides some properties of your TableView.
Just pay attention to the parameters of each function.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
This method creates and configures an appropriate cell for the given index path.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
Here you decide what happens when cell rows are clicked (go to another view, display some content, etc)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
Returns the number of rows each section of your TableView should have.
And so on. There are many others methods that you can use for different reasons. To see which methods you can override, type tableView on your Xcode and see the autocomplete options.
That is called method overloading. Swift designers chose to follow this way of writing functions to make it easier for the programmers to find all related tableView functions. That tableView has many more functions like:
a one that has heightForRowAt and willDisplayCell in its parameters. Swift utilizes heavily a pattern called Delegate Pattern, the framework calls these tableView functions when you assign your ViewController as a delegate.

Ignore first item in array in UITableView - Swift

How can I ignore the first item in an array when display in a TableView?
What I want is to ignore the first item when presented in a UITableView, I DON'T want remove it just not show it in the TableView.
The following code show all items from the array in the TableView.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return lists.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "listCell", for: indexPath)
let data = lists[indexPath.row]
cell.textLabel!.text = data.listName
return cell
}
Your data source methods are using lists as their basis, and you don't want to do anything that mess that up. The numberOfRowsInSection and the cellForRowAt need to stay in sync.
I can think of two possibilities:
Keep the real model elsewhere, and keep in lists only the part of the model that you want to include in the table.
Or (a whole different approach) implement heightForRowAt to give the undesired row a zero height.

UITableview: failed to obtain a cell from its dataSource

I get this error message: failed to obtain a cell from its dataSource
The strange that
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
not even get called, problem is not that returned cell is nil. I put in breakpoints. cellForRowAtnot get called.
numberOfRowsInSection returns value.
I do not know it is relating or not, but now I do not use UITableViewController, I just have a UIViewController, and it is the datasource and delegate for the UITableView. I hope it can not cause interfere. Any idea?
Cross check below check list:-
it's because you are failing to dequeue a reusable cell.
The problem is that your cellForRowAtIndexPath function is embedded in another function
when you forgot to add the UITableViewDataSource and UITableViewDelegate protocols to the ViewController declaration.
class DataViewController: UIViewController, UITableViewDataSource, UITableViewDelegate
I guess this is your problem - Failed to obtain a cell from its DataSource
you can take a look at: https://www.hackingwithswift.com/example-code/uikit/fixing-failed-to-obtain-a-cell-from-its-datasource
try something like this:
cell = tableView.dequeueReusableCellWithIdentifier(getCellIdentifierForType(cellType))
setDataToCell(cell, item: item)
good luck
Please make sure when you are migrating your code from swift older to new versions then your tableview data source and delegates have updated syntax.
For example
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
and
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
Can not auto updated to below:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {

Swift 3: Understand syntax change for UITableViewDataSource method

I have some questions with Swift 3 function calling. Below is an example.
Old Swift:
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell
Swift 3:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
That's fine with the above syntax. But now Xcode shows me an error and asks me to do like below:
#objc(tableView:cellForRowAtIndexPath:) func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
I do not understand why I have to declare #objc(tableView:cellForRowAtIndexPath:).
This is happening only when I am trying to implement table view datasource methods under an extension. Also this is not happening for numberOfRowsInSection or viewForHeaderInSection.
Can anyone help me to understand why this is happening?
While I am not sure what triggers the #objc, I can suggest the following approach:
Store the tableView variable somewhere in the viewDidLoad:
let tv = tableView!
Then hover over the tableView variable and press the command button in conjunction with a click.
This should take you to the interface of a UITableView.
Then, hover over either UITableViewDelegate or UITableViewDataSource and press the command button in conjunction with a click.
Now you can see the new signatures.
A lot has changed...Happy upgrade!
Swift compiler forced to write Objc(funcName) before function if you are using this function from Objective c. According to app doc
Use the #objc(name) attribute to provide Objective-C names for
properties and methods when necessary. For example, you can mark a
property called enabled to have a getter named isEnabled in
Objective-C like this:
var enabled: Bool {
#objc(isEnabled) get {
// ...
}
}
To void this, use extension to write TableView Datasource and delegate
extension YourViewControllerName:UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell() as SplitAddContactCell
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80.0
}
}

Changing index of a cell

I'm starting to work with UITableViews and can't seem to find out how to change the position of a cell with code. Changing the position in the storyboard is straightforward enough but I need to be able to do it in swift.
TLDR;
Update your data. i.e. swap(&arr[2], &arr[3]).
Call the tableView's reloadData() method to reflect the changes to your data.
Long answer
An instance of UITableView works by checking its data source (UITableViewDataSource) for the information it needs. This includes the number of sections and rows, as well as the instance of UITableViewCell that the table view is to use. These are defined by the following UITableViewDataSource delegate methods:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int;
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int;
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell;
Usually, you would base the former two on some data you have, likely an Array or similar container. For example, if your tableView displayed data from an Array named fruitArray (which contained names of different fruit - a list of strings), then you might have something like the following:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// Our array is one dimensional, so only need one section.
// If you have an array of arrays for example, you could set this using the number of elements of your child arrays
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Number of fruits in our array
return fruitArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("yourCellId") // Set this in Interface Builder
cell.textLabel?.text = fruitArray[indexPath.row]
return cell
}
Then, you can see that the answer to your question becomes simple! Since the contents of a given cell are based upon fruitArray, all you need to do is update your array. But how do you get the tableView to "recheck" its dataSource? Well, you use the reloadData method, like so:
swap(&fruitArray[2], &fruitArray[3])
tableView.reloadData()
This then triggers the tableView to "recheck" its dataSource, hence causing your data swap to appear on the screen!
If you'd like the user to be able to swap the positions of the cells, you can use the following UITableViewDelegate (not UITableViewDataSource) delegate method:
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool
Have a look at this article for more info. You can also view Apple's documentation on UITableView, UITableViewDataSource, and UITableViewDelegate for further detail.
Hope this helps!

Resources