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!
Related
I'm a newbie in swift, I stumbuled upon these two functions that must be used when calling UiTableViewDataSource:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
I can see what each function do, but I can't understand their structures. Like what about the parameter that each one takes? Why don't we give any values of these parameters? How does it determine that what indexpath really is?
UITableView is the one responsible for calling them and picking the right index path according to what the table view needs or is visible at that time on the screen. Your only responsibility on the first one is to return the amount of elements that are in a given section and for the second one to dequeue the right cell and configure it with the data that corresponds to the index path that was passed in.
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.
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.
I have a table view controller that needs to be updated through a delegate call. I have set the datasource and delegate and on initial load of the tableview, all works as expected. I have a delegate method that gets called after a datasource update. The delegate calls a refresh method in the table view controller class which calls .reloadData()
When reloadData is called, numberOfRowsInSection is called and accurately returns the number of rows, however cellForRowAtIndexPath never gets called.
In this particular case, numberOfRowsInSection returns 2, therefore cellForRowAtIndexPath should be called twice but it's called zero times.
On initial load everything is fine. It's only when reloadData is called taht cellForRowAtIndexPath is ignored. I have done this same thing many times in Obj-C without any weirdness. Are there any known issues with this in Swift?
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(LayerMenuCell.reuseId) as! LayerMenuCell
// ....
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(layerEntries?.count)
return (layerEntries?.count)!
}
func refresh() {
self.layersTableView.reloadData()
}
Thanks!
Try setting the delegate and dataSource of your UITableView:
myTable.delegate = self
myTable.dataSource = self
As specified in the Documentation:
A functioning table view requires three table view data source
methods.
func numberOfSectionsInTableView(tableView: UITableView) -> Int
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
MAake sure you implement the above three delegate methods and they return some values other than nil or 0.
There is a chance that cell height could be 0/ table height is 0 in both the cases cell for row method will not get called.
Also make sure you set the delegate properly and call the reloadData method on main thread. More on here
Things you need to check when Tableview is not working as expected:
1. Setting the delegate and datasource through storyboard or by code.
self.tableView.delegate = self
self.tableView.dataSource = self
2.check if tableView in storyboard is connected to tableView outlet.
3.check numberOfRowsInSection and numberOfSectionsInT‌​ableView returning the correct values.
4.check if the methods is written properly.
5.add the delegate and datasource after UIViewController.
<UITableViewDelegate , UITableViewDataSource>
this will help you if you are missing any thing.
I think this is probably an X Y problem, so I'll give some background info first.
I am building an app that can show a "form". The user can fill in the form with some stuff and create some custom things.
I think the most suitable thing to do is to use a table view for the form. And I can display all the text boxes that need to be fill in, in each of the table cells.
Here's a screenshot:
The "Add New Input" button will insert a new cell on the bottom when it is tapped. And if you swipe one of the inputs to the left, you get a "Delete" button. You can use that to delete the input.
As you can see, this table view needs to add and delete rows.
Originally, I was using a cocoapod called "TableViewModel" which makes this super easy. But then I found a really severe bug in the library so I don't want to use it anymore.
I tried using the table view's deleteRowsAtIndexPaths and insertRowsAtIndexPaths methods. But if I do it like this:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = "Hello"
return cell
}
// I use motionEnded here because I don't want to add a button or anything just to test this
override func motionEnded(motion: UIEventSubtype, withEvent event: UIEvent?) {
tableView.deleteRowsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 0)], withRowAnimation: .Fade)
}
It will result in an exception saying that the table view is inconsistent with the data source after I deleted one row. This means I have to have code to handle this inconsistency. This will definitely make my code harder to read.
The same thing goes with the insert method.
Also, I need to keep track of how many rows there are in each section. And my code just becomes really messy and unmaintainable with all that.
I have also searched for other libraries but they are all really weird and not as straightforward as "tableViewModel". They all seem to require me to create a model for the table view. I don't understand how to do that in my case. I just want to display a bunch of text fields!
How can I insert or delete rows more elegantly? I think I either need to write an extension of the table view or learn to write a model for my table view. But I am able to do neither of these methods.
Because you don't have any data source. Also see logically: suppose you add a new row with insertRowsAtIndexPaths . So now you have 2 rows. However your numberOfRowsInSection always returning 1. so it will crash and vice versa for delete. Table view are supposed to work with a collection (NSArray, NSDictionary, NSSet etc.).
For your help:
Already they have nade a form as yours
obj-c easy to understand how table view with data source work
Adding, Updating, Deleting and Moving records using Swift.
You may use a TableView to create the form but you must design an appropriate dataSource to keep track of these data. Now the dataSource method must be modified to
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return yourDataSourceForSection.count
}
You may edit this dataSource when a delete operation is performed as follows
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
//Delete the row from the data source to ensure consistency
self.array.removeAtIndex(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
}
You may use a model class as the dataSource.
class NewCustomOperation: NSObject {
var name: String?
var rejectFloatingPoints: Bool?
var inputs: AvailableInputs?
var results: Results?
}
class AvailableInputs: NSObject {
var name: [String]?
var description: [String]?
}
class Results: NSObject {
var name: [String]?
var formula: [String]?
}
I found a solution!
I basically keep an array of an array of cells for the table view to display:
var cells: [[UITableViewCell]] = [[], [], []] // I have 3 sections, so 3 empty arrays
And then I added these data source methods:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return cells.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cells[section].count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return cells[indexPath.section][indexPath.row]
}
Now, I can add and remove cells super easily:
func addCellToSection(section: Int, index: Int, cell: UITableViewCell) {
cells[section].insert(cell, atIndex: index)
tableView.insertRowsAtIndexPaths([NSIndexPath(forRow: index, inSection: section)], withRowAnimation: .Left)
}
func removeCellFromSection(section: Int, index: Int) {
cells[section].removeAtIndex(index)
tableView.deleteRowsAtIndexPaths([NSIndexPath(forRow: index, inSection: section)], withRowAnimation: .Left)
}
With just two lines, I can add/remove cells with animation!