Я пришел к следующему решению.Огромное спасибо всем участникам этой ветки за большую часть решения.Класс TableViewController.TableView обеспечивает желаемую функциональность.Оставшаяся часть кода завершает полный пример.
//
// TableViewController.swift
// Tables
//
// Created by Robert Vaessen on 11/6/18.
// Copyright © 2018 Robert Vaessen. All rights reserved.
//
// Note: Add the following to AppDelegate:
//
// func application(_ application: UIApplication,
// didFinishLaunchingWithOptions launchOptions:
// [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// window = UIWindow(frame: UIScreen.main.bounds)
// window?.makeKeyAndVisible()
// window?.rootViewController = TableViewController()
// return true
// }
import UIKit
class TableViewController: UIViewController {
class TableView : UITableView {
override func reloadData() {
execute() { super.reloadData() }
}
override func reloadRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation) {
execute() { super.reloadRows(at: indexPaths, with: animation) }
}
private func execute(reload: @escaping () -> Void) {
CATransaction.begin()
CATransaction.setCompletionBlock() {
print("Reload completed")
_ = self.adjustFooter()
}
print("\nReload begun")
reload()
CATransaction.commit()
}
private func adjustFooter() -> Bool {
guard let footerView = tableFooterView else { return false }
func calcFooterHeight() -> CGFloat {
var heightUsed = tableHeaderView?.bounds.height ?? 0
for cell in visibleCells { heightUsed += cell.bounds.height }
let heightRemaining = bounds.height - heightUsed
let minHeight: CGFloat = 44
return heightRemaining > minHeight ? heightRemaining : minHeight
}
let newHeight = calcFooterHeight()
guard newHeight != footerView.bounds.height else { return false }
// Keep the origin where it is, i.e. tweaking just the height expands the frame about its center
let currentFrame = footerView.frame
footerView.frame = CGRect(x: currentFrame.origin.x, y: currentFrame.origin.y, width: currentFrame.width, height: newHeight)
return true
}
}
class FooterView : UIView {
override func draw(_ rect: CGRect) {
print("Drawing footer")
super.draw(rect)
}
}
private var tableView: TableView!
private let cellReuseId = "TableCell"
private let data: [UIColor] = [UIColor(white: 0.4, alpha: 1), UIColor(white: 0.5, alpha: 1), UIColor(white: 0.6, alpha: 1), UIColor(white: 0.7, alpha: 1)]
private var dataRepeatCount = 1
override func viewDidLoad() {
super.viewDidLoad()
func createTable(in: UIView) -> TableView {
let tableView = TableView(frame: CGRect.zero)
tableView.separatorStyle = .none
tableView.translatesAutoresizingMaskIntoConstraints = false
`in`.addSubview(tableView)
tableView.centerXAnchor.constraint(equalTo: `in`.centerXAnchor).isActive = true
tableView.centerYAnchor.constraint(equalTo: `in`.centerYAnchor).isActive = true
tableView.widthAnchor.constraint(equalTo: `in`.widthAnchor, multiplier: 1).isActive = true
tableView.heightAnchor.constraint(equalTo: `in`.heightAnchor, multiplier: 0.8).isActive = true
tableView.dataSource = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseId)
return tableView
}
func addHeader(to: UITableView) {
let header = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 50))
to.tableHeaderView = header
let color = UIColor.black
let offset: CGFloat = 64
let add = UIButton(type: .system)
add.setTitle("Add", for: .normal)
add.layer.borderColor = color.cgColor
add.layer.borderWidth = 1
add.layer.cornerRadius = 5
add.tintColor = color
add.contentEdgeInsets = UIEdgeInsets.init(top: 8, left: 8, bottom: 8, right: 8)
add.addTarget(self, action: #selector(addRows), for: .touchUpInside)
add.translatesAutoresizingMaskIntoConstraints = false
header.addSubview(add)
add.centerXAnchor.constraint(equalTo: to.centerXAnchor, constant: -offset).isActive = true
add.centerYAnchor.constraint(equalTo: header.centerYAnchor).isActive = true
let remove = UIButton(type: .system)
remove.setTitle("Remove", for: .normal)
remove.layer.borderColor = color.cgColor
remove.layer.borderWidth = 1
remove.layer.cornerRadius = 5
remove.tintColor = color
remove.contentEdgeInsets = UIEdgeInsets.init(top: 8, left: 8, bottom: 8, right: 8)
remove.addTarget(self, action: #selector(removeRows), for: .touchUpInside)
remove.translatesAutoresizingMaskIntoConstraints = false
header.addSubview(remove)
remove.centerXAnchor.constraint(equalTo: header.centerXAnchor, constant: offset).isActive = true
remove.centerYAnchor.constraint(equalTo: header.centerYAnchor).isActive = true
}
func addFooter(to: UITableView) {
let footer = FooterView(frame: CGRect(x: 0, y: 0, width: 0, height: 50))
footer.layer.borderWidth = 3
footer.layer.borderColor = UIColor.red.cgColor
//footer.contentMode = .redraw
to.tableFooterView = footer
}
tableView = createTable(in: view)
addHeader(to: tableView)
addFooter(to: tableView)
view.backgroundColor = .white
tableView.backgroundColor = .black // UIColor(white: 0.2, alpha: 1)
tableView.tableHeaderView!.backgroundColor = .cyan // UIColor(white: 0, alpha: 1)
tableView.tableFooterView!.backgroundColor = .white // UIColor(white: 0, alpha: 1)
}
@objc private func addRows() {
dataRepeatCount += 1
tableView.reloadData()
}
@objc private func removeRows() {
dataRepeatCount -= dataRepeatCount > 0 ? 1 : 0
tableView.reloadData()
}
}
extension TableViewController : UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard section == 0 else { fatalError("Unexpected section: \(section)") }
return dataRepeatCount * data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath)
cell.textLabel?.textAlignment = .center
cell.backgroundColor = data[indexPath.row % data.count]
cell.textLabel?.textColor = .white
cell.textLabel?.text = "\(indexPath.row)"
return cell
}
}