Since I've upgraded to Xcode 9 / Swift 4 some of my UITableViewDelegate methods are not called anymore
As of Xcode 9 / Swift 4 all Objective-C methods should be marked #objc. The compiler does a reasonable job of recognizing where it should be applied however it doesn't figure out inheritance. For example:
class Delegate: NSObject, UITableViewDelegate {
}
class SuperDelegate: Delegate {
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { return indexPath }
}
This will not generate any warning, crashing or build error. However your line will not be called until you add #objc:
#objc class SuperDelegate: Delegate {
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { return indexPath }
}
Related
I am using the following method to move between viewControllers:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "segueJokeDetail", sender: self)
}
That's the capture from the storyboard showing segueId
If you do a segue from a UITableViewCell in interface builder, you don’t need to do it in the UITableView delegate as well.
I pushed my version 7.6 of my iOS app to the App Store and noticed that the app suddenly contained many bugs (strange behaviours) that did not show up during debugging.
Since the app worked perfectly in xCode 9.x I suspect strongly that the issues started happening with Xcode 10.3.
I am using Swift 4.2 (conversion of Swift 5 is for next update)
After investigating for many hours I located the issue: when Compilation mode = "whole module" the bugs appear and when set to "incremental" the disappear. In debug mode (when app is run out of Xcode) the Compilation mode is set to "incremental" for release its "whole module" (this is the standard configuration when you create a new project in Xcode 10.x I suspect) this explains why we did not see the issues during debug testing.
Also note that changing to legacy build system did not solve the issues. Only setting Compilation mode = "incremental" solved the issues.
Analysis:
I tracked the issue to the fact that for all my TableViews the delegate was not being called.
I have the following simple hierarchy :
Code of the ViewTableRoot:
class ViewTableRoot : UITableView, UITableViewDelegate, UITableViewDataSource {
var didScrollToOffset : ( (CGFloat) -> Void )?
var didEndScrolling : ( (CGFloat) -> Void )?
var didChangeEditing : ( ( ) -> Void )?
//MARK: lifecycle
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override init(frame: CGRect, style: UITableView.Style) {
super.init(frame: frame, style: style)
setup();
}
func setup() {
//set the corner radius of the layer so that the sliding of the cells underneath the rounded headers does not show up
layer.cornerRadius = 5
//setup myself as delegate and data source
delegate = self
dataSource = self
}
deinit {
let className = String(describing: self)
log.debug("**********\(className)")
}
//MARK: - public API
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
didChangeEditing?()
}
//MARK: - scrollview delegate
func scrollViewDidScroll(_ scrollView: UIScrollView) {
//because we are also getting events when swiping on the cells, we need to see the difference between
//swipig on the cell and swiping in the "actual" table => we do this by checking the frame size
guard scrollView.frame == frame else { return }
didScrollToOffset?(scrollView.contentOffset.y)
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
//because we are also getting events when swiping on the cells, we need to see the difference between
//swipig on the cell and swiping in the "actual" table => we do this by checking the frame size
guard scrollView.frame == frame else { return }
if !decelerate {
didEndScrolling?(scrollView.contentOffset.y)
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
//because we are also getting events when swiping on the cells, we need to see the difference between
//swipig on the cell and swiping in the "actual" table => we do this by checking the frame size
guard scrollView.frame == frame else { return }
didEndScrolling?(contentOffset.y)
}
//MARK: - UITableViewDataSource
func numberOfSections(in tableView: UITableView) -> Int {
preconditionFailure("Must be implemented by derrived class")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
preconditionFailure("Must be implemented by derrived class")
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
preconditionFailure("Must be implemented by derrived class")
}
//MARK: - UITableViewDelegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
}
Code of the ViewTableSettings:
class ViewTableSettings : ViewTableRoot {
var settings : [[Setting]]? {
didSet {
reloadData()
}
}
var didPressSetting : ((Setting, CGRect) -> (Void))?
//MARK: lifecycle
override func setup() {
super.setup()
log.debug("delegate : \(delegate)")
//register xibs
register(CellTableSetting.nib, forCellReuseIdentifier: CellTableSetting.reuseIdentifier)
}
//MARK: - UITableView
override func numberOfSections(in tableView: UITableView) -> Int {
let count = settings?.count ?? 0
log.debug("count: \(count)")
log.debug("delegate : \(delegate)")
return count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let count = settings?[section].count ?? 0
log.debug("count: \(count)")
log.debug("delegate : \(delegate)")
return count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
log.debug("delegate : \(delegate)")
//ask for a new cell
let cell = tableView.dequeueReusableCell(withIdentifier: CellTableSetting.reuseIdentifier, for: indexPath) as! CellTableSetting
guard let setting = settings?[indexPath.section][indexPath.row] else {
preconditionFailure("Asking CellTableSetting but no Setting model defined")
}
//load up!
cell.setting = setting
cell.lastCell = indexPath.section != numberOfSections - 1 ? false : indexPath.row == (numberOfRows(inSection:indexPath.section) - 1)
//return cell to use
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
log.debug("-")
return CellTableSetting.height
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
log.debug("-")
tableView.deselectRow(at:indexPath, animated: true)
guard let setting = settings?[indexPath.section][indexPath.row] else {
return
}
didPressSetting?(setting,rectForRow(at: indexPath))
}
func tableView(_: UITableView, viewForFooterInSection: Int) -> UIView? {
log.debug("-")
guard viewForFooterInSection < (numberOfSections-1) else {
let version = UILabel()
version.text = UIApplication.AppVersionAndBuildPrettyPrint
version.font = UIFont.defaultBoldFont(size: 12)
version.textColor = PaintCode.mainLightGray_a50
version.textAlignment = .center
return version
}
let v = UIView()
v.backgroundColor = PaintCode.mainLightGray_a50
return v
}
func tableView(_: UITableView, heightForFooterInSection: Int) -> CGFloat {
log.debug("-")
return heightForFooterInSection < (numberOfSections-1) ? 5 : 40
}
}
as you can see the ViewTableRoot declares compliance to the UITableViewDelegate (also to UITableViewDataSource but that is besides the issue for now)
the delegate is actually assigned to self in the ViewTableRoot but the actual delegate functions are implemented in the derived ViewTableSettings(again this worked perfectly in Xcode 9.x)
when compilation mode = "Whole Module" these delegate functions are not being call => this is the bug
when set to "incremental" these delegate functions are called just fine!
Additional tests I have done to get more insight in the issue:
switching to the "legacy build system" (via Xcode/file/project settings) does not solve the issue; as long as the Whole Module is enabled the issue remains
when I create empty delegate functions in the ViewTableRoot and override them in the ViewTableSettings it does work :-o
I did verify in ViewTableSettings that the delegate was indeed set to an instance of ViewTableSettings and not ViewTableRoot (in which case there would not be any delegate functions implemented)
My thoughts
I get the feeling that I stumbled upon a bug in the (new?) build system?
Anybody else run into similar issues?
Yes, I have the same issue as you. When the Compilation Mode is "Whole Module", the Collectionview controllers are messed up but not the Tableview controllers. result on simulator device. Maybe you can try to use UITableViewController directly rather than conform TableView protocol.
Installed the latest Xcode 11.4 (11E146) and the problem seams to be resolved in this version. I have re-enabled the whole module optimization and everything seams to be working as expected. So... turns out that it was a bug XCdoe!
I would like to update the content of my custom cell with his height so I wrote this code :
extension MyViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
}
in my customCellTableView xib i added the top,right,left,bottom constraint to all the labels and also the numberOflines properties to 0
but when t run the app i got a cell of 250 height to all the cells which is very abnormal. I work on Xcode 9 and swift 4.
please if someone can help me,
I have re-typed the function, played around with the deletion of the override portion, yet still can't get the superclass error to dissipate. Before Xcode crashed on me last night and converted to the latest swift 3 syntax, this code (albeit in prior 2.3 syntax) was fully functional. Can't seem to catch the bug here.
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return BPC.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "BPCcell") as! BPC_TableCell
cell.namelabel.text = BPC[indexPath.row].name
cell.addresslabel.text = BPC[indexPath.row].address
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "BPCsegue"
{
if let destViewControler = segue.destinationViewController as? BPCDetails
{
let indexPath = self.tableView.indexPathForSelectedRow!
destViewControler.details = BPC[indexPath.row]
}
}
}
}
In swift, if I create a new cocoa touch class of type UITableViewController, I am given this file called TableViewController.swift:
import UIKit
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 0
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return 0
}
/*override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath) as UITableViewCell
// Configure the cell...
return cell
}*/
/*
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView!, canEditRowAtIndexPath indexPath: NSIndexPath!) -> Bool {
// Return NO if you do not want the specified item to be editable.
return true
}
*/
/*
// Override to support editing the table view.
override func tableView(tableView: UITableView!, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath!) {
if editingStyle == .Delete {
// Delete the row from the data source
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
*/
/*
// Override to support rearranging the table view.
override func tableView(tableView: UITableView!, moveRowAtIndexPath fromIndexPath: NSIndexPath!, toIndexPath: NSIndexPath!) {
}
*/
/*
// Override to support conditional rearranging of the table view.
override func tableView(tableView: UITableView!, canMoveRowAtIndexPath indexPath: NSIndexPath!) -> Bool {
// Return NO if you do not want the item to be re-orderable.
return true
}
*/
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
}
When I uncomment this provided method:
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath) as UITableViewCell
// Configure the cell...
return cell
}
I am greeted with this compiler error: "Overriding method with selector 'tableView: cellForRowAtIndex path has incomplete type..."
Is there something I can do to fix this or is this just a beta issue?
The tableView:didSelectRowAtIndexPath: method's declaration has changed between Xcode 6 beta 6 and Xcode 6 beta 7. This method now requires non-optional parameters and returns a non-optional UITableViewCell.
So, with Xcode 6 beta 7, you have to set the following lines in your UITableViewController subclasses in order to avoid error messages:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
...
}
instead of:
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
...
}
See Apple Developper Library for more details.