280 lines
11 KiB
Swift
280 lines
11 KiB
Swift
//
|
|
// ThreeLayerTableViewController.swift
|
|
// MVMCoreUI
|
|
//
|
|
// Created by Scott Pfeil on 4/18/19.
|
|
// Copyright © 2019 Verizon Wireless. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
|
|
open class ThreeLayerTableViewController: ProgrammaticTableViewController, RotorViewElementsProtocol {
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Main Views
|
|
//--------------------------------------------------
|
|
|
|
private var headerView: UIView?
|
|
private var footerView: UIView?
|
|
public var topView: UIView?
|
|
public var middleView: UIView? {
|
|
get { tableView }
|
|
set { }
|
|
}
|
|
public var bottomView: UIView?
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Properties
|
|
//--------------------------------------------------
|
|
|
|
public var bottomViewOutsideOfScrollArea: Bool = false
|
|
public var topViewOutsideOfScrollArea: Bool = false
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Constraints
|
|
//--------------------------------------------------
|
|
|
|
private var topViewBottomConstraint: NSLayoutConstraint?
|
|
private var bottomViewTopConstraint: NSLayoutConstraint?
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - MFViewController
|
|
//--------------------------------------------------
|
|
|
|
open override func updateViews() {
|
|
super.updateViews()
|
|
let width = view.bounds.width
|
|
if let topView = topView as? MVMCoreViewProtocol {
|
|
topView.updateView(width)
|
|
showHeader(width)
|
|
}
|
|
if let bottomView = bottomView as? MVMCoreViewProtocol {
|
|
bottomView.updateView(width)
|
|
showFooter(width)
|
|
}
|
|
tableView.reloadData()
|
|
}
|
|
|
|
open override func updateUI(for molecules: [MoleculeModelProtocol]? = nil) {
|
|
super.updateUI(for: molecules)
|
|
|
|
guard molecules == nil else { return }
|
|
|
|
createViewForTableHeader()
|
|
createViewForTableFooter()
|
|
tableView?.reloadData()
|
|
}
|
|
|
|
override open func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
// Do any additional setup after loading the view.
|
|
setNoSectionHeadersFooters()
|
|
|
|
// Ensures the footer and headers are the right size
|
|
tableView.frameChangeAction = { [weak self] in
|
|
self?.view.setNeedsUpdateConstraints()
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Spacing
|
|
//--------------------------------------------------
|
|
|
|
// If both are subclassed to return a value, then the buttons will not be pinned towards the bottom because neither spacing would try to fill the screen.
|
|
/// Space between the top view and the table sections, nil to fill. 0 default
|
|
open func spaceBelowTopView() -> CGFloat? { 0 }
|
|
|
|
/// Space between the bottom view and the table sections, nil to fill. nil default
|
|
open func spaceAboveBottomView() -> CGFloat? { nil }
|
|
|
|
/// can override to return a minimum fill space.
|
|
open func minimumFillSpace() -> CGFloat { 0 }
|
|
|
|
open override func updateViewConstraints() {
|
|
guard let tableView = tableView else {
|
|
super.updateViewConstraints()
|
|
return
|
|
}
|
|
|
|
let minimumSpace: CGFloat = minimumFillSpace()
|
|
var currentSpace: CGFloat = 0
|
|
var totalMinimumSpace: CGFloat = 0
|
|
|
|
var fillTop = false
|
|
if spaceBelowTopView() == nil, tableView.tableHeaderView != nil {
|
|
fillTop = true
|
|
currentSpace += topViewBottomConstraint?.constant ?? 0
|
|
totalMinimumSpace += minimumSpace
|
|
}
|
|
|
|
var fillBottom = false
|
|
if spaceAboveBottomView() == nil, tableView.tableFooterView != nil {
|
|
fillBottom = true
|
|
currentSpace += bottomViewTopConstraint?.constant ?? 0
|
|
totalMinimumSpace += minimumSpace
|
|
}
|
|
|
|
guard fillTop || fillBottom else {
|
|
super.updateViewConstraints()
|
|
return
|
|
}
|
|
|
|
let newSpace = MVMCoreUIUtility.getVariableConstraintHeight(currentSpace, in: tableView, minimumHeight: totalMinimumSpace)
|
|
let width = view.bounds.width
|
|
if !MVMCoreGetterUtility.cgfequalwiththreshold(newSpace, currentSpace, 0.1) {
|
|
if fillTop && fillBottom {
|
|
// space both
|
|
let half = newSpace / 2
|
|
topViewBottomConstraint?.constant = half
|
|
bottomViewTopConstraint?.constant = half
|
|
showHeader(width)
|
|
showFooter(width)
|
|
} else if fillTop {
|
|
topViewBottomConstraint?.constant = newSpace
|
|
showHeader(width)
|
|
} else if fillBottom {
|
|
// Only bottom is spaced.
|
|
bottomViewTopConstraint?.constant = newSpace
|
|
showFooter(width)
|
|
}
|
|
refreshTable()
|
|
}
|
|
super.updateViewConstraints()
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Header Footer
|
|
//--------------------------------------------------
|
|
|
|
/// Gets the top view and adds it to a spacing view, headerView, and then calls showHeader.
|
|
open func createViewForTableHeader() {
|
|
var topView = viewForTop()
|
|
self.topView = topView
|
|
|
|
// If top view is outside of scroll area, create a dummy view for the header. Small height is needed to stop apple from adding padding for grouped tables when no header.
|
|
if topViewOutsideOfScrollArea {
|
|
topView = MVMCoreUICommonViewsUtility.getView(with: 0.5)
|
|
}
|
|
let headerView = MVMCoreUICommonViewsUtility.commonView()
|
|
headerView.addSubview(topView)
|
|
topView.topAnchor.constraint(equalTo: headerView.topAnchor).isActive = true
|
|
topView.leftAnchor.constraint(equalTo: headerView.leftAnchor).isActive = true
|
|
headerView.rightAnchor.constraint(equalTo: topView.rightAnchor).isActive = true
|
|
topViewBottomConstraint = headerView.bottomAnchor.constraint(equalTo: topView.bottomAnchor, constant: spaceBelowTopView() ?? 0)
|
|
topViewBottomConstraint?.isActive = true
|
|
self.headerView = headerView
|
|
showHeader(nil)
|
|
}
|
|
|
|
/// Gets the bottom view and adds it to a spacing view, footerView, and then calls showFooter.
|
|
open func createViewForTableFooter() {
|
|
var bottomView = viewForBottom()
|
|
self.bottomView = bottomView
|
|
|
|
// If bottom view is outside of scroll area, create a dummy view for the header. Small height is needed to stop apple from adding padding for grouped tables when no header.
|
|
if bottomViewOutsideOfScrollArea {
|
|
bottomView = MVMCoreUICommonViewsUtility.getView(with: 0.5)
|
|
}
|
|
let footerView = MVMCoreUICommonViewsUtility.commonView()
|
|
footerView.backgroundColor = bottomView.backgroundColor
|
|
footerView.addSubview(bottomView)
|
|
bottomViewTopConstraint = bottomView.topAnchor.constraint(equalTo: footerView.topAnchor, constant: spaceAboveBottomView() ?? 0)
|
|
bottomViewTopConstraint?.isActive = true
|
|
bottomView.leftAnchor.constraint(equalTo: footerView.leftAnchor).isActive = true
|
|
footerView.rightAnchor.constraint(equalTo: bottomView.rightAnchor).isActive = true
|
|
footerView.bottomAnchor.constraint(equalTo: bottomView.bottomAnchor).isActive = true
|
|
self.footerView = footerView
|
|
showFooter(nil)
|
|
}
|
|
|
|
/// Takes the current headerView and adds it to the tableHeaderView
|
|
func showHeader(_ sizingWidth: CGFloat?) {
|
|
headerView?.removeFromSuperview()
|
|
tableView?.tableHeaderView = nil
|
|
guard let headerView = headerView else { return }
|
|
|
|
if topViewOutsideOfScrollArea,
|
|
let topView = topView {
|
|
// put top view outside of scrolling area.
|
|
topConstraint?.isActive = false
|
|
view.addSubview(topView)
|
|
topView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
|
|
topView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
|
|
view.rightAnchor.constraint(equalTo: topView.rightAnchor).isActive = true
|
|
tableView.topAnchor.constraint(equalTo: topView.bottomAnchor).isActive = true
|
|
} else {
|
|
topConstraint?.isActive = true
|
|
}
|
|
// This extra view is needed because of the wonkiness of apple's table header. Things breaks if using autolayout.
|
|
headerView.setNeedsLayout()
|
|
headerView.layoutIfNeeded()
|
|
MVMCoreUIUtility.sizeView(toFit: headerView)
|
|
let tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: MVMCoreUIUtility.getWidth(), height: headerView.frame.height))
|
|
tableHeaderView.addSubview(headerView)
|
|
NSLayoutConstraint.constraintPinSubview(toSuperview: headerView)
|
|
tableView?.tableHeaderView = tableHeaderView
|
|
}
|
|
|
|
/// Takes the current footerView and adds it to the tableFooterView
|
|
func showFooter(_ sizingWidth: CGFloat?) {
|
|
footerView?.removeFromSuperview()
|
|
guard let footerView = footerView,
|
|
let tableView = tableView else {
|
|
self.tableView?.tableFooterView = nil
|
|
return
|
|
}
|
|
|
|
if bottomViewOutsideOfScrollArea,
|
|
let bottomView = bottomView {
|
|
// put bottom view outside of scrolling area.
|
|
bottomConstraint?.isActive = false
|
|
view.addSubview(bottomView)
|
|
bottomView.topAnchor.constraint(equalTo: tableView.bottomAnchor).isActive = true
|
|
bottomView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
|
|
view.rightAnchor.constraint(equalTo: bottomView.rightAnchor).isActive = true
|
|
view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: bottomView.bottomAnchor).isActive = true
|
|
} else {
|
|
bottomConstraint?.isActive = true
|
|
}
|
|
|
|
// if footer already exists, use the same y location to avoid strange moving animation
|
|
let y = tableView.tableFooterView?.frame.minY ?? 0.0
|
|
|
|
//force footerView to redraw
|
|
footerView.setNeedsLayout()
|
|
footerView.layoutIfNeeded()
|
|
|
|
// This extra view is needed because of the wonkiness of apple's table footer. Things breaks if using autolayout.
|
|
MVMCoreUIUtility.sizeView(toFit: footerView)
|
|
let tableFooterView = UIView(frame: CGRect(x: 0, y: y, width: MVMCoreUIUtility.getWidth(), height: footerView.frame.height))
|
|
tableFooterView.addSubview(footerView)
|
|
NSLayoutConstraint.constraintPinSubview(toSuperview: footerView)
|
|
tableView.tableFooterView = tableFooterView
|
|
}
|
|
|
|
//--------------------------------------------------
|
|
// MARK: - Functions to subclass
|
|
//--------------------------------------------------
|
|
|
|
/// Subclass for a top view.
|
|
open func viewForTop() -> UIView {
|
|
// Small height is needed to stop apple from adding padding for grouped tables when no header.
|
|
MVMCoreUICommonViewsUtility.getView(with: 0.5)
|
|
}
|
|
|
|
/// Subclass for a bottom view.
|
|
open func viewForBottom() -> UIView {
|
|
// Default spacing is standard when no buttons.
|
|
MVMCoreUICommonViewsUtility.getView(with: PaddingDefaultVerticalSpacing)
|
|
}
|
|
|
|
deinit {
|
|
tableView?.delegate = nil
|
|
}
|
|
|
|
// Ensures the footer and headers are the right size
|
|
func scrollViewDidChangeAdjustedContentInset(_ scrollView: UIScrollView) {
|
|
view.setNeedsUpdateConstraints()
|
|
}
|
|
}
|