didSelectRowAt not called in custom UITableView - ios

After some refactoring I decided to create a custom tableview that looks a bit like:
class BaseTable: UITableView, UITableViewDelegate, UITableViewDataSource {
var rowsInSection: Int { return 0}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.delegate = self
self.dataSource = self
self.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return rowsInSection
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.deselectRow(at: indexPath, animated: true)
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "headerCell")!
return cell
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 40
}
And then I subclass it like this:
class CustomTable: BaseTable{
override var rowsInSection: Int {return payArray.count }
}
This works fine, however I've noticed none of the subclassed didSelectRowAt are being called??? Can anyone help?

Well, you should follow this things:
You should split up logic from initWithCoder: to external method such as and call it in initWithFrame:, because different approaches called different init methods. Such as
func setUpTableView() {
self.delegate = self
self.dataSource = self
self.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
For clarity in your code, you should explicitly call super method from child class methods
Otherwise, I strongly recommend you not to use this approach, better use standard UITableView in controller, and different classes, that implements protocols alongside with custom UITableViewCell's

This is bad design. The reason there are delegate and data source protocols for UITableViews is because view objects should be reusable. You're making your BaseTable class to function as a controller as well as a view.
To answer your question, are you sure it's not being called? You only have deselectRow(at:animated:) calling from tableView(didSelectRowAt:)

Related

Code Optimisation in terms of (UI) Delegate methods in Swift

I am asked to do optimise my swift code especially when it comes to UI Delegate methods.
Scenario: I have UITableView in five UIViewControllers, in each view controller I am using UITableView delegate methods due to which my code is repeating. So how can I optimise this thing?
Below is the structure of code: You can see delegate method code is repeating
class FirstViewController: UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var userArray = [UserModel]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
// register cell according to model (Dynamic Cells)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return userArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell01", for: indexPath)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
}
class SecondViewController: UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var productArray = [ProductModel]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
// register cell according to model (Dynamic Cells)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return productArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell01", for: indexPath)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
}

Dynamic Table View within Static Table View Cell

I'm fairly new into Swift programming and right now I'm implementing a dynamic table view within a static table view's cell. I know there are plenty of solutions on stackoverflow already but I realised that most of them are in Obj-C which I'm not very familiar with it yet.
Basically, I have a TableView that is set as dynamic in one of the cell of a static table view which is part of the main table view controller. The problem I am having now is there doesn't seem to be a way to implement the data source functions without declaring them for the static table view. I have declared an #IBOutlet for the dynamic table (let's call it dynamicTableView in this scenario).
I have managed to get the override func numberOfSections(in tableView: UITableView) working by returning 1 if the tableView is not dynamicTableView as in the following code:
override func numberOfSections(in tableView: UITableView) -> Int {
if tableView == dynamicTableView {
return data.count
}
else {
return 1
}
}
However, the problem I am having now is implementing the override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath). I have no idea what to be returned if the tableView parameter is not dynamicTableView, but for the static table view.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == dynamicTableView {
let cell = tableView.dequeueReusableCell(withIdentifier: "dynamic", for: indexPath) as! dynamicTableViewCell
cell.update(data[indexPath.row]) // A helper function declared in the dynamicTableViewCell.swift
return cell
}
else {
// What to return here?
}
}
Thanks!
Edit: What I meant was I can't seem to have a cellForRowAt data source function that does not affect my static table view.
If there is a value in numberForRows then you have to retutn a cell like this
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView == bloggerReviewTableView {
let cell = tableView.dequeueReusableCell(withIdentifier: "dynamic", for: indexPath) as! dynamicTableViewCell
cell.update(data[indexPath.row]) // A helper function declared in the dynamicTableViewCell.swift
return cell
}
else {
// What to return here?
let cell = tableView.dequeueReusableCell(withIdentifier: "other", for: indexPath) as! OtherTableCell
return cell
}
}
//
but if the return is zero then there is no need for the if statement inside cellForRowAt as it won't be called for the other table
If the static tableview cells are fairly distinct, they can be individually subclassed.
The dynamic tableview/collectionview can be added in required subclass of static tableview cell.
//class for static tableview
let reviewCellId = "reviewCell"
class StaticTableClass: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
//register static cell classes
tableView.register(ReviewCell.self, forCellReuseIdentifier: reviewCellId)
//..
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reviewCellId, for: indexPath)
return cell
}
}
Create a separate ReviewCell class which will contain the dynamic UITableView like so.
This way one class will handle methods of only one tableview.
class ReviewCell: UITableViewCell, UITableViewDelegate, UITableViewDataSource {
lazy var dynamicTableView: UITableView = {
let tv = UITableView()
tv.delegate = self
tv.dataSource = self
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
addSubview(dynamicTableView)
dynamicTableView.register(UITableViewCell.self, forCellReuseIdentifier: "dynamicCellId")
}
// add further tableview methods in here
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
}
}

