mvm_core_ui/MVMCoreUI/BaseClasses/TableViewCell.swift
Pfeil, Scott Robert c89929648f fix centering logic
2020-04-18 17:45:14 -04:00

325 lines
12 KiB
Swift
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// TableViewCell.swift
// MVMCoreUI
//
// Created by Scott Pfeil on 10/29/19.
// Copyright © 2019 Verizon Wireless. All rights reserved.
//
import UIKit
@objcMembers open class TableViewCell: UITableViewCell, MoleculeViewProtocol, MoleculeListCellProtocol, MVMCoreViewProtocol {
open var molecule: MoleculeViewProtocol?
open var listItemModel: ListItemModelProtocol?
public let containerHelper = ContainerHelper()
// For the accessory view convenience.
private var caretView: CaretView?
private var caretViewWidthSizeObject: MFSizeObject?
private var caretViewHeightSizeObject: MFSizeObject?
// For separation between cells.
public var topSeparatorView: Line?
public var bottomSeparatorView: Line?
/// For subclasses that want to use a custom accessory view.
open var customAccessoryView = false
public var heroAccessoryCenter: CGPoint?
private var initialSetupPerformed = false
// MARK: - Styling
open func style(with styleString: String?) {
guard let styleString = styleString else {
return
}
switch styleString {
case "standard":
styleStandard()
case "shortDivider":
styleShortDivider()
case "tallDivider":
styleTallDivider()
case "sectionFooter":
styleFooter()
case "none":
styleNone()
default: break
}
}
open func styleStandard() {
listItemModel?.topMarginPadding = 24
listItemModel?.bottomMarginPadding = 24
topSeparatorView?.setStyle(.none)
bottomSeparatorView?.setStyle(.standard)
}
open func styleTallDivider() {
listItemModel?.topMarginPadding = 48
listItemModel?.bottomMarginPadding = 16
topSeparatorView?.setStyle(.none)
bottomSeparatorView?.setStyle(.thin)
}
open func styleShortDivider() {
listItemModel?.topMarginPadding = 32
listItemModel?.bottomMarginPadding = 16
topSeparatorView?.setStyle(.none)
bottomSeparatorView?.setStyle(.thin)
}
open func styleFooter() {
listItemModel?.topMarginPadding = 24
listItemModel?.bottomMarginPadding = 0
topSeparatorView?.setStyle(.none)
bottomSeparatorView?.setStyle(.none)
}
open func styleNone() {
listItemModel?.topMarginPadding = 0
listItemModel?.bottomMarginPadding = 0
topSeparatorView?.setStyle(.none)
bottomSeparatorView?.setStyle(.none)
}
/// Adds the molecule to the view.
open func addMolecule(_ molecule: MoleculeViewProtocol) {
contentView.addSubview(molecule)
containerHelper.constrainView(molecule)
self.molecule = molecule
}
open override func layoutSubviews() {
super.layoutSubviews()
// Ensures accessory view aligns to the center y derived from the
if let _ = heroAccessoryCenter {
alignAccessoryToHero()
}
}
// MARK: - Inits
public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
initialSetup()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialSetup()
}
public func initialSetup() {
if !initialSetupPerformed {
initialSetupPerformed = true
setupView()
}
}
// MARK: - MFViewProtocol
open func updateView(_ size: CGFloat) {
containerHelper.updateViewMargins(self, model: listItemModel, size: size)
if accessoryView != nil {
// Smaller left margin if accessory view.
var margin = directionalLayoutMargins
margin.trailing = 16
contentView.directionalLayoutMargins = margin
// Update caret automatically.
if let caretView = caretView, let widthObject = caretViewWidthSizeObject, let heightObject = caretViewHeightSizeObject {
caretView.frame = CGRect(x: 0, y: 0, width: widthObject.getValueBased(onSize: size), height: heightObject.getValueBased(onSize: size))
}
} else {
contentView.directionalLayoutMargins = directionalLayoutMargins
}
topSeparatorView?.updateView(size)
bottomSeparatorView?.updateView(size)
(molecule as? MVMCoreViewProtocol)?.updateView(size)
}
open func setupView() {
selectionStyle = .none
insetsLayoutMarginsFromSafeArea = false
preservesSuperviewLayoutMargins = false
contentView.insetsLayoutMarginsFromSafeArea = false
contentView.preservesSuperviewLayoutMargins = false
}
//TODO: ModelProtocol, Change to model
open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
guard let model = model as? ListItemModelProtocol else { return }
self.listItemModel = model
style(with: model.style)
// Add the caret if there is an action and it's not declared hidden.
if !customAccessoryView {
if let _ = model.action, !(model.hideArrow ?? false) {
addCaretViewAccessory()
} else {
accessoryView = nil
}
}
// override the separator
if let separator = model.line {
addSeparatorsIfNeeded()
bottomSeparatorView?.set(with: separator, nil, nil)
}
if let moleculeModel = model as? MoleculeModelProtocol,
let backgroundColor = moleculeModel.backgroundColor {
self.backgroundColor = backgroundColor.uiColor
}
// align if needed.
containerHelper.set(with: model, for: molecule as? MVMCoreUIViewConstrainingProtocol)
}
open func reset() {
molecule?.reset()
backgroundColor = .white
}
open class func nameForReuse(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? {
return model.moleculeName
}
open class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? {
return nil
}
// MARK: - Caret View
/// Adds the standard mvm style caret to the accessory view
@objc public func addCaretViewAccessory() {
guard accessoryView == nil else { return }
let caret = CaretView(lineWidth: 1)
caret.translatesAutoresizingMaskIntoConstraints = true
caret.isAccessibilityElement = true
caret.accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "AccTabHint")
caret.size = .small(.vertical)
if let size = caret.size?.dimensions() {
caret.frame = CGRect(origin: CGPoint.zero, size: size)
caretViewWidthSizeObject = MFSizeObject(standardSize: size.width, standardiPadPortraitSize: 9)
caretViewHeightSizeObject = MFSizeObject(standardSize: size.height, standardiPadPortraitSize: 16)
}
caretView = caret
accessoryView = caret
}
/// NOTE: Should only be called when displayed or about to be displayed.
public func alignAccessoryToHero() {
// Layout call required to force draw in memory to get dimensions of subviews.
layoutIfNeeded()
guard let heroLabel = findHeroLabel(views: contentView.subviews), let hero = heroLabel.hero else { return }
let rect = Label.boundingRect(forCharacterRange: NSRange(location: hero, length: 1), in: heroLabel)
accessoryView?.center.y = convert(UIView(frame: rect).center, from: heroLabel).y
heroAccessoryCenter = accessoryView?.center
}
/// Traverses the view hierarchy for a 🦸 heroic Label.
private func findHeroLabel(views: [UIView]) -> Label? {
if views.isEmpty {
return nil
}
var queue = [UIView]()
// Reversed the array to first check views at the front.
for view in views.reversed() {
// Only one Label will have a hero in a table cell.
if let label = view as? Label, label.hero != nil {
return label
}
queue.append(contentsOf: view.subviews)
}
return findHeroLabel(views: queue)
}
// MARK: - MoleculeListCellProtocol
/// For when the separator between cells shows using json and frequency. Default is type: standard, frequency: allExceptTop.
public func setLines(with model: LineModel?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?, indexPath: IndexPath) {
addSeparatorsIfNeeded()
if let model = model {
topSeparatorView?.set(with: model, delegateObject, additionalData)
bottomSeparatorView?.set(with: model, delegateObject, additionalData)
} else {
topSeparatorView?.setStyle(.standard)
bottomSeparatorView?.setStyle(.standard)
}
setSeparatorFrequency(model?.frequency ?? .allExceptTop, indexPath: indexPath)
}
public func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
//TODO: Use object when handleAction is rewrote to handle action model
if let actionMap = self.listItemModel?.action?.toJSON() {
MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject)
}
}
public func willDisplay() {
alignAccessoryToHero()
}
// MARK: - Separator
open func addSeparatorsIfNeeded() {
if topSeparatorView == nil {
let line = Line()
line.setStyle(.none)
addSubview(line)
NSLayoutConstraint.pinViewTop(toSuperview: line, useMargins: false, constant: 0).isActive = true
NSLayoutConstraint.pinViewLeft(toSuperview: line, useMargins: true, constant: 0).isActive = true
NSLayoutConstraint.pinViewRight(toSuperview: line, useMargins: true, constant: 0).isActive = true
topSeparatorView = line
}
if bottomSeparatorView == nil {
let line = Line()
line.setStyle(.none)
addSubview(line)
NSLayoutConstraint.pinViewBottom(toSuperview: line, useMargins: false, constant: 0).isActive = true
NSLayoutConstraint.pinViewLeft(toSuperview: line, useMargins: true, constant: 0).isActive = true
NSLayoutConstraint.pinViewRight(toSuperview: line, useMargins: true, constant: 0).isActive = true
bottomSeparatorView = line
}
}
/// For when the separator between cells shows.
public func setSeparatorFrequency(_ separatorFrequency: LineModel.Frequency, indexPath: IndexPath) {
switch separatorFrequency {
case .all:
if indexPath.row == 0 {
topSeparatorView?.isHidden = false
} else {
topSeparatorView?.isHidden = true
}
bottomSeparatorView?.isHidden = false
case .allExceptBottom:
topSeparatorView?.isHidden = false
bottomSeparatorView?.isHidden = true
case .between:
if indexPath.row == 0 {
topSeparatorView?.isHidden = true
} else {
topSeparatorView?.isHidden = false
}
bottomSeparatorView?.isHidden = true
case .allExceptTop:
fallthrough
default:
topSeparatorView?.isHidden = true
bottomSeparatorView?.isHidden = false
}
}
}