Table View In Swift - ios

I am trying to configure a page to look something as follows - where I have a standard view controller with a table view inside of it.
I have my prototype cell denied, so I can configure these in code and add the relevant images.
However, I am trying to set the code base up to do this (i.e.) implement the required table view methods but am getting a number of errors;
Is this occurring because this only apples when you have a UITableView and not a Table in a normal view controller? If so, how do I manage my dynamic table in the code?
Thanks for your time

When using a UITableView in a viewcontroller (not in an UITableViewController) as you are doing it, your custom viewcontroller has to implement the UITableViewDataSource and the UITableViewDelegate protocols.
At least the following required functions:
class MyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
...
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
}
}
Since your class is not derived from UITableViewController it does not override these functions, so remove the override directive.

Related

How to update informations of UITableViewController when IBAction of a cell is triggered?

I have a UITableViewController with custom UITableViewCell, and every cell has an UISwitch inside.
Need to update an information on my table (a string in the header), when any of these switches turn on/off (need to display the number of switches on in the section header).
I'm not confident with iOS and UIKit, but I've already found 2 possibile solutions:
implement a delegate pattern between cell and table controller
write update logic inside the function tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
In the first solution my TableViewController conforms to
protocol TableViewDelegate {
func reloadTable()
}
that update its inner counter of switches on - and update the header section in function tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? - calling
DispatchQueue.main.async{
self.tableView.reloadData()
}
Obviously my custom UITableViewCell has the reference to the delegate TableViewController and call its reloadTable().
The second solution instead is about the possibility to get the information of every cell in the method cellForRowAt indexPath. I've found that this function in not only called when the table has to be drawn, but also when I interact with a component into a cell.
So I need to implement a counting in the function cellForRowAt indexPath? Or the first solution with delegate pattern is a good one?
you should use a delegate in your cell.. can go like this
protocol CellDelegate: class {
func actionDidPressed()
}
then in your cell should use it
class cell: UITableViewCell {
weak var delegate: CellDelegate?
#IBAction func buttonPressed() {
delegate?.actionDidPressed()
}
}
then lastly in your controller you can conform to it
class ViewController: UIViewController {
// in your cellAtIndexPath method after creating cell you can
cell.delegate = self
}
I prefer to make an extension to the View Controller to conform to delegates
extension ViewController: CellDelegate {
func actionDidPressed() {
// add the action you need here
}
}
You can use NSNotification to pass data between different views. You'll need to register the recipient controller by using the addObserver method and use post to send a message. You can refer to this post

Programmatically switch UITableView in UITableViewController

NOTE: I know this is a re-post; I posted the same question a few days ago sans-code and, understandably, it was closed. I edited the question to add some relevant snippets but the question was not reopened, so I am reposting here. If this isn't the proper way to do this, let me know!
I have an application with two modes, and the settings screen is slightly different for each mode (one additional section in one of them, a few differences in number of rows, different UserDefaults keys, etc.). In the past I implemented this with a crapton of switch and if statements, but in an effort to make things more maintainable I'm investigating ways on splitting the two modes into their own separate classes of something. Initially I considered making two separate UITableViewController subclasses, however I was having trouble thinking of how that would work with storyboards and such. I then thought to use two separate UITableView subclasses, and select which one to show based on the mode in viewDidLoad.
However, I'm having issues with this approach. I have it set up so that the controller's cellForRow method calls the TableView's cellForRow method, but that's where things break. When trying to do a dequeueReusableCell, the app crashes with the ever vague "EXC_BAD_INSTRUCTION" error on that line.
Here's some relevant code:
ViewController.swift
...
override func viewDidLoad()
{
super.viewDidLoad()
...
tableView = SRScaleSettingsTableView()
}
...
override func tableView(_ tableView: UITableView?, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
return (tableView?.cellForRow(at: indexPath))!
}
SRScaleSettingsTableView.swift
override func cellForRow(at indexPath: IndexPath) -> UITableViewCell?
{
...
switch indexPath.section
{
case 0:
...
let switchCell = dequeueReusableCell(withIdentifier: "SwitchCell") as! SRSwitchCell
^ Debugger breaks on that line with EXC_BAD_INSTRUCTION
...
return switchCell
...
}
}
Any ideas on what would cause this? Is my approach even correct; is there a better way to do this?
You can keep a single UITableView class (you likely don't need to subclass UITableView at all) and a single UIViewController subclass. Create two different classes that implement the UITableViewDataSource protocol (and possibly also UITableViewDelegate). These two classes can implement the various delegate/datasource methods (e.g. cellForRowAtIndexPath, numberOfRowsInSection, didSelectRow) in completely different ways for the different modes your app needs to run in.
protocol SettingsSource: UITableViewDelegate, UITableViewDataSource {
}
class SettingsSourceForModeA: NSObject, SettingsSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int)...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)...
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)...
}
class SettingsSourceForModeB: NSObject, SettingsSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int)...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)...
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)...
}
class SettingsViewController: UIViewController {
#IBOutlet tableView: UITableView!
var source: SettingsSource! {
didSet {
tableView.dataSource = source
tableView.delegate = source
}
}
override func viewDidLoad() {
super.viewDidLoad()
// whatever logic is appropriate to determine the mode
if appIsRunningInModeA() {
source = SettingsSourceForModeA()
} else {
source = SettingsSourceForModeB()
}
}
}
The key detail in the above code is the source variable in SettingsViewController - the value of source is based on which mode the app is running in, and it determines what class will be used as the data source for the table view.
The storyboard setup is simple: one scene SettingsViewController, and a single stock UITableView in that scene.
Note that the SettingsViewController above is a UIViewController subclass, not UITableViewController, since the data source and delegate protocols are being implemented in separate classes and determined at runtime. This will require you to manually wire up the tableView outlet in your storyboard. However, you do not wire up the UITableView's dataSource and delegate outlets in the storyboard. Instead, it's done at runtime as shown in the sample code above.
Note that you may not have any need to implement UITableViewDelegate, in which case you can just ignore the references to UITableViewDelegate and its methods in the sample code above. Or, if you the UITableViewDelegate implementation (such as the didSelectRow method) is identical for the two modes your app can run in, you may be able to implement that in your view controller class, in which case you can wire up the delegate outlet of your table view directly to your view controller, in the storyboard.
You have a misunderstanding of how the UITableView and UITableViewController work together. A UITableView needs a UITableViewDataSource to provide it the details of the underlying data (number of sections, number of rows and actual cells, etc). This is what the UITableViewController does (it conforms to the UITableViewDataSource). So if you call the cellForRow for the tableView then it will call it's data sources cellForRow method to obtain that.
So in your code when you do this:
return (tableView?.cellForRow(at: indexPath))!
Your table view calls its data source which is your UITableViewController and that calls the table view cellForRow and so on. You have just entered a recursive loop which is eventually killed off with the error you see.
As for your overall approach I would go down the two UITableViewControllers route as that separates out the different logics between the two making it easier to both understand and maintain and also allowing for more reuse as well.
As for how that works with storyboards it depends greatly on how you switch between the two modes but in essence you can setup segues to switch between the two controllers.

