// // MoleculeTableViewCell.swift // MVMCoreUI // // Created by Scott Pfeil on 4/18/19. // Copyright © 2019 Verizon Wireless. All rights reserved. // import UIKit @objcMembers open class MoleculeTableViewCell: UITableViewCell, MVMCoreUIMoleculeViewProtocol, MoleculeListCellProtocol { open var molecule: (UIView & MVMCoreUIMoleculeViewProtocol)? open var json: [AnyHashable: Any]? // In updateView, will set padding to default. open var updateViewHorizontalDefaults = true open var updateViewVerticalDefaults = true // For the accessory view convenience. public var caretView: CaretView? private var caretViewWidthSizeObject: MFSizeObject? private var caretViewHeightSizeObject: MFSizeObject? // For separation between cells. public var topSeparatorView: SeparatorView? public var bottomSeparatorView: SeparatorView? public enum SeparatorFrequency: String { case All = "all" case AllExceptTop = "allExceptTop" case AllExceptBottom = "allExceptBottom" case Between = "between" } // MARK: - Inits public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupView() } public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setupView() } // MARK: - MFViewProtocol public func updateView(_ size: CGFloat) { MFStyler.setDefaultMarginsFor(self, size: size, horizontal: updateViewHorizontalDefaults, vertical: updateViewVerticalDefaults) if #available(iOS 11.0, *) { if accessoryView != nil { // Smaller left margin if accessory view. var margin = directionalLayoutMargins margin.trailing = 16 contentView.directionalLayoutMargins = margin } else { contentView.directionalLayoutMargins = directionalLayoutMargins } topSeparatorView?.setLeftAndRightPinConstant(directionalLayoutMargins.leading) bottomSeparatorView?.setLeftAndRightPinConstant(directionalLayoutMargins.leading) } else { if accessoryView != nil { // Smaller left margin if accessory view. var margin = layoutMargins margin.right = 16 contentView.layoutMargins = margin } else { contentView.layoutMargins = layoutMargins } topSeparatorView?.setLeftAndRightPinConstant(layoutMargins.left) bottomSeparatorView?.setLeftAndRightPinConstant(layoutMargins.left) } molecule?.updateView(size) if let _ = accessoryView, 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)) } topSeparatorView?.updateView(size) bottomSeparatorView?.updateView(size) } public func setupView() { selectionStyle = .none if #available(iOS 11.0, *) { insetsLayoutMarginsFromSafeArea = false contentView.insetsLayoutMarginsFromSafeArea = false contentView.preservesSuperviewLayoutMargins = false } } // MARK: - MVMCoreUIMoleculeViewProtocol public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { self.json = json; if let useHorizontalMargins = json?.optionalBoolForKey("useHorizontalMargins") { updateViewHorizontalDefaults = useHorizontalMargins } if let useVerticalMargins = json?.optionalBoolForKey("useVerticalMargins") { updateViewVerticalDefaults = useVerticalMargins } if let backgroundColorString = json?.optionalStringForKey(KeyBackgroundColor) { backgroundColor = UIColor.mfGet(forHex: backgroundColorString) } // Add the caret if there is an action and it's not declared hidden. if let _ = json?.optionalDictionaryForKey("actionMap"), !json!.boolForKey("hideArrow") { addCaretViewAccessory() } else { accessoryView = nil } // override the separator if let separator = json?.optionalDictionaryForKey("separator") { addSeparatorsIfNeeded() bottomSeparatorView?.setWithJSON(separator, delegateObject: delegateObject, additionalData: additionalData) } guard let json = json, let moleculeJSON = json.optionalDictionaryForKey(KeyMolecule) else { return } if molecule == nil { if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: true) { contentView.addSubview(moleculeView) let standardConstraints = (moleculeView as? MVMCoreUIViewConstrainingProtocol)?.useStandardConstraints?() ?? true NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: moleculeView, useMargins: standardConstraints).values)) molecule = moleculeView } } else { molecule?.setWithJSON(moleculeJSON, delegateObject: delegateObject, additionalData: additionalData) } // This molecule will by default handle margins. if let castView = molecule as? MVMCoreUIViewConstrainingProtocol { castView.shouldSetHorizontalMargins?(false) castView.shouldSetVerticalMargins?(false) } } public func reset() { molecule?.reset?() updateViewVerticalDefaults = true updateViewHorizontalDefaults = true backgroundColor = .white } public static func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { guard let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule), let height = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: moleculeJSON)?.estimatedHeight?(forRow: moleculeJSON, delegateObject: delegateObject) else { return 80 } return max(2 * PaddingDefaultVerticalSpacing3, height) } public static func name(forReuse molecule: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { guard let molecule = molecule?.optionalDictionaryForKey(KeyMolecule) else { return nil } return MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: molecule)?.name?(forReuse: molecule, delegateObject: delegateObject) ?? molecule.optionalStringForKey(KeyMoleculeName) } public static func requiredModules(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { guard let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule), let theClass = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: moleculeJSON) else { return nil } return theClass.requiredModules?(moleculeJSON, delegateObject: delegateObject, error: error) } // MARK: - Arrow /// Adds the standard mvm style caret to the accessory view public func addCaretViewAccessory() { guard accessoryView == nil else { return } let width: CGFloat = 6 let height: CGFloat = 10 caretView = CaretView(lineThickness: CaretView.thin) caretView?.frame = CGRect(x: 0, y: 0, width: width, height: height) caretViewWidthSizeObject = MFSizeObject(standardSize: width, standardiPadPortraitSize: 9) caretViewHeightSizeObject = MFSizeObject(standardSize: height, standardiPadPortraitSize: 16) accessoryView = caretView } // MARK: - MoleculeListCellProtocol /// For when the separator between cells shows using json and frequency. Default is type: standard, frequency: allExceptTop. public func setSeparatorWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?, indexPath: IndexPath) { addSeparatorsIfNeeded() if let json = json { topSeparatorView?.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) bottomSeparatorView?.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) if let separatorFrequencyString = json.optionalStringForKey("frequency"), let separatorFrequency = SeparatorFrequency(rawValue: separatorFrequencyString) { setSeparatorFrequency(separatorFrequency, indexPath: indexPath) } } else { topSeparatorView?.hide() bottomSeparatorView?.setAsLight() setSeparatorFrequency(MoleculeTableViewCell.SeparatorFrequency.AllExceptTop, indexPath: indexPath) } } public func didSelectCell(atIndex indexPath: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { if let actionMap = json?.optionalDictionaryForKey("actionMap") { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) } } // MARK: - Separator func addSeparatorsIfNeeded() { if topSeparatorView == nil { topSeparatorView = SeparatorView.separatorAdd(to: self, position: SeparatorPositionTop) topSeparatorView?.hide() } if bottomSeparatorView == nil { bottomSeparatorView = SeparatorView.separatorAdd(to: self, position: SeparatorPositionBot) bottomSeparatorView?.hide() } } /// For when the separator between cells shows. public func setSeparatorFrequency(_ separatorFrequency: SeparatorFrequency, indexPath: IndexPath) { switch separatorFrequency { case .All: if indexPath.row == 0 { topSeparatorView?.show() } else { topSeparatorView?.hide() } bottomSeparatorView?.show() case .AllExceptBottom: topSeparatorView?.show() bottomSeparatorView?.hide() case .Between: if indexPath.row == 0 { topSeparatorView?.hide() } else { topSeparatorView?.show() } bottomSeparatorView?.hide() case .AllExceptTop: fallthrough default: topSeparatorView?.hide() bottomSeparatorView?.show() } } }