281 lines
13 KiB
Swift
281 lines
13 KiB
Swift
//
|
|
// MoleculeStackView.swift
|
|
// MVMCoreUI
|
|
//
|
|
// Created by Scott Pfeil on 2/11/19.
|
|
// Copyright © 2019 Verizon Wireless. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
|
|
public class StackItem {
|
|
var view: UIView
|
|
var spacing: CGFloat?
|
|
var percentage: Int?
|
|
var verticalAlignment: UIStackView.Alignment?
|
|
var horizontalAlignment: UIStackView.Alignment?
|
|
|
|
init(with view: UIView) {
|
|
self.view = view
|
|
}
|
|
|
|
init(with view: UIView, json: [AnyHashable: Any]) {
|
|
self.view = view
|
|
spacing = json.optionalCGFloatForKey("spacing")
|
|
percentage = json["percent"] as? Int
|
|
if let alignment = json.optionalStringForKey("verticalAlignment") {
|
|
verticalAlignment = ViewConstrainingView.getAlignmentFor(alignment, defaultAlignment: .fill)
|
|
}
|
|
if let alignment = json.optionalStringForKey("horizontalAlignment") {
|
|
horizontalAlignment = ViewConstrainingView.getAlignmentFor(alignment, defaultAlignment: .fill)
|
|
}
|
|
}
|
|
}
|
|
|
|
public class MoleculeStackView: ViewConstrainingView {
|
|
var spacingBlock: ((Any) -> UIEdgeInsets)?
|
|
var useMargins: Bool = false
|
|
|
|
var contentView: UIView = MVMCoreUICommonViewsUtility.commonView()
|
|
var items: [StackItem] = []
|
|
|
|
private var spacingConstraints: [NSLayoutConstraint] = []
|
|
|
|
/// For setting the direction of the stack
|
|
var axis: NSLayoutConstraint.Axis = .vertical {
|
|
didSet {
|
|
if axis != oldValue {
|
|
restack()
|
|
}
|
|
}
|
|
}
|
|
|
|
var spacing: CGFloat = 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) -> NSLayoutConstraint {
|
|
let constraint = NSLayoutConstraint(item: view, attribute: attribute, relatedBy: relation, toItem: toView, attribute: attribute, multiplier: 1.0, constant: constant)
|
|
constraint.priority = priority
|
|
constraint.isActive = true
|
|
return constraint
|
|
}
|
|
|
|
// 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)
|
|
contentView.setContentHuggingPriority(.defaultHigh, for: .vertical)
|
|
contentView.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
|
}
|
|
|
|
public override func updateView(_ size: CGFloat) {
|
|
super.updateView(size)
|
|
for view in subviews {
|
|
if let mvmView = view as? MVMCoreViewProtocol {
|
|
mvmView.updateView(size)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - MVMCoreUIMoleculeViewProtocol
|
|
public override func reset() {
|
|
for item in items {
|
|
if let view = item.view as? MVMCoreUIMoleculeViewProtocol {
|
|
view.reset?()
|
|
}
|
|
}
|
|
}
|
|
|
|
open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
|
|
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
|
|
clear()
|
|
|
|
guard let molecules = json?.arrayForKey(KeyMolecules) as? [[String: Any]] else {
|
|
return
|
|
}
|
|
|
|
// Sets the stack attributes
|
|
setAxisWithJSON(json)
|
|
spacing = json?.optionalCGFloatForKey("spacing") ?? 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.
|
|
for (index, map) in molecules.enumerated() {
|
|
if let moleculeJSON = map.optionalDictionaryForKey(KeyMolecule), let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: true) {
|
|
|
|
addStackItem(StackItem(with: molecule, json: map), lastItem: index == molecules.count - 1)
|
|
/*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(map.optionalStringForKey("verticalAlignment"), defaultAlignment: (percent == nil && axis == .vertical ? UIStackView.Alignment.fill : UIStackView.Alignment.leading))
|
|
let horizontalAlignment = ViewConstrainingView.getAlignmentFor(map.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)
|
|
}*/
|
|
}
|
|
}
|
|
}
|
|
|
|
public override static func name(forReuse molecule: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? {
|
|
var name = ""
|
|
guard let molecules = molecule?.optionalArrayForKey(KeyMolecules) else {
|
|
return name
|
|
}
|
|
for case let item as [AnyHashable: AnyHashable] in molecules {
|
|
if let moleculeName = item.stringOptionalWithChainOfKeysOrIndexes([KeyMolecule, KeyMoleculeName]) {
|
|
name.append(moleculeName)
|
|
}
|
|
}
|
|
return name
|
|
}
|
|
|
|
// MARK: - Convenience Functions
|
|
func clear() {
|
|
MVMCoreUIStackableViewController.remove(contentView.subviews)
|
|
spacingConstraints = []
|
|
}
|
|
|
|
func restack() {
|
|
clear()
|
|
let stackItems = items
|
|
items.removeAll()
|
|
for (index, item) in stackItems.enumerated() {
|
|
addStackItem(item, lastItem: index == stackItems.count - 1)
|
|
}
|
|
}
|
|
|
|
/// Adds the view to the stack.
|
|
func addView(_ view: UIView, lastItem: Bool) {
|
|
addStackItem(StackItem(with: view), lastItem: lastItem)
|
|
}
|
|
|
|
/// Adds the stack item to the stack.
|
|
func addStackItem(_ stackItem: StackItem, lastItem: Bool) {
|
|
let view = stackItem.view
|
|
contentView.addSubview(view)
|
|
view.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
let spacing = stackItem.spacing ?? self.spacing
|
|
if let view = view as? MVMCoreUIViewConstrainingProtocol {
|
|
let verticalAlignment = stackItem.verticalAlignment ?? (stackItem.percentage == nil && axis == .vertical ? UIStackView.Alignment.fill : UIStackView.Alignment.leading)
|
|
let horizontalAlignment = stackItem.horizontalAlignment ?? view.alignment?() ?? (axis == .vertical || stackItem.percentage == nil ? UIStackView.Alignment.fill : UIStackView.Alignment.leading)
|
|
view.alignHorizontal?(horizontalAlignment)
|
|
view.alignVertical?(verticalAlignment)
|
|
}
|
|
if axis == .vertical {
|
|
if items.count == 0 {
|
|
spacingConstraints.append(pinView(view, toView: contentView, attribute: .top, relation: .equal, priority: .required, constant: spacing))
|
|
} else if let previousView = items.last?.view {
|
|
spacingConstraints.append(NSLayoutConstraint(pinFirstView: previousView, toSecondView: view, withConstant: spacing, directionVertical: true))
|
|
}
|
|
pinView(view, toView: contentView, attribute: .leading, relation: .equal, priority: .required, constant: 0)
|
|
pinView(contentView, toView: view, attribute: .trailing, relation: .equal, priority: .required, constant: 0)
|
|
if let percent = stackItem.percentage {
|
|
view.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: CGFloat(percent)/100.0).isActive = true
|
|
}
|
|
if lastItem {
|
|
pinView(contentView, toView: view, attribute: .bottom, relation: .equal, priority: .required, constant: 0)
|
|
}
|
|
} else {
|
|
if items.count == 0 {
|
|
spacingConstraints.append(pinView(view, toView: contentView, attribute: .leading, relation: .equal, priority: .required, constant: spacing))
|
|
} else if let previousView = items.last?.view {
|
|
spacingConstraints.append(NSLayoutConstraint(pinFirstView: previousView, toSecondView: view, withConstant: spacing, directionVertical: false))
|
|
}
|
|
pinView(view, toView: contentView, attribute: .top, relation: .equal, priority: .required, constant: 0)
|
|
pinView(contentView, toView: view, attribute: .bottom, relation: .equal, priority: .required, constant: 0)
|
|
if let percent = stackItem.percentage {
|
|
view.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: CGFloat(percent)/100.0).isActive = true
|
|
}
|
|
}
|
|
if lastItem {
|
|
pinView(contentView, toView: view, attribute: .right, relation: .equal, priority: .required, constant: 0)
|
|
}
|
|
items.append(stackItem)
|
|
}
|
|
}
|