// // MoleculeStackView.swift // MVMCoreUI // // Created by Scott Pfeil on 2/11/19. // Copyright © 2019 Verizon Wireless. All rights reserved. // import UIKit public class MoleculeStackView: ViewConstrainingView { var spacingBlock: ((Any) -> UIEdgeInsets)? var moleculesArray: [UIView]? var useMargins: Bool = false var alignment: UIStackView.Alignment = .fill var contentView: UIView = MVMCoreUICommonViewsUtility.commonView() private var spacingBetweenItems: [NSLayoutConstraint]? /// For setting the direction of the stack var axis: NSLayoutConstraint.Axis = .vertical { didSet { if axis != oldValue { MVMCoreDispatchUtility.performBlock(onMainThread: { // remove constraints if self.axis == .vertical { // layout vertical } else { // layout horizontal } }) } } } /// For setting the alignment perpendicular to the direction of the stack. Default fill for vertical and center for horizontal. Can be overriden at the item level. var itemAlignment: UIStackView.Alignment = .fill { didSet { if itemAlignment != oldValue { MVMCoreDispatchUtility.performBlock(onMainThread: { // remove constraints if self.axis == .vertical { // layout vertical } else { // layout horizontal } }) } } } var spacing: Float = 16 { didSet { if spacing != oldValue { MVMCoreDispatchUtility.performBlock(onMainThread: { // loop space bettwen constraints and update. skip custom ones... }) } } } public func setAxisWithJSON(_ json: [AnyHashable: Any]?) { switch json?.optionalStringForKey("axis") { case "horizontal": axis = .horizontal default: axis = .vertical } } public func pinView(_ view: UIView, toView: UIView, attribute: NSLayoutConstraint.Attribute, relation: NSLayoutConstraint.Relation, priority: UILayoutPriority, constant: CGFloat) { let constraint = NSLayoutConstraint(item: view, attribute: attribute, relatedBy: relation, toItem: toView, attribute: attribute, multiplier: 1.0, constant: constant) constraint.priority = priority constraint.isActive = true } // MARK: - Inits public override init(frame: CGRect) { super.init(frame: frame) } public init(withJSON json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { super.init(frame: CGRect.zero) setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) } public convenience init(withJSON json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, spacingBlock: ((Any) -> UIEdgeInsets)?) { self.init(withJSON: json, delegateObject: delegateObject, additionalData: nil) self.spacingBlock = spacingBlock } public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - MFViewProtocol public override func setupView() { super.setupView() guard contentView.superview == nil else { return } translatesAutoresizingMaskIntoConstraints = false backgroundColor = .clear addConstrainedView(contentView) //self.setContentHuggingPriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) //self.setContentCompressionResistancePriority(UILayoutPriority.required, for: NSLayoutConstraint.Axis.vertical) } public override func updateView(_ size: CGFloat) { super.updateView(size) for view in subviews { if let mvmView = view as? MVMCoreViewProtocol { mvmView.updateView(size) } } } // MARK: - MVMCoreUIMoleculeViewProtocol open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) MVMCoreUIStackableViewController.remove(contentView.subviews) guard let molecules = json?.arrayForKey(KeyMolecules) as? [[String: Any]] else { return } // Sets the stack attributes setAxisWithJSON(json) spacing = json?["spacing"] as? Float ?? 16 // Set the alignment for the stack in the containing view. The json driven value is for the axis direction alignment. if axis == .vertical { alignHorizontal(.fill) alignVertical(ViewConstrainingView.getAlignmentFor(json?.optionalStringForKey("alignment"), defaultAlignment: .fill)) } else { alignHorizontal(ViewConstrainingView.getAlignmentFor(json?.optionalStringForKey("alignment"), defaultAlignment: .fill)) alignVertical(.leading) } // Create the molecules and set the json. var previousObject = contentView for (index, map) in molecules.enumerated() { if let moleculeJSON = map.optionalDictionaryForKey(KeyMolecule), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: true) { contentView.addSubview(molecule) molecule.translatesAutoresizingMaskIntoConstraints = false let spacing = CGFloat(map["spacing"] as? Float ?? self.spacing) let percent = map["percent"] as? Int let verticalAlignment = ViewConstrainingView.getAlignmentFor(json?.optionalStringForKey("verticalAlignment"), defaultAlignment: (percent == nil && axis == .vertical ? UIStackView.Alignment.fill : UIStackView.Alignment.leading)) let horizontalAlignment = ViewConstrainingView.getAlignmentFor(json?.optionalStringForKey("horizontalAlignment"), defaultAlignment: (axis == .vertical || percent == nil ? UIStackView.Alignment.fill : UIStackView.Alignment.leading)) if let molecule = molecule as? MVMCoreUIViewConstrainingProtocol { molecule.alignHorizontal?(horizontalAlignment) molecule.alignVertical?(verticalAlignment) } if axis == .vertical { if index == 0 { pinView(molecule, toView: previousObject, attribute: .top, relation: .equal, priority: .required, constant: spacing) } else { NSLayoutConstraint(pinFirstView: previousObject, toSecondView: molecule, withConstant: spacing, directionVertical: true)?.isActive = true } pinView(molecule, toView: contentView, attribute: .leading, relation: .equal, priority: .required, constant: 0) pinView(contentView, toView: molecule, attribute: .trailing, relation: .equal, priority: .required, constant: 0) if let percent = percent { molecule.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: CGFloat(percent)/100.0).isActive = true } } else { if index == 0 { pinView(molecule, toView: previousObject, attribute: .leading, relation: .equal, priority: .required, constant: spacing) } else { NSLayoutConstraint(pinFirstView: previousObject, toSecondView: molecule, withConstant: spacing, directionVertical: false)?.isActive = true } pinView(molecule, toView: contentView, attribute: .top, relation: .equal, priority: .required, constant: 0) pinView(contentView, toView: molecule, attribute: .bottom, relation: .equal, priority: .required, constant: 0) if let percent = percent { molecule.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: CGFloat(percent)/100.0).isActive = true } } previousObject = molecule } } if axis == .vertical { pinView(contentView, toView: previousObject, attribute: .bottom, relation: .equal, priority: .required, constant: 0) } else { pinView(contentView, toView: previousObject, attribute: .right, relation: .equal, priority: .required, constant: 0) } } }