【问题标题】:Swift, "subclass" the data source methods of UITableView somehow?Swift,以某种方式“子类化” UITableView 的数据源方法?
【发布时间】:2015-09-26 14:02:14
【问题描述】:

想象一个表格视图控制器ExtraRowTableViewController

它总是在(比如说)第三行之后插入一个额外的行。

所以在这个例子中...

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 将“接管”并实际返回 51。

对于 cellForRowAtIndexPath,它将“接管”并在第四行返回它自己的单元格,它会将您的单元格行 N 从 0 到 3 返回,并且它将返回您的单元格行减去四行以上的行。

如何在ExtraRowTableViewController 中实现这一点?

所以 SomeList 的程序员根本不需要做任何改变。

你会继承 UITableView,还是数据源委托.. 还是??


为了澄清,一个示例用例可能是,比方说,在第四行添加广告、编辑字段或一些特殊新闻。 SomeList 的程序员完全不需要做任何事情来实现这一点是合适的,即以完全 OO 的方式实现。


请注意,当然,只需添加新的“替代”调用很容易,您的表格视图会“知道”使用它而不是正常调用。 (RMenke 在下面提供了一个有用的完整示例。)所以,

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] )
  }

在示例中,您的表格视图程序员必须“只知道”他们必须在 SpecialTableViewController 中使用 specialNumberOfRowsInSection 和 specialCellForRowAtIndexPath 而不是通常的调用......这不是一个干净、直接的 OO 解决方案。


注意:我很欣赏您可能以某种方式继承 NSObject 来覆盖信号(例如讨论过的here),但这不是语言解决方案。

【问题讨论】:

    标签: swift oop overriding subclass


    【解决方案1】:

    github link -> 可能包含更多更新的代码

    回答问题:不能以子类的形式覆盖UITableViewController和UITableViewDataSource之间函数的标准流程。

    UIKit 源代码就像一个我们无法看到或更改的大黑盒子。 (如果您这样做,应用程序将被拒绝。)要完全按照您的意愿行事,您需要覆盖从 UITableViewDataSource 调用函数的函数,以便它们指向第三个函数而不是协议函数。这第三个函数将改变基本行为并从 UITableViewDataSource 触发该函数。这样对于其他开发人员来说,一切都会保持不变。

    Hack:子类化整个 UITableviewController -> 你需要存储的属性。这样,其他人就可以继承您的自定义类,而他们不会看到引擎盖下的任何魔法/混乱。


    下面的类使用与常规 UITableViewController 相同的样式。用户覆盖他们希望更改的方法。因为这些方法是在现有函数中使用的,所以您会获得改变的功能。

    很遗憾,无法将这些函数标记为私有。


    indexPath 的适配器存储 Bool 和原始 indexPath。 -> 这将与您的数据相对应。

    新插入的单元格将根据创建它们的部分和计数器获得一个 indexPath。 -> 可能有用。


    更新:在 y 行之后添加 x 额外行


    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)
            }
        }
    }
    

    【讨论】:

    • 你是不是在提议有新功能,而编写 IATableViewController 子类的人必须知道使用这些新功能,而不是通常的功能?
    • @JoeBlow 对第二部分是肯定的。不幸的是,默认函数的来源 TableViewDataSource 是一个协议,我们不能将存储的属性添加到协议中。这意味着我们不能为行/节号添加翻译器/适配器。你需要这样的东西。否则,您从中获得的 indexPath,例如选择一行将不再与您的数据匹配。查看github。它比现在看起来要简单得多,因为您只覆盖了新功能。其他一切都被隐藏了。
    • @JoeBlow 回答第一条评论。无论您在任何地方添加多少行/部分,您都将始终需要翻译器(如上所述)。显然,您可以将上面的代码更改为仅在末尾或“4”处添加一行。
    • 你知道 RM,我很欣赏周到的代码,它肯定会对未来的谷歌人有用,因为它是高度开发的代码。但是 - 我认为它可能在 Q 的早期编辑中提到过 - 当然可以添加新的实用程序替代函数,子类的程序员必须知道遵循(因此,使用那些而不是“普通”苹果那些.....)不幸的是,我想知道的是,实际上是否有一种方法可以“不这样做” ..实际上“超级覆盖”那些现有功能..而不必制作新的替代功能.. 很难!
    • @JoeBlow 讽刺的是没有。您要做的是向其他开发人员隐藏您的源代码。这就是苹果所做的,而这就是你不能做的原因。您想覆盖调用UITableViewDataSource 中的函数的函数,但您无权访问该函数。所以你不能在两者之间插入一个函数来进行翻译。
    猜你喜欢
    • 2018-06-23
    • 2011-03-23
    • 2019-05-13
    • 2015-01-17
    • 2014-09-21
    • 1970-01-01
    • 2019-12-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多