How to use UITableViewCell's accessoryButton as anchor for a Popover

I tried to use the accessoryButton of the UITableViewCell as the anchor of a Popover, but couldn't connect it using the storyboard. Programmatically I tried using the accessoryButtonTappedForRowWith function, this didn't work either. How can I connect it?
If your class is subclass of UITableViewController then use:
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
print(indexPath.row)
}
Check example code:
class ViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "\(indexPath.row)"
cell.accessoryType = UITableViewCellAccessoryType.detailButton
return cell
}
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
print(indexPath.row)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
}
And if it's a subclass of UIViewController then you don't need override before that method so it will be:
func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
print(indexPath.row)
}
And don't forget to connect UITableViewDataSource and UITableViewDelegate of your table view with your ViewController in this case.

How to put a tableView inside a section Header

I’m trying to put a tableView inside a collectionView sectionHeader. I tried adding a tableView to the header in storyboard and then setting its class to tableViewController.swift but that didn’t work.
(I’m using swift)
After trying a bit i did manage to get it working properly.
First i created a header as normal, then created an IBOutlet of the tableView.
Then I simply setup the header file like a tableViewController is setup, connected to the dataSource and delegate in awakeFromNib and done.
class HomeHeader: UICollectionReusableView, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
override func awakeFromNib() {
tableView.delegate = self
tableView.dataSource = self
//tableView.backgroundColor = UIColor.clear
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1 //number of sections
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5 //number of cells
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as UITableViewCell
cell.textLabel?.text = "test"
// cell.backgroundColor = UIColor.clear
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
}

UITableView does not conform to protocol UITableViewDataSource error

Question 1)
I've spent a few hours trying to figure out this problem, from what I've read those are the two functions needed to implement the UITableView but it still gives me the error in the title:
import UIKit
class UITableView: UIViewController, UITableViewDataSource,UITableViewDelegate
{
override func viewDidLoad()
{
super.viewDidLoad()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
}
}
Question 2)
The TableView is a part of a tabbed view controller that I am implementing for my app. I want my TableView controller to have entries that once pressed will open another ViewController. How can I implement something like that?
Thank you in advance
You have incorrectly set up your ViewController. Either create a UITableViewController which is just a UITableView, or add a UITableView to a UIViewController (Like you have almost done).The didSelectRowAt method will allow you to segue to a new viewController, but in my examples only if it is set up correctly in a storyboard.
UITableViewController option
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
// Configure the cell...
return cell
}
// MARK: - Table view delegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
performSegue(withIdentifier: "SegueIdentifier", sender: self)
}
UIViewController option (The UITableView will need to be added through a Storyboard in this example)
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
// MARK: - Table view data source
func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
// Configure the cell...
return cell
}
// MARK: - Table view delegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
performSegue(withIdentifier: "SegueIdentifier", sender: self)
}
Here is an example project to work from
You shouldn't be using UITableView to manage the data source. That should be the job of UITableViewController which manages data to be displayed on UITableView. The View in general should only worry about how to display the data, not manage it. See MVC design pattern by Apple.
Apple has a really nice, and complete Swift App tutorial (See Display the Data) that goes through how to set up UITableViewController with a table view you built with storyboard. You can also download and run the source project at the end of the page. I highly recommend it.
You have to to Implement This Mendatory DataSource Method
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "", for: indexPath)
return cell
}
Hope it Helps.

Resources