diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 3e539a83..2864d545 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -100,6 +100,7 @@ D22D1F47220496A30077CEC0 /* MVMCoreUISwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = D22D1F45220496A30077CEC0 /* MVMCoreUISwitch.m */; }; D22D1F562204CE5D0077CEC0 /* MVMCoreUIStackableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = D22D1F542204CE5D0077CEC0 /* MVMCoreUIStackableViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; D22D1F572204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D22D1F552204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m */; }; + D243859923A16B1800332775 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = D243859823A16B1800332775 /* Container.swift */; }; D260D7B122D65BDD007E7233 /* MVMCoreUIPageControl.h in Headers */ = {isa = PBXBuildFile; fileRef = D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */; settings = {ATTRIBUTES = (Public, ); }; }; D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */ = {isa = PBXBuildFile; fileRef = D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */; }; D260D7B622D68514007E7233 /* MVMCoreUIPagingProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -251,6 +252,8 @@ D2E1FADB2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FADA2260D3D200AEFD8C /* MVMCoreUIDelegateObject.swift */; }; D2E1FADF2268B8E700AEFD8C /* ThreeLayerTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FADE2268B8E700AEFD8C /* ThreeLayerTableViewController.swift */; }; D2E1FAE12268E81D00AEFD8C /* MoleculeListTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E1FAE02268E81D00AEFD8C /* MoleculeListTemplate.swift */; }; + D2FB151B23A2B65B00C20E10 /* MoleculeContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FB151A23A2B65B00C20E10 /* MoleculeContainer.swift */; }; + D2FB151D23A40F1500C20E10 /* StackItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2FB151C23A40F1500C20E10 /* StackItem.swift */; }; DB06250B2293456500B72DD3 /* LeftRightLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB06250A2293456500B72DD3 /* LeftRightLabelView.swift */; }; DBC4391822442197001AB423 /* CaretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC4391622442196001AB423 /* CaretView.swift */; }; DBC4391922442197001AB423 /* DashLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC4391722442197001AB423 /* DashLine.swift */; }; @@ -353,6 +356,7 @@ D22D1F45220496A30077CEC0 /* MVMCoreUISwitch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUISwitch.m; sourceTree = ""; }; D22D1F542204CE5D0077CEC0 /* MVMCoreUIStackableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIStackableViewController.h; sourceTree = ""; }; D22D1F552204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIStackableViewController.m; sourceTree = ""; }; + D243859823A16B1800332775 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; D260D7AF22D65BDD007E7233 /* MVMCoreUIPageControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIPageControl.h; sourceTree = ""; }; D260D7B022D65BDD007E7233 /* MVMCoreUIPageControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreUIPageControl.m; sourceTree = ""; }; D260D7B522D68509007E7233 /* MVMCoreUIPagingProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreUIPagingProtocol.h; sourceTree = ""; }; @@ -508,6 +512,8 @@ D2E1FADE2268B8E700AEFD8C /* ThreeLayerTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreeLayerTableViewController.swift; sourceTree = ""; }; D2E1FAE02268E81D00AEFD8C /* MoleculeListTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeListTemplate.swift; sourceTree = ""; }; D2F4DDE52371A4CB00CD28BB /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + D2FB151A23A2B65B00C20E10 /* MoleculeContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeContainer.swift; sourceTree = ""; }; + D2FB151C23A40F1500C20E10 /* StackItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackItem.swift; sourceTree = ""; }; DB06250A2293456500B72DD3 /* LeftRightLabelView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LeftRightLabelView.swift; sourceTree = ""; }; DB891E822253FA8500022516 /* Label.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Label.swift; sourceTree = ""; }; DBC4391622442196001AB423 /* CaretView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaretView.swift; sourceTree = ""; }; @@ -724,6 +730,7 @@ D224799A231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift */, D27CD40D2322EEAF00C1DC07 /* TabsTableViewCell.swift */, D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */, + D2FB151C23A40F1500C20E10 /* StackItem.swift */, ); path = Items; sourceTree = ""; @@ -831,6 +838,7 @@ D2D6CD3F22E78C1A00D701B8 /* Scroller.swift */, 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */, 017BEB372360C6AC0024EF95 /* RadioButtonLabel.swift */, + D2FB151A23A2B65B00C20E10 /* MoleculeContainer.swift */, 017BEB47236230DB0024EF95 /* MoleculeViewProtocol.swift */, 017BEB49236235BA0024EF95 /* ModelMoleculeViewProtocol.swift */, ); @@ -879,6 +887,7 @@ D29DF2B721E7BE79003B2FB9 /* TabBarController */, D29DF2B621E7BE66003B2FB9 /* SplitViewController */, D2B18B93236214AD00A9AEDC /* NavigationController.swift */, + D243859823A16B1800332775 /* Container.swift */, ); path = Containers; sourceTree = ""; @@ -1300,6 +1309,7 @@ DBC4391922442197001AB423 /* DashLine.swift in Sources */, 0AA33B34239813C50067DD0F /* UIColor+Extension.swift in Sources */, 0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */, + D2FB151D23A40F1500C20E10 /* StackItem.swift in Sources */, D29DF29621E7ADB8003B2FB9 /* StackableViewController.m in Sources */, 0116A4E5228B19640094F3ED /* RadioButtonModel.swift in Sources */, 017BEB48236230DB0024EF95 /* MoleculeViewProtocol.swift in Sources */, @@ -1399,6 +1409,7 @@ D2A6390122CBB1820052ED1F /* Carousel.swift in Sources */, D29DF2C721E7BF57003B2FB9 /* MFTabBarInteractor.m in Sources */, D29DF29521E7ADB8003B2FB9 /* ProgrammaticScrollViewController.m in Sources */, + D2FB151B23A2B65B00C20E10 /* MoleculeContainer.swift in Sources */, D2A638FD22CA98280052ED1F /* HeadlineBody.swift in Sources */, 012CA9DE2388723E003F810F /* ListPageTemplateModel.swift in Sources */, D29DF16121E69996003B2FB9 /* MFViewController.m in Sources */, @@ -1416,6 +1427,7 @@ D27CD4102339057800C1DC07 /* EyebrowHeadlineBodyLink.swift in Sources */, D29DF11D21E684A9003B2FB9 /* MVMCoreUISplitViewController.m in Sources */, 0198F79F225679880066C936 /* FormValidationProtocol.swift in Sources */, + D243859923A16B1800332775 /* Container.swift in Sources */, D29DF29821E7ADB8003B2FB9 /* MFScrollingViewController.m in Sources */, D29770C821F7C4AE00B2F0D0 /* TopLabelsView.m in Sources */, 01EB369323609801006832FA /* HeaderModel.swift in Sources */, diff --git a/MVMCoreUI/Atoms/Buttons/CaretButton.swift b/MVMCoreUI/Atoms/Buttons/CaretButton.swift index b01d2758..6c9de088 100644 --- a/MVMCoreUI/Atoms/Buttons/CaretButton.swift +++ b/MVMCoreUI/Atoms/Buttons/CaretButton.swift @@ -136,7 +136,7 @@ open class CaretButton: MFCustomButton, MVMCoreUIMoleculeViewProtocol, MVMCoreUI return true } - open func alignment() -> UIStackView.Alignment { + open func horizontalAlignment() -> UIStackView.Alignment { return UIStackView.Alignment.leading; } diff --git a/MVMCoreUI/Atoms/Buttons/MFTextButton.m b/MVMCoreUI/Atoms/Buttons/MFTextButton.m index e1a6c504..a0ee6ca7 100644 --- a/MVMCoreUI/Atoms/Buttons/MFTextButton.m +++ b/MVMCoreUI/Atoms/Buttons/MFTextButton.m @@ -156,7 +156,7 @@ return YES; } -- (UIStackViewAlignment)alignment { +- (UIStackViewAlignment)horizontalAlignment { return UIStackViewAlignmentLeading; } diff --git a/MVMCoreUI/Atoms/Buttons/PrimaryButton.m b/MVMCoreUI/Atoms/Buttons/PrimaryButton.m index 3b1802e5..2bc9e026 100644 --- a/MVMCoreUI/Atoms/Buttons/PrimaryButton.m +++ b/MVMCoreUI/Atoms/Buttons/PrimaryButton.m @@ -664,6 +664,8 @@ } - (void)setAsMolecule { + self.translatesAutoresizingMaskIntoConstraints = false; + [self pinHeight]; [self setAsStandardCustom]; } @@ -713,6 +715,10 @@ [self setWithActionMap:json delegateObject:delegateObject additionalData:additionalData]; } +- (UIStackViewAlignment)horizontalAlignment { + return UIStackViewAlignmentCenter; +} + #pragma mark - Handling Validations - (void)setEnabledByValidity { diff --git a/MVMCoreUI/Atoms/Views/CaretView.swift b/MVMCoreUI/Atoms/Views/CaretView.swift index 3a2c5561..fb9689e5 100644 --- a/MVMCoreUI/Atoms/Views/CaretView.swift +++ b/MVMCoreUI/Atoms/Views/CaretView.swift @@ -154,7 +154,7 @@ extension CaretView: MVMCoreUIViewConstrainingProtocol { return true } - open func alignment() -> UIStackView.Alignment { + open func horizontalAlignment() -> UIStackView.Alignment { return UIStackView.Alignment.leading; } } diff --git a/MVMCoreUI/Atoms/Views/Checkbox.swift b/MVMCoreUI/Atoms/Views/Checkbox.swift index 370f5826..a206953a 100644 --- a/MVMCoreUI/Atoms/Views/Checkbox.swift +++ b/MVMCoreUI/Atoms/Views/Checkbox.swift @@ -11,11 +11,13 @@ import MVMCore /** This class expects its height and width to be equal. */ -@objcMembers open class Checkbox: UIControl, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol { +@objcMembers open class Checkbox: Control, MVMCoreUIViewConstrainingProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- + public var sizeObject: MFSizeObject? = MFSizeObject(standardSize: Checkbox.defaultHeightWidth, standardiPadPortraitSize: Checkbox.defaultHeightWidth + 6.0) + // Form Validation var isRequired = false var fieldKey: String? @@ -117,8 +119,8 @@ import MVMCore // MARK: - Constraints //-------------------------------------------------- - private var heightConstraint: NSLayoutConstraint? - private var widthConstraint: NSLayoutConstraint? + public var heightConstraint: NSLayoutConstraint? + public var widthConstraint: NSLayoutConstraint? /// Updates the height and width anchors of the Checkbox with the assigned value. public var heigthWidthConstant: CGFloat = Checkbox.defaultHeightWidth { @@ -138,7 +140,6 @@ import MVMCore accessibilityTraits = .button accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "checkbox_action_hint") updateAccessibilityLabel() - setupView() } /// There is currently no intention on using xib files. @@ -147,7 +148,7 @@ import MVMCore fatalError("xib file is not implemented for Checkbox.") } - public convenience init() { + public convenience override init() { self.init(frame:.zero) } @@ -180,7 +181,8 @@ import MVMCore layer.borderColor = borderColor.cgColor } - open func setupView() { + open override func setupView() { + super.setupView() guard constraints.isEmpty else { return } @@ -349,18 +351,7 @@ import MVMCore open override func touchesEnded(_ touches: Set, with event: UIEvent?) { - sendActions(for: touchIsAcceptablyOutside(touches.first) ? .touchUpOutside : .touchUpInside) - } - - func touchIsAcceptablyOutside(_ touch: UITouch?) -> Bool { - let endLocation = touch?.location(in: self) - let x = endLocation?.x ?? 0.0 - let y = endLocation?.y ?? 0.0 - let faultTolerance: CGFloat = 20.0 - let widthLimit = CGFloat(bounds.size.width + faultTolerance) - let heightLimt = CGFloat(bounds.size.height + faultTolerance) - - return x < -faultTolerance || y < -faultTolerance || x > widthLimit || y > heightLimt + sendActions(for: .touchUpInside) } override open func accessibilityActivate() -> Bool { @@ -376,7 +367,8 @@ import MVMCore return true } - open func reset() { + open override func reset() { + super.reset() isEnabled(true) shapeLayer?.removeAllAnimations() @@ -396,17 +388,24 @@ import MVMCore setupView() } - public func updateView(_ size: CGFloat) { + public override func updateView(_ size: CGFloat) { + super.updateView(size) + + if let dimension = sizeObject?.getValueBased(onSize: size) { + widthConstraint?.constant = dimension + heightConstraint?.constant = dimension + } layoutIfNeeded() } - public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + public override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) self.delegateObject = delegateObject FormValidator.setupValidation(molecule: self, delegate: delegateObject?.formValidationProtocol) guard let dictionary = json else { return } - + groupName = dictionary.optionalStringForKey("groupName") fieldValue = dictionary.optionalStringForKey("value") if let fieldKey = dictionary[KeyFieldKey] as? String { @@ -463,7 +462,7 @@ import MVMCore // MARK:- FormValidationProtocol extension Checkbox: FormValidationFormFieldProtocol { - + public func formFieldGroupName() -> String? { return groupName } diff --git a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift index f148863b..6991adef 100644 --- a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift +++ b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift @@ -7,7 +7,7 @@ // -@objcMembers open class CheckboxWithLabelView: ViewConstrainingView { +@objcMembers open class CheckboxWithLabelView: View { //-------------------------------------------------- // MARK: - Outlets //-------------------------------------------------- @@ -19,9 +19,7 @@ // MARK: - Properties //-------------------------------------------------- - var sizeObject: MFSizeObject? = MFSizeObject(standardSize: Checkbox.defaultHeightWidth, standardiPadPortraitSize: Checkbox.defaultHeightWidth + 6.0) - - var checkboxPosition: CheckboxPosition = .center + public var checkboxPosition: CheckboxPosition = .center public enum CheckboxPosition: String { case center @@ -32,13 +30,10 @@ //-------------------------------------------------- // MARK: - Constraints //-------------------------------------------------- - - var checkboxWidthConstraint: NSLayoutConstraint? - var checkboxHeightConstraint: NSLayoutConstraint? - var checkboxTopConstraint: NSLayoutConstraint? - var checkboxBottomConstraint: NSLayoutConstraint? - var checkboxCenterYConstraint: NSLayoutConstraint? - var centerLabelCheckboxConstraint: NSLayoutConstraint? + + public var checkboxTopConstraint: NSLayoutConstraint? + public var checkboxBottomConstraint: NSLayoutConstraint? + public var checkboxCenterYConstraint: NSLayoutConstraint? //-------------------------------------------------- // MARK: - Life Cycle @@ -49,32 +44,20 @@ guard subviews.isEmpty else { return } - translatesAutoresizingMaskIntoConstraints = false - addSubview(checkbox) addSubview(label) label.text = "" - let dimension = sizeObject?.getValueBasedOnApplicationWidth() ?? Checkbox.defaultHeightWidth - checkboxWidthConstraint = checkbox.heightAnchor.constraint(equalToConstant: dimension) - checkboxWidthConstraint?.isActive = true - checkboxHeightConstraint = checkbox.widthAnchor.constraint(equalToConstant: dimension) - checkboxHeightConstraint?.isActive = true - checkbox.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true - checkbox.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor).isActive = true - layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: checkbox.bottomAnchor).isActive = true - let checboxBottom = checkbox.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor) - checboxBottom.priority = UILayoutPriority(249) - checboxBottom.isActive = true - - // Allows various positions of checkbox. checkboxBottomConstraint = layoutMarginsGuide.bottomAnchor.constraint(equalTo: checkbox.bottomAnchor) + checkboxBottomConstraint?.isActive = true + checkboxTopConstraint = checkbox.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor) + checkboxTopConstraint?.isActive = true + checkboxCenterYConstraint = checkbox.centerYAnchor.constraint(equalTo: centerYAnchor) - centerLabelCheckboxConstraint = label.centerYAnchor.constraint(equalTo: checkbox.centerYAnchor) label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true layoutMarginsGuide.trailingAnchor.constraint(equalTo: label.trailingAnchor).isActive = true @@ -82,7 +65,7 @@ layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: label.bottomAnchor).isActive = true let bottomLabelConstraint = layoutMarginsGuide.bottomAnchor.constraint(equalTo: label.bottomAnchor) - bottomLabelConstraint.priority = UILayoutPriority(249) + bottomLabelConstraint.priority = .defaultLow bottomLabelConstraint.isActive = true alignCheckbox(.center) @@ -92,17 +75,12 @@ // MARK: - Initializers //-------------------------------------------------- - required public init?(coder: NSCoder) { - super.init(coder: coder) - fatalError("xib file is not implemented for CheckboxWithLabelView") - } - - override public init(frame: CGRect) { + public override init(frame: CGRect) { super.init(frame: frame) setupView() } - public convenience init() { + public convenience override init() { self.init(frame: .zero) } @@ -112,6 +90,11 @@ alignCheckbox(position) } + required public init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("xib file is not implemented for CheckboxWithLabelView") + } + //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- @@ -125,64 +108,41 @@ checkboxBottomConstraint?.isActive = false checkboxTopConstraint?.isActive = false checkboxCenterYConstraint?.isActive = true - centerLabelCheckboxConstraint?.isActive = true case .top: checkboxBottomConstraint?.isActive = false checkboxTopConstraint?.isActive = true checkboxCenterYConstraint?.isActive = false - centerLabelCheckboxConstraint?.isActive = false case .bottom: checkboxBottomConstraint?.isActive = true checkboxTopConstraint?.isActive = false checkboxCenterYConstraint?.isActive = false - centerLabelCheckboxConstraint?.isActive = false } } } -/// MARK: - Molecular +// MARK: - Molecular extension CheckboxWithLabelView { - override open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - return CGFloat(Checkbox.defaultHeightWidth) + open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return 200 } @objc override open func updateView(_ size: CGFloat) { super.updateView(size) label.updateView(size) - - if self.checkbox.responds(to: #selector(self.updateView(_:))) { - if let dimension = sizeObject?.getValueBased(onSize: size) { - checkboxWidthConstraint?.constant = dimension - checkboxHeightConstraint?.constant = dimension - checkbox.updateView(size) - } - } - + checkbox.updateView(size) layoutIfNeeded() } - override open func alignment() -> UIStackView.Alignment { - return .leading - } - - open override func resetConstraints() { - super.resetConstraints() - - checkboxCenterYConstraint?.isActive = false - checkboxBottomConstraint?.isActive = false - checkboxTopConstraint?.isActive = false - centerLabelCheckboxConstraint?.isActive = false - } - open override func reset() { super.reset() label.text = "" checkbox.reset() + alignCheckbox(.center) } override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { diff --git a/MVMCoreUI/Atoms/Views/Label.swift b/MVMCoreUI/Atoms/Views/Label.swift index ca112b9a..b37e74fb 100644 --- a/MVMCoreUI/Atoms/Views/Label.swift +++ b/MVMCoreUI/Atoms/Views/Label.swift @@ -705,7 +705,7 @@ extension Label { return true } - public func alignment() -> UIStackView.Alignment { + public func horizontalAlignment() -> UIStackView.Alignment { return .leading } diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.m b/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.m index 8ec71241..33874a8d 100644 --- a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.m +++ b/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.m @@ -193,7 +193,7 @@ const CGFloat SwitchShakeIntensity = 2; return YES; } -- (UIStackViewAlignment)alignment { +- (UIStackViewAlignment)horizontalAlignment { return UIStackViewAlignmentTrailing; } diff --git a/MVMCoreUI/Atoms/Views/ProgressBar.swift b/MVMCoreUI/Atoms/Views/ProgressBar.swift index b640e4c7..90d63dfb 100644 --- a/MVMCoreUI/Atoms/Views/ProgressBar.swift +++ b/MVMCoreUI/Atoms/Views/ProgressBar.swift @@ -74,8 +74,9 @@ import Foundation if let thickness = json?.optionalCGFloatForKey("thickness") { self.thickness = thickness } - if let percentage = json?["percent"] as? Float { - progress = percentage/100.0 + // as? Float returns nil, apple defect. + if let percentage = json?["percent"] as? CGFloat { + progress = Float(percentage/100.0) } if let progressColor = json?.optionalStringForKey("progressColor") { progressTintColor = UIColor.mfGet(forHex: progressColor) diff --git a/MVMCoreUI/BaseClasses/Control.swift b/MVMCoreUI/BaseClasses/Control.swift index 942cda65..e425a29b 100644 --- a/MVMCoreUI/BaseClasses/Control.swift +++ b/MVMCoreUI/BaseClasses/Control.swift @@ -8,28 +8,41 @@ import UIKit -public class Control: UIControl, ModelMoleculeViewProtocol { - var json: [AnyHashable: Any]? - public var model: MoleculeProtocol? +@objcMembers open class Control: UIControl, ModelMoleculeViewProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public var json: [AnyHashable: Any]? + public var model: MoleculeProtocol? + private var initialSetupPerformed = false + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + public override init(frame: CGRect) { super.init(frame: .zero) initialSetup() } - init() { + public init() { super.init(frame: .zero) initialSetup() } public required init?(coder: NSCoder) { super.init(coder: coder) - initialSetup() + fatalError("Control does not support xib.") } + //-------------------------------------------------- + // MARK: - Setup + //-------------------------------------------------- + public func initialSetup() { + if !initialSetupPerformed { initialSetupPerformed = true setupView() @@ -39,11 +52,25 @@ public class Control: UIControl, ModelMoleculeViewProtocol { public func setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [String : AnyHashable]?) { self.model = model } + + //-------------------------------------------------- + // MARK: - UITouch + //-------------------------------------------------- + + override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + + // If the control is smaller than 44pt by width or height, this will compensate. + let faultToleranceX: CGFloat = max((MinimumTappableArea - bounds.size.width) / 2.0, 0) + let faultToleranceY: CGFloat = max((MinimumTappableArea - bounds.size.height) / 2.0, 0) + let area = bounds.insetBy(dx: -faultToleranceX, dy: -faultToleranceY) + return area.contains(point) + } } +// MARK: - MVMCoreViewProtocol extension Control: MVMCoreViewProtocol { - public func updateView(_ size: CGFloat) { - } + + public func updateView(_ size: CGFloat) {} /// Will be called only once. public func setupView() { @@ -52,8 +79,9 @@ extension Control: MVMCoreViewProtocol { } } +// MARK: - MVMCoreUIMoleculeViewProtocol extension Control: MVMCoreUIMoleculeViewProtocol { - public func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { self.json = json if let backgroundColorString = json?.optionalStringForKey(KeyBackgroundColor) { diff --git a/MVMCoreUI/Containers/Container.swift b/MVMCoreUI/Containers/Container.swift new file mode 100644 index 00000000..e2b4e723 --- /dev/null +++ b/MVMCoreUI/Containers/Container.swift @@ -0,0 +1,239 @@ +// +// Container.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 12/11/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +public protocol ContainerModelProtocol { + var horizontalAlignment: UIStackView.Alignment? { get set } + var verticalAlignment: UIStackView.Alignment? { get set } + var useHorizontalMargins: Bool? { get set } + var useVerticalMargins: Bool? { get set } +} + +public class ContainerHelper: NSObject { + var leftConstraint: NSLayoutConstraint? + var topConstraint: NSLayoutConstraint? + var bottomConstraint: NSLayoutConstraint? + var rightConstraint: NSLayoutConstraint? + + var alignCenterHorizontalConstraint: NSLayoutConstraint? + var alignCenterLeftConstraint: NSLayoutConstraint? + var alignCenterRightConstraint: NSLayoutConstraint? + + var alignCenterVerticalConstraint: NSLayoutConstraint? + var alignCenterTopConstraint: NSLayoutConstraint? + var alignCenterBottomConstraint: NSLayoutConstraint? + + var leftLowConstraint: NSLayoutConstraint? + var topLowConstraint: NSLayoutConstraint? + var bottomLowConstraint: NSLayoutConstraint? + var rightLowConstraint: NSLayoutConstraint? + + func constrainView(_ view: UIView) { + guard let margins = view.superview?.layoutMarginsGuide else { return } + leftConstraint = view.leftAnchor.constraint(equalTo: margins.leftAnchor) + leftConstraint?.isActive = true + + topConstraint = view.topAnchor.constraint(equalTo: margins.topAnchor) + topConstraint?.isActive = true + + rightConstraint = margins.rightAnchor.constraint(equalTo: view.rightAnchor) + rightConstraint?.isActive = true + + bottomConstraint = margins.bottomAnchor.constraint(equalTo: view.bottomAnchor) + bottomConstraint?.isActive = true + + alignCenterHorizontalConstraint = view.centerXAnchor.constraint(equalTo: margins.centerXAnchor) + alignCenterLeftConstraint = view.leftAnchor.constraint(greaterThanOrEqualTo: margins.leftAnchor) + alignCenterRightConstraint = margins.rightAnchor.constraint(greaterThanOrEqualTo: view.rightAnchor) + + alignCenterVerticalConstraint = view.centerYAnchor.constraint(equalTo: margins.centerYAnchor) + alignCenterTopConstraint = view.topAnchor.constraint(greaterThanOrEqualTo: margins.topAnchor) + alignCenterBottomConstraint = margins.bottomAnchor.constraint(greaterThanOrEqualTo: view.bottomAnchor) + + leftLowConstraint = view.leftAnchor.constraint(equalTo: margins.leftAnchor) + leftLowConstraint?.priority = UILayoutPriority(rawValue: 200) + leftLowConstraint?.isActive = true + + topLowConstraint = view.topAnchor.constraint(equalTo: margins.topAnchor) + topLowConstraint?.priority = UILayoutPriority(rawValue: 200) + topLowConstraint?.isActive = true + + rightLowConstraint = margins.rightAnchor.constraint(equalTo: view.rightAnchor) + rightLowConstraint?.priority = UILayoutPriority(rawValue: 200) + rightLowConstraint?.isActive = true + + bottomLowConstraint = margins.bottomAnchor.constraint(equalTo: view.bottomAnchor) + bottomLowConstraint?.priority = UILayoutPriority(rawValue: 200) + bottomLowConstraint?.isActive = true + + setAccessibility(view) + } + + func setAccessibility(_ view: UIView) { + guard let superView = view.superview else { return } + superView.isAccessibilityElement = false + if let elements = view.accessibilityElements { + superView.accessibilityElements = elements + } else { + superView.accessibilityElements = [view] + } + } + + func alignHorizontal(_ alignment: UIStackView.Alignment) { + switch alignment { + case .center: + alignCenterHorizontalConstraint?.isActive = true + alignCenterLeftConstraint?.isActive = true + alignCenterRightConstraint?.isActive = true + leftConstraint?.isActive = false + rightConstraint?.isActive = false + case .leading: + alignCenterHorizontalConstraint?.isActive = false + alignCenterLeftConstraint?.isActive = false + alignCenterRightConstraint?.isActive = true + leftConstraint?.isActive = true + rightConstraint?.isActive = false + case .trailing: + alignCenterHorizontalConstraint?.isActive = false + alignCenterLeftConstraint?.isActive = true + alignCenterRightConstraint?.isActive = false + leftConstraint?.isActive = false + rightConstraint?.isActive = true + case .fill: + alignCenterHorizontalConstraint?.isActive = false + alignCenterLeftConstraint?.isActive = false + alignCenterRightConstraint?.isActive = false + leftConstraint?.isActive = true + rightConstraint?.isActive = true + default: break + } + } + + func alignVertical(_ alignment: UIStackView.Alignment) { + switch alignment { + case .center: + alignCenterVerticalConstraint?.isActive = true + alignCenterTopConstraint?.isActive = true + alignCenterBottomConstraint?.isActive = true + topConstraint?.isActive = false + bottomConstraint?.isActive = false + case .leading: + alignCenterVerticalConstraint?.isActive = false + alignCenterTopConstraint?.isActive = false + alignCenterBottomConstraint?.isActive = true + topConstraint?.isActive = true + bottomConstraint?.isActive = false + case .trailing: + alignCenterVerticalConstraint?.isActive = false + alignCenterTopConstraint?.isActive = true + alignCenterBottomConstraint?.isActive = false + topConstraint?.isActive = false + bottomConstraint?.isActive = true + case .fill: + alignCenterVerticalConstraint?.isActive = false + alignCenterTopConstraint?.isActive = false + alignCenterBottomConstraint?.isActive = false + topConstraint?.isActive = true + bottomConstraint?.isActive = true + default: break + } + } + + func set(with model: ContainerModelProtocol) { + if let horizontalAlignment = model.horizontalAlignment { + alignHorizontal(horizontalAlignment) + } + if let verticalAlignment = model.verticalAlignment { + alignVertical(verticalAlignment) + } + } + + static func getAlignment(for string: String) -> UIStackView.Alignment? { + switch string { + case "leading": + return .leading + case "trailing": + return .trailing + case "center": + return .center + case "fill": + return .fill + default: + return nil + } + } + + func set(with JSON: [AnyHashable: Any]?, for contained: UIView) { + if let horizontalAlignmentString = JSON?.optionalStringForKey("horizontalAlignment"), let alignment = ContainerHelper.getAlignment(for: horizontalAlignmentString) ?? (contained as? MVMCoreUIViewConstrainingProtocol)?.horizontalAlignment?() { + alignHorizontal(alignment) + } else if let alignment = (contained as? MVMCoreUIViewConstrainingProtocol)?.horizontalAlignment?() { + alignHorizontal(alignment) + } + + if let verticalAlignmentString = JSON?.optionalStringForKey("verticalAlignment"), let alignment = ContainerHelper.getAlignment(for: verticalAlignmentString) ?? (contained as? MVMCoreUIViewConstrainingProtocol)?.verticalAlignment?() { + alignVertical(alignment) + } else if let alignment = (contained as? MVMCoreUIViewConstrainingProtocol)?.verticalAlignment?() { + alignVertical(alignment) + } + } +} + +open class Container: View { + var containerModel: ContainerModelProtocol? + var view: UIView? + let containerHelper = ContainerHelper() + + var topMarginPadding: CGFloat = 0 + var bottomMarginPadding: CGFloat = 0 +} + +// MARK: - MVMCoreViewProtocol +public extension Container { + override func updateView(_ size: CGFloat) { + super.updateView(size) + (view as? MVMCoreViewProtocol)?.updateView(size) + MFStyler.setMarginsFor(self, size: size, defaultHorizontal: containerModel?.useHorizontalMargins ?? true, top: containerModel?.useHorizontalMargins ?? true ? topMarginPadding : 0, bottom: containerModel?.useHorizontalMargins ?? true ? bottomMarginPadding : 0) + } + + /// Will be called only once. + override func setupView() { + super.setupView() + backgroundColor = .clear + } + + func addAndContain(_ view: UIView) { + view.translatesAutoresizingMaskIntoConstraints = false + addSubview(view) + containerHelper.constrainView(view) + self.view = view + } + + convenience init(andContain view: UIView) { + self.init() + addAndContain(view) + } +} + +// MARK: - MVMCoreUIMoleculeViewProtocol +public extension Container { + override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + guard let view = view else { return } + containerHelper.set(with: json, for: view) + } + + override func reset() { + super.reset() + (view as? MVMCoreUIMoleculeViewProtocol)?.reset?() + } + + override func setAsMolecule() { + (view as? MVMCoreUIMoleculeViewProtocol)?.setAsMolecule?() + } +} diff --git a/MVMCoreUI/Legacy/Views/MVMCoreUICheckBox.m b/MVMCoreUI/Legacy/Views/MVMCoreUICheckBox.m index 54358ebe..40fa987f 100644 --- a/MVMCoreUI/Legacy/Views/MVMCoreUICheckBox.m +++ b/MVMCoreUI/Legacy/Views/MVMCoreUICheckBox.m @@ -54,7 +54,7 @@ static const CGFloat CheckBoxHeightWidth = 18.0; return YES; } -- (UIStackViewAlignment)alignment { +- (UIStackViewAlignment)horizontalAlignment { return UIStackViewAlignmentLeading; } diff --git a/MVMCoreUI/Models/Molecules/MoleculeStackItemModel.swift b/MVMCoreUI/Models/Molecules/MoleculeStackItemModel.swift index fe532654..70402061 100644 --- a/MVMCoreUI/Models/Molecules/MoleculeStackItemModel.swift +++ b/MVMCoreUI/Models/Molecules/MoleculeStackItemModel.swift @@ -18,16 +18,14 @@ import Foundation public var percentage: Int? public var verticalAlignment: String? public var horizontalAlignment: String? - public var gone: Bool? = false + public var gone: Bool? + public var useHorizontalMargins: Bool? + public var useVerticalMargins: Bool? - public init(molecule: MoleculeProtocol?, spacing: CGFloat?, percentage: Int?, verticalAlignment: String?, horizontalAlignment: String?, gone: Bool = false) { + public init(molecule: MoleculeProtocol) { self.molecule = molecule - self.spacing = spacing - self.percentage = percentage - self.verticalAlignment = verticalAlignment - self.horizontalAlignment = horizontalAlignment - self.gone = gone } + enum CodingKeys: String, CodingKey { case moleculeName case molecule @@ -37,6 +35,8 @@ import Foundation case verticalAlignment case horizontalAlignment case gone + case useHorizontalMargins + case useVerticalMargins } required public init(from decoder: Decoder) throws { @@ -49,6 +49,8 @@ import Foundation self.verticalAlignment = try typeContainer.decodeIfPresent(String.self, forKey: .verticalAlignment) self.horizontalAlignment = try typeContainer.decodeIfPresent(String.self, forKey: .horizontalAlignment) self.gone = try typeContainer.decodeIfPresent(Bool.self, forKey: .gone) + self.useHorizontalMargins = try typeContainer.decodeIfPresent(Bool.self, forKey: .useHorizontalMargins) + self.useVerticalMargins = try typeContainer.decodeIfPresent(Bool.self, forKey: .useVerticalMargins) } public func encode(to encoder: Encoder) throws { @@ -60,5 +62,7 @@ import Foundation try container.encodeIfPresent(verticalAlignment, forKey: .verticalAlignment) try container.encodeIfPresent(horizontalAlignment, forKey: .horizontalAlignment) try container.encodeIfPresent(gone, forKey: .gone) + try container.encodeIfPresent(useHorizontalMargins, forKey: .useHorizontalMargins) + try container.encodeIfPresent(useVerticalMargins, forKey: .useVerticalMargins) } } diff --git a/MVMCoreUI/Molecules/Items/MoleculeCollectionViewCell.swift b/MVMCoreUI/Molecules/Items/MoleculeCollectionViewCell.swift index 00b8c1ee..d532147c 100644 --- a/MVMCoreUI/Molecules/Items/MoleculeCollectionViewCell.swift +++ b/MVMCoreUI/Molecules/Items/MoleculeCollectionViewCell.swift @@ -12,7 +12,8 @@ open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeVi open var molecule: (UIView & MVMCoreUIMoleculeViewProtocol)? open var json: [AnyHashable: Any]? - + public let containerHelper = ContainerHelper() + // In updateView, will set padding to default. open var updateViewHorizontalDefaults = true open var updateViewVerticalDefaults = true @@ -93,19 +94,17 @@ open class MoleculeCollectionViewCell: UICollectionViewCell, MVMCoreUIMoleculeVi if molecule == nil { if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(collectionModel.molecule, delegateObject, true) { contentView.insertSubview(moleculeView, at: 0) - NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: moleculeView, useMargins: true).values)) + containerHelper.constrainView(moleculeView) molecule = moleculeView } } else { (molecule as? ModelMoleculeViewProtocol)?.setWithModel(collectionModel.molecule, delegateObject, additionalData) } - // This molecule will handle spacing by default. - if let castView = molecule as? MVMCoreUIViewConstrainingProtocol { - castView.shouldSetHorizontalMargins?(false) - castView.shouldSetVerticalMargins?(false) - } - accessibilityElements = molecule?.subviews + guard let molecule = molecule else { return } + containerHelper.set(with: json, for: molecule) + + accessibilityElements = molecule.subviews } public func reset() { diff --git a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift index f3674562..96841596 100644 --- a/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/MoleculeTableViewCell.swift @@ -9,7 +9,8 @@ import UIKit @objcMembers open class MoleculeTableViewCell: TableViewCell { - + + // MARK: - MVMCoreUIMoleculeViewProtocol public override func setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [String : AnyHashable]?) { super.setWithModel(model, delegateObject, additionalData) diff --git a/MVMCoreUI/Molecules/Items/StackItem.swift b/MVMCoreUI/Molecules/Items/StackItem.swift new file mode 100644 index 00000000..bc79428e --- /dev/null +++ b/MVMCoreUI/Molecules/Items/StackItem.swift @@ -0,0 +1,54 @@ +// +// StackItem.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 12/13/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +open class StackItemView: MoleculeContainer { +} + +open class StackItemModel: ContainerModelProtocol { + public var view: StackItemView + public var spacing: CGFloat? + public var percentage: Int? + public var verticalAlignment: UIStackView.Alignment? + public var horizontalAlignment: UIStackView.Alignment? + public var useHorizontalMargins: Bool? = false + public var useVerticalMargins: Bool? = false + public var gone = false + + init(with view: StackItemView) { + self.view = view + view.containerModel = self + } + + init(with view: StackItemView, stackItemModel: MoleculeStackItemModel?) { + self.view = view + update(with: stackItemModel) + } + + func update(with stackItemModel: MoleculeStackItemModel?) { + gone = stackItemModel?.gone ?? false + spacing = stackItemModel?.spacing + percentage = stackItemModel?.percentage + + if let alignment = stackItemModel?.verticalAlignment { + verticalAlignment = ContainerHelper.getAlignment(for: alignment) + } else { + verticalAlignment = nil + } + if let alignment = stackItemModel?.horizontalAlignment { + horizontalAlignment = ContainerHelper.getAlignment(for: alignment) + } else { + horizontalAlignment = nil + } + useHorizontalMargins = stackItemModel?.useHorizontalMargins ?? false + useVerticalMargins = stackItemModel?.useVerticalMargins ?? false + } +} + + diff --git a/MVMCoreUI/Molecules/Items/TableViewCell.swift b/MVMCoreUI/Molecules/Items/TableViewCell.swift index 216f95aa..60af3f68 100644 --- a/MVMCoreUI/Molecules/Items/TableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/TableViewCell.swift @@ -11,9 +11,10 @@ import UIKit @objcMembers open class TableViewCell: UITableViewCell, MVMCoreUIMoleculeViewProtocol, MoleculeListCellProtocol, ModelMoleculeViewProtocol { open var molecule: (UIView & MVMCoreUIMoleculeViewProtocol)? + open var json: [AnyHashable: Any]? open var listItemModel: ListItemModel? - open var json: [String: Any]? - + public let containerHelper = ContainerHelper() + // In updateView, will set padding to default. open var updateViewHorizontalDefaults = true @@ -50,6 +51,8 @@ import UIKit styleStandard() case "header": styleHeader() + case "sectionFooter": + styleFooter() case "none": styleNone() default: break @@ -70,6 +73,13 @@ import UIKit bottomSeparatorView?.style = .thin } + open func styleFooter() { + topMarginPadding = 24 + bottomMarginPadding = 0 + topSeparatorView?.style = .none + bottomSeparatorView?.style = .none + } + open func styleNone() { topMarginPadding = 0 bottomMarginPadding = 0 @@ -80,14 +90,7 @@ import UIKit /// Adds the molecule to the view. open func addMolecule(_ molecule: UIView & MVMCoreUIMoleculeViewProtocol) { contentView.addSubview(molecule) - let standardConstraints = (molecule as? MVMCoreUIViewConstrainingProtocol)?.useStandardConstraints?() ?? true - NSLayoutConstraint.activate(Array(NSLayoutConstraint.pinView(toSuperview: molecule, useMargins: standardConstraints).values)) - - // This molecule will by default handle margins. - if let castView = molecule as? MVMCoreUIViewConstrainingProtocol { - castView.shouldSetHorizontalMargins?(false) - castView.shouldSetVerticalMargins?(false) - } + containerHelper.constrainView(molecule) self.molecule = molecule } @@ -178,14 +181,9 @@ import UIKit addSeparatorsIfNeeded() bottomSeparatorView?.setWithModel(separator, nil, nil) } - - (molecule as? ModelMoleculeViewProtocol)?.setWithModel(model.molecule, delegateObject, additionalData) - - // This molecule will by default handle margins. - if let castView = molecule as? MVMCoreUIViewConstrainingProtocol { - castView.shouldSetHorizontalMargins?(false) - castView.shouldSetVerticalMargins?(false) - } + + guard let molecule = molecule else { return } + containerHelper.set(with: json, for: molecule) } public func reset() { @@ -241,11 +239,13 @@ import UIKit var queue = [UIView]() - for view in views { + // 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) } diff --git a/MVMCoreUI/Molecules/ModuleMolecule.swift b/MVMCoreUI/Molecules/ModuleMolecule.swift index 2087dff5..b05585c4 100644 --- a/MVMCoreUI/Molecules/ModuleMolecule.swift +++ b/MVMCoreUI/Molecules/ModuleMolecule.swift @@ -8,13 +8,20 @@ import UIKit +//struct ModuleMoleculeModel: ContainerModelProtocol { +// var horizontalAlignment: UIStackView.Alignment? = .fill +// var verticalAlignment: UIStackView.Alignment? = .fill +// var useHorizontalMargins: Bool? = false +// var useVerticalMargins: Bool? = false +//} + open class ModuleMolecule: ViewConstrainingView, ModelMoleculeViewProtocol { open var moduleMolecule: (UIView & MVMCoreUIMoleculeViewProtocol & ModelMoleculeViewProtocol)? - open override func updateView(_ size: CGFloat) { - super.updateView(size) - moduleMolecule?.updateView(size) + public override func setupView() { + super.setupView() + //containerModel = ModuleMoleculeModel() } public func setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [String: AnyHashable]?) { @@ -24,7 +31,7 @@ open class ModuleMolecule: ViewConstrainingView, ModelMoleculeViewProtocol { guard let moduleMoleculeModel = model as? ModuleMoleculeModel, let moduleModel = delegateObject?.moduleProtocol?.getModuleWithName(moduleMoleculeModel.moduleName) as? MoleculeProtocol else { - // Critical error + // Critical error return } @@ -45,29 +52,17 @@ open class ModuleMolecule: ViewConstrainingView, ModelMoleculeViewProtocol { moduleMolecule?.setWithModel(model, delegateObject, additionalData) } } - - open override func setAsMolecule() { - super.setAsMolecule() - moduleMolecule?.setAsMolecule?() - } - - open override func reset() { - super.reset() - moduleMolecule?.reset?() - } - + public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - guard let moduleName = json?.optionalStringForKey("moduleName"), - let module = delegateObject?.moleculeDelegate?.getModuleWithName(moduleName) else { + guard let moduleName = json?.optionalStringForKey("moduleName"), let module = delegateObject?.moleculeDelegate?.getModuleWithName(moduleName) else { // Critical error return 0 } return MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: module)?.estimatedHeight?(forRow: module, delegateObject: delegateObject) ?? 0 } - + public override class func name(forReuse molecule: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { - guard let moduleName = molecule?.optionalStringForKey("moduleName"), - let module = delegateObject?.moleculeDelegate?.getModuleWithName(moduleName) else { + guard let moduleName = molecule?.optionalStringForKey("moduleName"), let module = delegateObject?.moleculeDelegate?.getModuleWithName(moduleName) else { // Critical error return "moduleMolecule<>" } @@ -87,25 +82,4 @@ open class ModuleMolecule: ViewConstrainingView, ModelMoleculeViewProtocol { } return nil } - - // MARK: - MVMCoreUIViewConstrainingProtocol - open override func useStandardConstraints() -> Bool { - return (moduleMolecule as? MVMCoreUIViewConstrainingProtocol)?.useStandardConstraints?() ?? true - } - - open override func alignHorizontal(_ alignment: UIStackView.Alignment) { - (moduleMolecule as? MVMCoreUIViewConstrainingProtocol)?.alignHorizontal?(alignment) - } - - open override func alignVertical(_ alignment: UIStackView.Alignment) { - (moduleMolecule as? MVMCoreUIViewConstrainingProtocol)?.alignVertical?(alignment) - } - - open override func shouldSetHorizontalMargins(_ shouldSet: Bool) { - (moduleMolecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetHorizontalMargins?(shouldSet) - } - - open override func shouldSetVerticalMargins(_ shouldSet: Bool) { - (moduleMolecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetVerticalMargins?(shouldSet) - } } diff --git a/MVMCoreUI/Molecules/MoleculeContainer.swift b/MVMCoreUI/Molecules/MoleculeContainer.swift new file mode 100644 index 00000000..56ae491d --- /dev/null +++ b/MVMCoreUI/Molecules/MoleculeContainer.swift @@ -0,0 +1,27 @@ +// +// MoleculeContainer.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 12/12/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import UIKit + +open class MoleculeContainer: Container { + + override public func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + guard let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule) else { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + return + } + if view == nil { + if let molecule = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(forJSON: moleculeJSON, delegateObject: delegateObject, constrainIfNeeded: false) { + addAndContain(molecule) + } + } else { + (view as? MVMCoreUIMoleculeViewProtocol)?.setWithJSON?(moleculeJSON, delegateObject: delegateObject, additionalData: additionalData) + } + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + } +} diff --git a/MVMCoreUI/Molecules/StandardFooterView.swift b/MVMCoreUI/Molecules/StandardFooterView.swift index 7c9c9282..a83611cc 100644 --- a/MVMCoreUI/Molecules/StandardFooterView.swift +++ b/MVMCoreUI/Molecules/StandardFooterView.swift @@ -8,28 +8,23 @@ import UIKit -open class StandardFooterView: ViewConstrainingView { +open class StandardFooterView: MoleculeContainer { open override func setupView() { super.setupView() topMarginPadding = PaddingDefaultVerticalSpacing bottomMarginPadding = PaddingDefaultVerticalSpacing - shouldSetupMoleculeFromJSON = true - updateViewVerticalDefaults = true - updateViewHorizontalDefaults = true } - open override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - - // This molecule will by default handle margins. - (molecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetHorizontalMargins?(false) - (molecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetVerticalMargins?(false) - } - - public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { if let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule), let height = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: moleculeJSON)?.estimatedHeight?(forRow: moleculeJSON, delegateObject: delegateObject) { return height + PaddingDefaultVerticalSpacing + PaddingDefaultVerticalSpacing } return 42 } + + open override func reset() { + super.reset() + topMarginPadding = PaddingDefaultVerticalSpacing + bottomMarginPadding = PaddingDefaultVerticalSpacing + } } diff --git a/MVMCoreUI/Molecules/StandardHeaderView.swift b/MVMCoreUI/Molecules/StandardHeaderView.swift index 71880d69..bbf6e44e 100644 --- a/MVMCoreUI/Molecules/StandardHeaderView.swift +++ b/MVMCoreUI/Molecules/StandardHeaderView.swift @@ -8,7 +8,7 @@ import UIKit -public class StandardHeaderView: ViewConstrainingView, ModelMoleculeViewProtocol { +public class StandardHeaderView: MoleculeContainer { var line: Line? // MARK: - MVMCoreViewProtocol @@ -19,9 +19,6 @@ public class StandardHeaderView: ViewConstrainingView, ModelMoleculeViewProtocol public override func setupView() { super.setupView() - shouldSetupMoleculeFromJSON = true - updateViewVerticalDefaults = true - updateViewHorizontalDefaults = true topMarginPadding = PaddingDefaultVerticalSpacing bottomMarginPadding = PaddingDefaultVerticalSpacing @@ -38,25 +35,20 @@ public class StandardHeaderView: ViewConstrainingView, ModelMoleculeViewProtocol // MARK: - MVMCoreUIMoleculeViewProtocol open override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - - // This molecule will by default handle margins. - (molecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetHorizontalMargins?(false) - (molecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetVerticalMargins?(false) - if let separatorJSON = json?.optionalDictionaryForKey("separator") { line?.setWithJSON(separatorJSON, delegateObject: delegateObject, additionalData: additionalData) } } - open func setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [String : AnyHashable]?) { + open override func setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [String : AnyHashable]?) { #warning("This below call should be repaced with super.setWithModel once we get rid of ViewConstrainingView.") //TODO: This below call should be repaced with super.setWithModel once we get rid of ViewConstrainingView. - setUpWithModel(model, delegateObject, additionalData) - - // This molecule will by default handle margins. - (molecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetHorizontalMargins?(false) - (molecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetVerticalMargins?(false) +// setUpWithModel(model, delegateObject, additionalData) +// +// // This molecule will by default handle margins. +// (molecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetHorizontalMargins?(false) +// (molecule as? MVMCoreUIViewConstrainingProtocol)?.shouldSetVerticalMargins?(false) guard let headerModel = model as? HeaderModel else { return @@ -66,15 +58,16 @@ public class StandardHeaderView: ViewConstrainingView, ModelMoleculeViewProtocol line?.setWithJSON(seperatorModel.toJSON(), delegateObject: delegateObject, additionalData: additionalData) } } + open override func reset() { super.reset() + line?.style = .heavy topMarginPadding = PaddingDefaultVerticalSpacing bottomMarginPadding = PaddingDefaultVerticalSpacing - line?.style = .heavy } - public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { if let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule), let height = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: moleculeJSON)?.estimatedHeight?(forRow: moleculeJSON, delegateObject: delegateObject) { return height + PaddingDefaultVerticalSpacing + PaddingDefaultVerticalSpacing } diff --git a/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift b/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift index c9f2299f..0cd5a235 100644 --- a/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift +++ b/MVMCoreUI/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift @@ -22,15 +22,14 @@ import UIKit return } stack.spacing = 0 - stack.updateViewHorizontalDefaults = false addSubview(stack) pinView(toSuperView: stack) - stack.addStackItem(StackItem(with: eyebrow), lastItem: false) - stack.addStackItem(StackItem(with: headline), lastItem: false) - stack.addStackItem(StackItem(with: body), lastItem: false) + stack.addStackItem(StackItemModel(with: StackItemView(andContain: eyebrow)), lastItem: false) + stack.addStackItem(StackItemModel(with: StackItemView(andContain: headline)), lastItem: false) + stack.addStackItem(StackItemModel(with: StackItemView(andContain: body)), lastItem: false) // To visually take into account the extra padding in the intrinsic content of a button. - let stackItem = StackItem(with: link) + let stackItem = StackItemModel(with: StackItemView(andContain: link)) stackItem.spacing = -6 stack.addStackItem(stackItem, lastItem: true) } @@ -58,7 +57,6 @@ import UIKit super.reset() stack.reset() stack.spacing = 0 - stack.updateViewHorizontalDefaults = false eyebrow.styleB3(true) headline.styleB1(true) body.styleB2(true) diff --git a/MVMCoreUI/Organisms/MoleculeStackView.swift b/MVMCoreUI/Organisms/MoleculeStackView.swift index dc5db34f..30de32a8 100644 --- a/MVMCoreUI/Organisms/MoleculeStackView.swift +++ b/MVMCoreUI/Organisms/MoleculeStackView.swift @@ -8,49 +8,14 @@ import UIKit -public class StackItem { - var view: UIView - var spacing: CGFloat? - var percentage: Int? - var verticalAlignment: UIStackView.Alignment? - var horizontalAlignment: UIStackView.Alignment? - var gone = false - - init(with view: UIView) { - self.view = view - } - - init(with view: UIView, stackItemModel: MoleculeStackItemModel?) { - self.view = view - update(with: stackItemModel) - } - - func update(with stackItemModel: MoleculeStackItemModel?) { - gone = stackItemModel?.gone ?? false - spacing = stackItemModel?.spacing - percentage = stackItemModel?.percentage - - if let alignment = stackItemModel?.verticalAlignment { - verticalAlignment = ViewConstrainingView.getAlignmentFor(alignment, defaultAlignment: .fill) - } else { - verticalAlignment = nil - } - if let alignment = stackItemModel?.horizontalAlignment { - horizontalAlignment = ViewConstrainingView.getAlignmentFor(alignment, defaultAlignment: .fill) - } else { - horizontalAlignment = nil - } - } -} - -public class MoleculeStackView: ViewConstrainingView, ModelMoleculeViewProtocol { +open class MoleculeStackView: Container { var contentView: UIView = MVMCoreUICommonViewsUtility.commonView() - var stackItems: [StackItem] = [] + var stackItems: [StackItemModel] = [] var useStackSpacingBeforeFirstItem = false var moleculeStackModel: MoleculeStackModel? - private var moleculesShouldSetHorizontalMargins = false - private var moleculesShouldSetVerticalMargins = false + var moleculesShouldSetHorizontalMargins = false + var moleculesShouldSetVerticalMargins = false /// For setting the direction of the stack var axis: NSLayoutConstraint.Axis = .vertical { @@ -99,6 +64,10 @@ public class MoleculeStackView: ViewConstrainingView, ModelMoleculeViewProtocol } // MARK: - Inits + public override init() { + super.init() + } + public override init(frame: CGRect) { super.init(frame: frame) } @@ -119,19 +88,20 @@ public class MoleculeStackView: ViewConstrainingView, ModelMoleculeViewProtocol return } MVMCoreUIUtility.setMarginsFor(contentView, leading: 0, top: 0, trailing: 0, bottom: 0) - updateViewHorizontalDefaults = true translatesAutoresizingMaskIntoConstraints = false backgroundColor = .clear addSubview(contentView) - pinView(toSuperView: contentView) + containerHelper.constrainView(contentView) contentView.setContentHuggingPriority(.defaultHigh, for: .vertical) contentView.setContentHuggingPriority(.defaultHigh, for: .horizontal) } public override func updateView(_ size: CGFloat) { super.updateView(size) + directionalLayoutMargins.leading = 0 + directionalLayoutMargins.trailing = 0 for item in stackItems { - (item.view as? MVMCoreViewProtocol)?.updateView(size) + item.view.updateView(size) } } @@ -139,16 +109,13 @@ public class MoleculeStackView: ViewConstrainingView, ModelMoleculeViewProtocol public override func reset() { super.reset() backgroundColor = .clear - updateViewHorizontalDefaults = true for item in stackItems { - if let view = item.view as? MVMCoreUIMoleculeViewProtocol { - view.reset?() - } + item.view.reset() } } //TODO: Model, Change to model - public func setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [String : AnyHashable]?) { + public override func setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [String : AnyHashable]?) { guard let model = model as? MoleculeStackModel else { return } @@ -157,11 +124,11 @@ public class MoleculeStackView: ViewConstrainingView, ModelMoleculeViewProtocol #warning("This below call should be repaced with super.setWithModel once we get rid of ViewConstrainingView.") //TODO: This below call should be repaced with super.setWithModel once we get rid of ViewConstrainingView. - setUpWithModel(model, delegateObject, additionalData) + //setUpWithModel(model, delegateObject, additionalData) removeAllItemViews() // If the items in the stack are the same, just update previous items instead of re-allocating. - var items: [StackItem]? + var items: [StackItemModel]? if let previousModel = previousModel { let previoudReuseName = MoleculeStackView.name(forReuse: previousModel, delegateObject: delegateObject) @@ -185,22 +152,25 @@ public class MoleculeStackView: ViewConstrainingView, ModelMoleculeViewProtocol if let moleculeModel = stackItemModel.molecule { var view: UIView? if let item = items?[index] { - item.update(with: stackItemModel) + item.update(with: stackItemModel) view = item.view (view as? ModelMoleculeViewProtocol)?.setWithModel(moleculeModel, delegateObject, nil) addStackItem(item, lastItem: index == molecules.count - 1) - } else if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(moleculeModel, delegateObject, true) { + } else if let moleculeView = MVMCoreUIMoleculeMappingObject.shared()?.createMolecule(moleculeModel, delegateObject, true) as? StackItemView { view = moleculeView - addStackItem(StackItem(with: moleculeView, stackItemModel: stackItemModel), lastItem: index == molecules.count - 1) + addStackItem(StackItemModel(with: moleculeView, stackItemModel: stackItemModel), lastItem: index == molecules.count - 1) } (view as? MVMCoreUIViewConstrainingProtocol)?.shouldSetHorizontalMargins?(moleculesShouldSetHorizontalMargins) (view as? MVMCoreUIViewConstrainingProtocol)?.shouldSetVerticalMargins?(moleculesShouldSetVerticalMargins) } + + stackItemModel.useHorizontalMargins = moleculesShouldSetHorizontalMargins + stackItemModel.useVerticalMargins = moleculesShouldSetVerticalMargins } } - public override class func name(forReuse molecule: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { + public class func name(forReuse molecule: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> String? { // This will aggregate names of molecules to make an id. guard let molecules = molecule?.optionalArrayForKey(KeyMolecules) else { return "stack<>" @@ -215,7 +185,7 @@ public class MoleculeStackView: ViewConstrainingView, ModelMoleculeViewProtocol return name } - public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + public class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { guard let items = json?.optionalArrayForKey(KeyMolecules) else { return 0 } @@ -237,7 +207,7 @@ public class MoleculeStackView: ViewConstrainingView, ModelMoleculeViewProtocol return estimatedHeight } - public override class func requiredModules(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { + public class func requiredModules(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { guard let items = json?.optionalArrayForKey(KeyMolecules) else { return nil } @@ -253,11 +223,11 @@ public class MoleculeStackView: ViewConstrainingView, ModelMoleculeViewProtocol // MARK: - Adding to stack /// Adds the view to the stack. func addView(_ view: UIView, lastItem: Bool) { - addStackItem(StackItem(with: view), lastItem: lastItem) + addStackItem(StackItemModel(with: StackItemView(andContain: view)), lastItem: lastItem) } /// Adds the stack item to the stack. - func addStackItem(_ stackItem: StackItem, lastItem: Bool) { + func addStackItem(_ stackItem: StackItemModel, lastItem: Bool) { guard !stackItem.gone else { stackItems.append(stackItem) return @@ -311,10 +281,10 @@ public class MoleculeStackView: ViewConstrainingView, ModelMoleculeViewProtocol stackItems.append(stackItem) } - func setWithStackItems(_ items: [StackItem]) { + func setWithStackItems(_ items: [StackItemModel]) { removeAllItemViews() - self.stackItems.removeAll() - var previousPresentItem: StackItem? = nil + stackItems.removeAll() + var previousPresentItem: StackItemModel? = nil for item in items { if !item.gone { previousPresentItem = item diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index a7b8e586..378332b6 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -28,7 +28,7 @@ mapping = [@{ @"label": Label.class, @"line": Line.class, - @"button": ButtonView.class, + @"button": PrimaryButton.class, @"textButton": MFTextButton.class, @"header": StandardHeaderView.class, @"moleculeStack": MoleculeStackView.class, diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIViewConstrainingProtocol.h b/MVMCoreUI/OtherHandlers/MVMCoreUIViewConstrainingProtocol.h index afea0ef6..a2ac2340 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIViewConstrainingProtocol.h +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIViewConstrainingProtocol.h @@ -16,7 +16,13 @@ - (BOOL)needsToBeConstrained; /// The alignment if constrained. -- (UIStackViewAlignment)alignment; +- (UIStackViewAlignment)alignment __deprecated; + +/// The alignment if constrained. +- (UIStackViewAlignment)horizontalAlignment; + +/// The alignment if constrained. +- (UIStackViewAlignment)verticalAlignment; /// Can be used to override any standard constraints that may be added. - (BOOL)useStandardConstraints; diff --git a/MVMCoreUI/Templates/MoleculeStackTemplate.swift b/MVMCoreUI/Templates/MoleculeStackTemplate.swift index dacbd2cb..9ee96d04 100644 --- a/MVMCoreUI/Templates/MoleculeStackTemplate.swift +++ b/MVMCoreUI/Templates/MoleculeStackTemplate.swift @@ -61,6 +61,7 @@ open class MoleculeStackTemplate: ThreeLayerViewController, TemplateProtocol { let stack = MoleculeStackView(frame: .zero) stack.useStackSpacingBeforeFirstItem = true + stack.moleculesShouldSetHorizontalMargins = true stack.setWithModel(moleculeStackModel, delegateObject() as? MVMCoreUIDelegateObject, nil) return stack } diff --git a/MVMCoreUI/Utility/MVMCoreUIConstants.h b/MVMCoreUI/Utility/MVMCoreUIConstants.h index 778ede4f..e5587745 100644 --- a/MVMCoreUI/Utility/MVMCoreUIConstants.h +++ b/MVMCoreUI/Utility/MVMCoreUIConstants.h @@ -77,3 +77,7 @@ typedef NS_ENUM(NSInteger, CoreUIErrorCode) { ErrorCodeModuleMolecule = 100, ErrorCodeListMolecule = 101 }; + +#pragma mark - Apple Design Guidelines + +extern CGFloat const MinimumTappableArea; diff --git a/MVMCoreUI/Utility/MVMCoreUIConstants.m b/MVMCoreUI/Utility/MVMCoreUIConstants.m index 26636266..6211cfee 100644 --- a/MVMCoreUI/Utility/MVMCoreUIConstants.m +++ b/MVMCoreUI/Utility/MVMCoreUIConstants.m @@ -69,3 +69,7 @@ BOOL DisableAnimations = NO; // Hand Scroll Key NSString * const KeyHandScrollAnimation = @"handScrollAnimation"; NSString * const KeyHandScroll = @"hand_scroll"; + +#pragma mark - Apple Design Guidelines + +CGFloat const MinimumTappableArea = 44.0f;