1

Imagine a table view controller ExtraRowTableViewController,

which always inserts an extra row, after (let's say) the third row.

So in this example ...

class SomeList:ExtraRowTableViewController
override func numberOfSectionsInTableView(tableView: UITableView)->Int
    {
    return yourData.count ... say, 50 items
    }
override func tableView
    (tableView:UITableView, cellForRowAtIndexPath indexPath:NSIndexPath)
                                        -> UITableViewCell
    {
    return yourData.cell ... for that row number
    }

ExtraRowTableViewController would "take over" and actually return 51.

For cellForRowAtIndexPath, it would "take over" and return its own cell at row four, it would return your cell row N from 0 to 3, and it would return your cell row minus one for rows above four.

How can this be achieved in ExtraRowTableViewController ?

So that the programmer of SomeList need make no change at all.

Would you be subclassing UITableView, or the data source delegate .. or??


To clarify, an example use case might be, let's say, adding an ad, editing field, or some special news, at the fourth row. It would be appropriate that the programmer of SomeList need do absolutely nothing to achieve this, ie it is achieved in a completely OO manner.


Note that it's, of course, easy to just add new "substitute" calls, which your table view would "just know" to use instead of the normal calls. (RMenke has provide a useful full example of this below.) So,

class SpecialTableViewController:UITableViewController

func tableView(tableView: UITableView, specialNumberOfRowsInSection section: Int) -> Int
  {
  print ("You forgot to supply an override for specialNumberOfRowsInSection")
  }

func tableView
  (tableView:UITableView, specialCellForRowAtIndexPath indexPath:NSIndexPath) -> UITableViewCell
  {
  print ("You forgot to supply an override for specialCellForRowAtIndexPath")
  }

override final func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
  {
  return self.specialNumberOfRowsInSection(section) + 1
  }

override final func tableView
  (tableView:UITableView, cellForRowAtIndexPath indexPath:NSIndexPath) -> UITableViewCell
  {
  if indexPath.row == 4
    { return ... the special advertisement cell ... }
  if indexPath.row < 4
    { return self.specialCellForRowAtIndexPath( indexPath )
  if indexPath.row > 4
    { return self.specialCellForRowAtIndexPath( [indexPath.row - 1] )
  }

In the example your table view programmer would have to "just know" that they must use specialNumberOfRowsInSection and specialCellForRowAtIndexPath in SpecialTableViewController rather than the usual calls ... it's not a clean, drop-in, OO solution.


Note: I appreciate you could probably subclass NSObject in some way to override the signals (such as discussed here), but that is not a language solution.

Community
  • 1
  • 1
Fattie
  • 27,874
  • 70
  • 431
  • 719

1 Answers1

3

github link -> might contain more updated code

To answer the question: It is not possible to override the standard flow of the functions between the UITableViewController and the UITableViewDataSource in the form of a subclass.

The UIKit source code is like a big black box which we can not see or alter. (apps will be rejected if you do.) To do exactly what you want you would need to override the functions that call on the functions from the UITableViewDataSource so they point to a third function instead of to the protocol functions. This third function would alter the basic behaviour and trigger the function from the UITableViewDataSource. This way it would all stay the same for other devs.

Hack : Subclass the entire UITableviewController -> you need stored properties. This way other people can subclass your custom class and they won't see any of the magic/mess under the hood.


The class below uses the same style as the regular UITableViewController. Users override the methods they wish to alter. Because those methods are used inside the existing function you get an altered functionality.

Unfortunately it is not possible to mark those functions as private.


The adapter for the indexPath stores a Bool and the original indexPath. -> This will correspond to your data.

The new inserted cells will get an indexPath based on the section they are created in and a counter. -> Could be useful.


Update: Add x extra rows after y rows


class IATableViewController: UITableViewController {

    private var adapters : [[cellAdapter]] = []

    private struct cellAdapter {
        var isDataCell : Bool = true
        var indexPath : NSIndexPath = NSIndexPath()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func cellIdentifier(tableView: UITableView, isDataCell: Bool) -> String {
        return "Cell"
    }


    func numberOfSections(tableView: UITableView) -> Int {
        return 0
    }

    func numberOfRowsInSection(tableView: UITableView, section: Int) -> Int {
        return 0
    }

    func insertXRowsEveryYRows(tableView: UITableView, section: Int) -> (numberOfRows:Int, everyYRows:Int)? {
        //(numberOfRows:0, everyYRows:0)
        return nil
    }

    func insertXRowsAfterYRows(tableView: UITableView, section: Int) -> (numberOfRows:Int, afterYRows:Int)? {
        //(numberOfRows:0, afterYRows:0)
        return nil
    }

    internal override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        let sections = numberOfSections(tableView)

        adapters = []

        for _ in 0..<sections {
            adapters.append([])
        }

        return sections
    }

    internal override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        let rows = numberOfRowsInSection(tableView, section: section)

        adapters[section] = []

        for row in 0..<rows {
            var adapter = cellAdapter()
            adapter.indexPath = NSIndexPath(forRow: row, inSection: section)
            adapter.isDataCell = true
            adapters[section].append(adapter)
        }

        insertion(tableView, section: section)

        return adapters[section].count
    }

    private func insertion(tableView: UITableView, section: Int) {

        if let insertRowEvery = insertXRowsEveryYRows(tableView, section: section) {
            let insertionPoint = insertRowEvery.everyYRows
            let insertionTimes = insertRowEvery.numberOfRows

            var counter = 0

            var startArray = adapters[section]
            var insertionArray: [cellAdapter] = []

            while !startArray.isEmpty {

                if startArray.count > (insertionPoint - 1) {

                    for _ in 0..<insertionPoint {
                        insertionArray.append(startArray.removeFirst())
                    }
                    for _ in 0..<insertionTimes {
                        var adapter = cellAdapter()
                        adapter.indexPath = NSIndexPath(forRow: counter, inSection: section)
                        adapter.isDataCell = false
                        insertionArray.append(adapter)
                        counter += 1
                    }
                } else {
                    insertionArray += startArray
                    startArray = []
                }
            }

            adapters[section] = insertionArray

        }
        else if let insertRowAfter = insertXRowsAfterYRows(tableView, section: section) {

            let insertionPoint = insertRowAfter.afterYRows
            let insertionTimes = insertRowAfter.numberOfRows

            if adapters[section].count > (insertionPoint - 1) {

                for i in 0..<insertionTimes {

                    var adapter = cellAdapter()
                    adapter.indexPath = NSIndexPath(forRow: i, inSection: section)
                    adapter.isDataCell = false
                    adapters[section].insert(adapter, atIndex: insertionPoint)

                }
            }
        }
    }


    func insertionCellForRowAtIndexPath(tableView: UITableView, cell: UITableViewCell, indexPath: NSIndexPath) -> UITableViewCell {

        return cell
    }



    func dataCellForRowAtIndexPath(tableView: UITableView, cell: UITableViewCell, indexPath: NSIndexPath) -> UITableViewCell {

        return cell

    }


    internal override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let adapter = adapters[indexPath.section][indexPath.row]

        let identifier = cellIdentifier(tableView, isDataCell: adapter.isDataCell)
        let cell = tableView.dequeueReusableCellWithIdentifier(identifier, forIndexPath: indexPath)

        switch adapter.isDataCell {
        case true :
            return dataCellForRowAtIndexPath(tableView, cell: cell, indexPath: adapter.indexPath)
        case false :
            return insertionCellForRowAtIndexPath(tableView, cell: cell, indexPath: adapter.indexPath)
        }
    }
}
R Menke
  • 8,183
  • 4
  • 35
  • 63
  • Are you basically proposing that there are new functions, and the people writing subclasses of IATableViewController would have to know to use those new functions, instead of the usual ones ? – Fattie Sep 30 '15 at 22:39
  • @JoeBlow yes to the second part. Unfortunately the TableViewDataSource, where the default functions come from, is a protocol and we can't add stored properties to protocols. Which means we can't add a translator/adapter for the row/section numbers. You need something like that. Else the indexPath you get from, for example selecting a row won't match your data anymore. Check out the github. It is a lot simpler than it may seem now because you only override the new functions. Everything else is hidden. – R Menke Sep 30 '15 at 22:48
  • @JoeBlow to answer the first comment. No matter how many rows/sections you add in any place you will always need a translator (like stated above). Obviously you can change the code above to only add one extra row at the end or at "4". – R Menke Sep 30 '15 at 22:49
  • You know RM, I appreciate the thoughtful code and it will surely be useful to future googlers since it's such highly developed code. However - I think it might have mentioned in an earlier edit of the Q - one can of course just add new utility substitute functions, which, programmers of subclasses would have to know to follow (so, using those instead of the "normal" apple ones.....) unfortunately what Im wondering is, is there in fact a way to **"not do that"** .. to actually "super-override" those EXISTING functions .. without having to make new substitute functions .. it's a hard one! – Fattie Oct 01 '15 at 17:01
  • @JoeBlow Ironically no. What you are trying to do is hide your source code from other devs. Which is what apple does and which is the reason you can't. You want to override the function that calls the functions in the `UITableViewDataSource` but you have no access to that function. So you can't insert a function in between the two to do the translation out of sight. – R Menke Oct 01 '15 at 17:13
  • hi RM - indeed that may be the actual answer to the question ("it can not actually be done") ... {perhaps you shoudl post another answer, nothing wrong with two answers}. Note however - if there was "simply" a way to make a new version of UITableViewController, **which was identical to normal but had a new, subclassed, version of the *protocol*** .. the job would be done. Maybe there is a way to do this in Swift. {note too that as I mention in a comment, you can in fact achieve this by intercepting the messages; but that's not appropriate behaviour :) } – Fattie Oct 01 '15 at 17:24
  • The protocol is not the problem. Since it is just a template. The problem is the function somewhere in the source code that triggers the function from the protocol. – R Menke Oct 01 '15 at 17:35
  • Hi RMenke, you've made the only answer and it has tremendous input, so you deservedly take the bounty! Thanks! – Fattie Oct 04 '15 at 14:25
  • No problem. Was an interesting question to work on. Cheers! – R Menke Oct 04 '15 at 15:07
  • @RMenke This is a great answer, I've been looking for something like this for a while! Cheers! – JoniVR May 18 '18 at 20:42