UITableViewController shows nothing but horizontal lines

I'm just starting with Swift and xCode, and currently messing around with UITableView, I can't manage to just write 'test' into a table.
I created a UITableViewController in the Storyboard, specified a custom class for it (my swift file below), filled in 'ClientCell' as a reuse identifier of the cell and the code is as follows:
class TableViewController: UITableViewController {
#IBOutlet var clientTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
clientTable.dataSource = self.dataSource;
clientTable.delegate = self;
}
func numberOfRowsInSection(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ClientCell", forIndexPath: indexPath) as! CustomTableViewCell
cell.tableLabel.text = "test"
return cell
}
}
In the storyboard, I added a 'UILabel' into the prototype cell, and created an outlet for it named 'tableLabel' in the CustomTableViewCell.swift.
I confused by all the side-things I've to consider when doing something as simple as this.
When I run it, the simulator just shows a table with a lot of horizontal lines, but nowhere it says 'test'.
You haven't used the numberOfRowsInSection method.....
Additionally you also need to set the table view's data source and delegate to be the class you are writing these methods in (in case you haven't).
At a bare minimum you need this:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1 // this should really be from your data source
}
If you create a UITableViewController subclass in Xcode the template will have commented-out versions of all the methods you likely will want to fill out.

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!

SWIFT: Prototype Cells not being loaded after performSegueWithIdentifier

I have a ViewController that calls (clicking on a button) another View using this function
#IBAction func btnSeeContact(sender: AnyObject) {
self.performSegueWithIdentifier("segueSeeContact", sender: self)
}
and my prototype cell is "linked" to a custom View Controller named ContactsTableViewCell that I have created and it implements:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! ContactsTableViewCell
cell.txtName.text = "test"
cell.txtPhone.text = "1234567890"
return cell
}
When I run the project, the button calls the table, but there is no Cell on it, and I put a breakpoint on those tableView functions and they are not being reached.
What am I missing here that those functions are never being called?
I am adding a new answer since my previous answer was up voted, so I don't want to make massive edits that one, and is still a valid way to fix your issue.
The issue is you have your custom classes confused. In your screen shot you can see that the the Table View Controller is not set to a custom class, it just says Table View Controller. That is the object that needs to get a custom implementation of the UITableViewController class.
Instead you seem to be setting the cell's class to a custom class, and implementing the delegate methods there. You still need a custom class for the table view cell, but it should be a custom class of UITableViewCell.
So your cell class should look something like this:
import UIKit
class YourCustomTableViewCell: UITableViewCell {
#IBOutlet weak var yourLabel1: UILabel!
#IBOutlet weak var yourLabel2: UILabel!
}
You will be given an instance of this cell to configure in cellForIndexPath.
So your Table view controller class should be set to a class that looks like below. The YourTableViewController is were you want to implement all the delegate methods.
Note: if you are using a UITableViewController dragged out from the storyboard, it will already have the tableView, and delegate / data source stuff already wired up for you. You will also notice that you are overriding the delegate methods as the UITableViewController class has default implementations of these. If you are just using a normal view controller, then see my previous answer for more details on how to set that up.
import UIKit
class YourTableViewController: UITableViewController {
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath)
if let cell = cell as? YourCustomTableViewCell {
cell.yourLabel1.text = "some text"
cell.yourLabel2.text = "some other text"
}
return cell
}
}
As others have commented, you really need to provide a little more context.
Here are a few things that might be going wrong, providing more context would confirm or deny this guesses.
First you don't show the numberOfSectionsInTableView method.
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 0
}
I think you would need to provide a value other than 0
Secondly, since I don't see override in front of what I am sure you are intending to be UITableViewDelegate methods function calls, that means your view controller is not a UITableViewController. This makes me wonder if you defined this view controller as conforming to the UITableViewDelegate protocol and if you set the table view outlet delegate to self. (or even wired up the UITableView to an outlet)
If you use a plain UIViewController to host a table view you need to do the following:
Wire up your UITableView to an outlet in your view controller
Declare the view controller as conforming to the UITableViewDeleagate (and maybe UITableViewDataSource) protocol
set the table view's outlet delegate (and maybe dataSource) properties to self (the view controller implementing the protocols)
Implement the required methods
So something like this:
class MyTableViewController: UIViewController, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("your PrototypeCell", forIndexPath: indexPath)
// Configure the cell...
return cell
}
}

Resources