Swift, “subclass” the data source methods of UITab

2019-08-30 23:09发布

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.

1条回答
Viruses.
2楼-- · 2019-08-30 23:45

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)
        }
    }
}
查看更多
登录 后发表回答