Refactored UI drawing to new class. Reflected change.

This commit is contained in:
Kevin G Christiano 2019-11-12 14:29:37 -05:00
parent 30a56923c0
commit 48bcd87bc1
7 changed files with 311 additions and 283 deletions

View File

@ -45,6 +45,7 @@
0A6BF4722360C56C0028F841 /* DropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6BF4712360C56C0028F841 /* DropdownEntryField.swift */; };
0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */; };
0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; };
0ABD136B237B193A0081388D /* FormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136A237B193A0081388D /* FormView.swift */; };
943784F5236B77BB006A1E82 /* GraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F3236B77BB006A1E82 /* GraphView.swift */; };
943784F6236B77BB006A1E82 /* GraphViewAnimationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */; };
9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */; };
@ -237,6 +238,7 @@
0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = "<group>"; };
0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = "<group>"; };
0A8321AE2355FE9500CB7F00 /* DigitBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitBox.swift; sourceTree = "<group>"; };
0ABD136A237B193A0081388D /* FormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormView.swift; sourceTree = "<group>"; };
943784F3236B77BB006A1E82 /* GraphView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphView.swift; sourceTree = "<group>"; };
943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphViewAnimationHandler.swift; sourceTree = "<group>"; };
9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MVMAnimationFramework.framework; path = ../SharedFrameworks/MVMAnimationFramework.framework; sourceTree = "<group>"; };
@ -441,6 +443,14 @@
path = FormUIHelpers;
sourceTree = "<group>";
};
0ABD1369237B18EE0081388D /* views */ = {
isa = PBXGroup;
children = (
0ABD136A237B193A0081388D /* FormView.swift */,
);
path = views;
sourceTree = "<group>";
};
D224798823142BF2003FCCF9 /* SwitchMolecules */ = {
isa = PBXGroup;
children = (
@ -645,6 +655,7 @@
D29DF11921E68467003B2FB9 /* Containers */ = {
isa = PBXGroup;
children = (
0ABD1369237B18EE0081388D /* views */,
D29DF2B721E7BE79003B2FB9 /* TabBarController */,
D29DF2B621E7BE66003B2FB9 /* SplitViewController */,
D206997521FB8A0B00CAE0DE /* MVMCoreUINavigationController.h */,
@ -1171,6 +1182,7 @@
0105618E224BBE7700E1557D /* FormValidator+TextFields.swift in Sources */,
0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */,
D29DF2BE21E7BEA4003B2FB9 /* TopTabbar.m in Sources */,
0ABD136B237B193A0081388D /* FormView.swift in Sources */,
D2A514632213643100345BFB /* MoleculeStackCenteredTemplate.swift in Sources */,
D29DF32421ED0DA2003B2FB9 /* TextButtonView.m in Sources */,
D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */,

View File

@ -13,28 +13,19 @@ import UIKit
}
@objcMembers open class DigitBox: UITextField, MVMCoreViewProtocol {
@objcMembers open class DigitBox: FormView, UITextFieldDelegate {
//--------------------------------------------------
// MARK: - Outlets
//--------------------------------------------------
let digitField = UITextField(frame: .zero)
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public var bottomBar: CAShapeLayer = {
let layer = CAShapeLayer()
layer.backgroundColor = UIColor.black.cgColor
layer.drawsAsynchronously = true
layer.anchorPoint = CGPoint(x: 0.5, y: 1.0);
return layer
}()
private var previousSize: CGFloat = 0.0
/// Determines if a border should be drawn.
var hideBorder = false
var showError = false
var borderStrokeColor: UIColor = .mfSilver()
private var borderPath: UIBezierPath = UIBezierPath()
//--------------------------------------------------
// MARK: - Delegate
//--------------------------------------------------
@ -76,8 +67,9 @@ import UIKit
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .white
textAlignment = .center
keyboardType = .numberPad
digitField.delegate = self
digitField.textAlignment = .center
digitField.keyboardType = .numberPad
showErrorState(false)
widthConstraint = widthAnchor.constraint(equalToConstant: 39)
@ -90,30 +82,6 @@ import UIKit
updateView(MVMCoreUISplitViewController.getDetailViewWidth())
}
open override func draw(_ rect: CGRect) {
super.draw(rect)
borderPath.removeAllPoints()
if !hideBorder {
// Brings the other half of the line inside the view to prevent cropping.
let origin = frame.origin
let size = frame.size
let insetLean: CGFloat = 0.5
borderPath.lineWidth = 1
borderPath.move(to: CGPoint(x: origin.x + insetLean, y: origin.y + size.height))
borderPath.addLine(to: CGPoint(x: origin.x + insetLean, y: origin.y + insetLean))
borderPath.addLine(to: CGPoint(x: origin.x + size.width - insetLean, y: origin.y + insetLean))
borderPath.addLine(to: CGPoint(x: origin.x + size.width - insetLean, y: origin.y + size.height))
borderStrokeColor.setStroke()
borderPath.stroke()
}
layoutIfNeeded()
}
open override func layoutSubviews() {
super.layoutSubviews()
@ -125,40 +93,44 @@ import UIKit
// MARK: - Methods
//--------------------------------------------------
override open func deleteBackward() {
super.deleteBackward()
textBoxDelegate?.textFieldDidDelete?(self)
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string.isBackspace {
textBoxDelegate?.textFieldDidDelete?(self.digitField)
}
return true
}
public func updateView(_ size: CGFloat) {
public override func updateView(_ size: CGFloat) {
super.updateView(size)
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
if !MVMCoreGetterUtility.fequal(a: Float(size), b: Float(self.previousSize)) {
MFStyler.styleTextField(self)
self.font = MFFonts.mfFont55Rg(28)
MFStyler.styleTextField(self.digitField)
self.digitField.font = MFFonts.mfFont55Rg(28)
var digitWidth: CGFloat = 0
var digitHeight: CGFloat = 0
var width: CGFloat = 0
var height: CGFloat = 0
let sizeObject = MFSizeObject(standardBlock: {
digitWidth = 39
digitHeight = 44
width = 39
height = 44
}, smalliPhone: {
digitWidth = 35
digitHeight = 38
width = 35
height = 38
}, standardiPadPortraitBlock: {
digitWidth = 59
digitHeight = 74
width = 59
height = 74
})
sizeObject?.performBlockBase(onSize: size)
self.widthConstraint?.constant = digitWidth
self.heightConstraint?.constant = digitHeight
self.widthConstraint?.constant = width
self.heightConstraint?.constant = height
self.previousSize = size
}
@ -178,3 +150,12 @@ import UIKit
layoutIfNeeded()
}
}
// TODO: Move if working properly.
extension String {
var isBackspace: Bool {
let char = self.cString(using: String.Encoding.utf8)!
return strcmp(char, "\\b") == -92
}
}

View File

@ -23,12 +23,14 @@ import UIKit
public override var isEnabled: Bool {
didSet {
titleLabel.textColor = isEnabled ? .black : .mfBattleshipGrey()
titleLabel.textColor = self.isEnabled ? .mfBattleshipGrey() : .mfSilver()
for digitBox in digitFields {
digitBox.isUserInteractionEnabled = isEnabled
digitBox.isEnabled = isEnabled
digitBox.textColor = isEnabled ? .black : .mfBattleshipGrey()
digitFields.forEach {
$0.isEnabled = self.isEnabled
$0.isUserInteractionEnabled = isEnabled
$0.digitField.isEnabled = isEnabled
$0.digitField.textColor = isEnabled ? .black : .mfBattleshipGrey()
}
}
}
@ -37,7 +39,7 @@ import UIKit
get {
var string = ""
digitFields.forEach { string += $0.attributedPlaceholder?.string ?? "" }
digitFields.forEach { string += $0.digitField.attributedPlaceholder?.string ?? "" }
return !string.isEmpty ? string : nil
}
@ -47,7 +49,7 @@ import UIKit
for (index, field) in digitFields.enumerated() {
if index < newValue.count {
let indexChar = newValue.index(newValue.startIndex, offsetBy: index)
field.attributedPlaceholder = NSAttributedString(string: String(newValue[indexChar]), attributes: [
field.digitField.attributedPlaceholder = NSAttributedString(string: String(newValue[indexChar]), attributes: [
NSAttributedString.Key.foregroundColor: UIColor.mfBattleshipGrey()])
}
}
@ -59,12 +61,6 @@ import UIKit
feedback = ""
}
// if let feedback = feedback, !feedback.isEmpty {
// feedbackContainerDistance?.constant = 10
// } else {
// feedbackContainerDistance?.constant = 0
// }
/*
* adding missing accessibilityLabel value
* if we have some value in accessibilityLabel,
@ -79,7 +75,7 @@ import UIKit
get {
var string = ""
digitFields.forEach { string += $0.text ?? "" }
digitFields.forEach { string += $0.digitField.text ?? "" }
return string
}
@ -89,7 +85,7 @@ import UIKit
for (index, field) in digitFields.enumerated() {
if index < newValue.count {
let indexChar = newValue.index(newValue.startIndex, offsetBy: index)
field.text = String(newValue[indexChar])
field.digitField.text = String(newValue[indexChar])
}
}
@ -97,46 +93,6 @@ import UIKit
}
}
/// Updates the visual appearance of the container, with some logical laterations as well.
public override func updateUI(appearance: Appearance) {
self.appearance = appearance
isUserInteractionEnabled = true
titleLabel.textColor = .mfBattleshipGrey()
feedback = showError ? errorMessage : nil
switch appearance {
case .original:
digitFields.forEach {
$0.borderStrokeColor = .mfSilver()
$0.bottomBar.backgroundColor = UIColor.black.cgColor
}
case .error:
digitFields.forEach {
$0.borderStrokeColor = .mfPumpkin()
$0.bottomBar.backgroundColor = UIColor.mfPumpkin().cgColor
$0.showErrorState(true)
}
case .lock:
digitFields.forEach {
$0.isUserInteractionEnabled = false
$0.bottomBar.backgroundColor = UIColor.clear.cgColor
}
case .select:
digitFields.forEach {
$0.borderStrokeColor = .black
$0.bottomBar.backgroundColor = UIColor.black.cgColor
}
case .disable:
digitFields.forEach {
$0.isUserInteractionEnabled = false
$0.borderStrokeColor = .mfSilver()
$0.bottomBar.backgroundColor = self.isEnabled ? UIColor.black.cgColor : UIColor.mfSilver().cgColor
}
titleLabel.textColor = self.isEnabled ? .mfBattleshipGrey() : .mfSilver()
}
}
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
@ -171,13 +127,10 @@ import UIKit
public override func setupFieldContainerContent(_ container: UIView) {
// textField.removeFromSuperview()
alignCenterHorizontal()
isAccessibilityElement = false
hideBorder = true
bottomBar.backgroundColor = UIColor.clear.cgColor
bottomBar.frame = CGRect(x: 0, y: fieldContainer.bounds.height, width: 0, height: 0)
entryContainer.bottomBar.backgroundColor = UIColor.clear.cgColor
entryContainer.bottomBar.frame = CGRect(x: 0, y: entryContainer.bounds.height, width: 0, height: 0)
setupDigitFieldsView(size: MVMCoreUISplitViewController.getDetailViewWidth())
textField.removeFromSuperview()
@ -185,11 +138,11 @@ import UIKit
private func createDigitField() -> DigitBox {
let textField = DigitBox()
textField.isAccessibilityElement = true
textField.delegate = self
textField.textBoxDelegate = self
return textField
let digitBox = DigitBox()
digitBox.isAccessibilityElement = true
digitBox.digitField.delegate = self
digitBox.textBoxDelegate = self
return digitBox
}
func setupDigitFieldsView(size: CGFloat) {
@ -205,18 +158,18 @@ import UIKit
for (index, box) in digitFields.enumerated() {
accessibleElements.append(box)
fieldContainer.addSubview(box)
entryContainer.addSubview(box)
box.topAnchor.constraint(equalTo: fieldContainer.topAnchor).isActive = true
fieldContainer.bottomAnchor.constraint(equalTo: box.bottomAnchor).isActive = true
box.centerYAnchor.constraint(equalTo: fieldContainer.centerYAnchor).isActive = true
box.topAnchor.constraint(equalTo: entryContainer.topAnchor).isActive = true
entryContainer.bottomAnchor.constraint(equalTo: box.bottomAnchor).isActive = true
box.centerYAnchor.constraint(equalTo: entryContainer.centerYAnchor).isActive = true
if index == 0 {
box.leadingAnchor.constraint(equalTo: fieldContainer.leadingAnchor).isActive = true
box.leadingAnchor.constraint(equalTo: entryContainer.leadingAnchor).isActive = true
} else if index == digitFields.count - 1 {
box.leadingAnchor.constraint(equalTo: prevBox!.trailingAnchor, constant: space).isActive = true
fieldContainer.trailingAnchor.constraint(greaterThanOrEqualTo: box.trailingAnchor).isActive = true
entryContainer.trailingAnchor.constraint(greaterThanOrEqualTo: box.trailingAnchor).isActive = true
} else {
box.leadingAnchor.constraint(equalTo: prevBox!.trailingAnchor, constant: space).isActive = true
@ -279,7 +232,7 @@ import UIKit
guard let self = self else { return }
for (index, field) in self.digitFields.enumerated() {
field.isSecureTextEntry = secureEntry
field.digitField.isSecureTextEntry = secureEntry
// Accessibility - 33704 fix voice over will read what pin user is filling
field.accessibilityLabel = String(format: "PIN %lu of %lu", UInt(index) + 1, UInt(self.digitFields.count))
@ -352,8 +305,8 @@ import UIKit
}
if !dictionary.isEmpty{
for textField in digitFields {
MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: delegateObject as? UITextFieldDelegate)
for digitBox in digitFields {
MVMCoreUICommonViewsUtility.addDismissToolbar(digitBox.digitField, delegate: delegateObject as? UITextFieldDelegate)
}
}

View File

@ -28,10 +28,9 @@ import UIKit
return label
}()
public private(set) var fieldContainer: UIView = {
let view = UIView(frame: .zero)
public private(set) var entryContainer: FormView = {
let view = FormView()
view.isAccessibilityElement = false
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
@ -57,23 +56,11 @@ import UIKit
public var isValid = false
public var fieldKey: String?
/// Determines if a border should be drawn.
var hideBorder = false
private var borderStrokeColor: UIColor = .mfSilver()
private var borderPath: UIBezierPath = UIBezierPath()
public var bottomBar: CAShapeLayer = {
let layer = CAShapeLayer()
layer.backgroundColor = UIColor.black.cgColor
layer.drawsAsynchronously = true
layer.anchorPoint = CGPoint(x: 0.5, y: 1.0);
return layer
}()
public var appearance: Appearance = .original
public var showError = false
public var showError = false {
didSet {
entryContainer.showError = showError
}
}
public var errorMessage: String?
@ -83,7 +70,21 @@ import UIKit
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.updateUI(appearance: self.isEnabled ? .original : .disable)
self.isUserInteractionEnabled = self.isEnabled
self.entryContainer.isEnabled = self.isEnabled
self.feedback = self.showError ? self.errorMessage : nil
self.titleLabel.textColor = self.isEnabled ? .mfBattleshipGrey() : .mfSilver()
}
}
}
public var isLocked = false {
didSet {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.isUserInteractionEnabled = !self.isLocked
self.entryContainer.lockedUI()
}
}
}
@ -109,7 +110,7 @@ import UIKit
set {
feedbackLabel.text = newValue
setAccessibilityString(newValue)
refreshBorderUI()
entryContainer.refreshUI()
}
}
@ -129,8 +130,8 @@ import UIKit
// MARK: - Constraints
//--------------------------------------------------
public var fieldContainerLeading: NSLayoutConstraint?
public var fieldContainerTrailing: NSLayoutConstraint?
public var entryContainerLeading: NSLayoutConstraint?
public var entryContainerTrailing: NSLayoutConstraint?
public var feedbackLabelTrailing: NSLayoutConstraint?
public var feedbackLabelLeading: NSLayoutConstraint?
@ -175,6 +176,7 @@ import UIKit
guard subviews.isEmpty else { return }
translatesAutoresizingMaskIntoConstraints = false
isAccessibilityElement = false
setContentCompressionResistancePriority(.required, for: .vertical)
accessibilityElements = [titleLabel, feedbackLabel]
backgroundColor = .clear
@ -187,19 +189,19 @@ import UIKit
titleLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor)
titleLabelLeading?.isActive = true
addSubview(fieldContainer)
setupFieldContainerContent(fieldContainer)
addSubview(entryContainer)
setupFieldContainerContent(entryContainer)
titleContainerDistance = fieldContainer.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4)
titleContainerDistance = entryContainer.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4)
titleContainerDistance?.isActive = true
fieldContainerLeading = fieldContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor)
fieldContainerLeading?.isActive = true
fieldContainerTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: fieldContainer.trailingAnchor)
fieldContainerTrailing?.isActive = true
entryContainerLeading = entryContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor)
entryContainerLeading?.isActive = true
entryContainerTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: entryContainer.trailingAnchor)
entryContainerTrailing?.isActive = true
addSubview(feedbackLabel)
feedbackContainerDistance = feedbackLabel.topAnchor.constraint(equalTo: fieldContainer.bottomAnchor, constant: PaddingOne)
feedbackContainerDistance = feedbackLabel.topAnchor.constraint(equalTo: entryContainer.bottomAnchor, constant: PaddingOne)
feedbackContainerDistance?.isActive = true
feedbackLabelLeading = feedbackLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor)
feedbackLabelLeading?.isActive = true
@ -207,14 +209,12 @@ import UIKit
feedbackLabelTrailing?.isActive = true
layoutMarginsGuide.bottomAnchor.constraint(equalTo: feedbackLabel.bottomAnchor).isActive = true
fieldContainer.layer.addSublayer(bottomBar)
setNeedsDisplay()
layoutIfNeeded()
}
/// Method to override.
/// Intended to add the interactive content (i.e. textField) to the fieldContainer.
/// Intended to add the interactive content (i.e. textField) to the entryContainer.
open func setupFieldContainerContent(_ container: UIView) {
// To be overridden by subclass.
}
@ -224,7 +224,7 @@ import UIKit
titleLabel.updateView(size)
feedbackLabel.updateView(size)
refreshBorderUI()
entryContainer.updateView(size)
}
open override func reset() {
@ -233,53 +233,8 @@ import UIKit
backgroundColor = .clear
titleLabel.reset()
feedbackLabel.reset()
fieldContainer.subviews.forEach { $0.removeFromSuperview() }
updateUI(appearance: .original)
}
open override func layoutSubviews() {
super.layoutSubviews()
refreshBorderUI(bottomBarSize: 1)
}
open func refreshBorderUI(bottomBarSize: CGFloat? = nil) {
let size: CGFloat = appearance == .error ? 4 : 1
bottomBar.frame = CGRect(x: 0, y: fieldContainer.bounds.height - size, width: fieldContainer.bounds.width, height: size)
delegateObject?.moleculeDelegate?.moleculeLayoutUpdated?(self)
setNeedsDisplay()
layoutIfNeeded()
}
//--------------------------------------------------
// MARK: - Drawing
//--------------------------------------------------
open override func draw(_ rect: CGRect) {
super.draw(rect)
borderPath.removeAllPoints()
if !hideBorder {
// Brings the other half of the line inside the view to prevent thinness from cropping.
let origin = fieldContainer.frame.origin
let size = fieldContainer.frame.size
let insetLean: CGFloat = 0.5
borderPath.lineWidth = 1
borderPath.move(to: CGPoint(x: origin.x + insetLean, y: origin.y + size.height))
borderPath.addLine(to: CGPoint(x: origin.x + insetLean, y: origin.y + insetLean))
borderPath.addLine(to: CGPoint(x: origin.x + size.width - insetLean, y: origin.y + insetLean))
borderPath.addLine(to: CGPoint(x: origin.x + size.width - insetLean, y: origin.y + size.height))
borderStrokeColor.setStroke()
borderPath.stroke()
}
layoutIfNeeded()
entryContainer.subviews.forEach { $0.removeFromSuperview() }
titleLabel.textColor = .mfBattleshipGrey()
}
//--------------------------------------------------
@ -288,66 +243,17 @@ import UIKit
open override func setLeftPinConstant(_ constant: CGFloat) {
fieldContainerLeading?.constant = constant
entryContainerLeading?.constant = constant
feedbackLabelLeading?.constant = constant
titleLabelLeading?.constant = constant
}
open override func setRightPinConstant(_ constant: CGFloat) {
fieldContainerTrailing?.constant = constant
entryContainerTrailing?.constant = constant
feedbackLabelTrailing?.constant = constant
titleLabelTrailing?.constant = constant
}
//--------------------------------------------------
// MARK: - Form UI Base Appearance
//--------------------------------------------------
public enum Appearance: String {
case original
case error
case lock
case select
case disable
}
/// Updates the visual appearance of the container, with some logical laterations as well.
public func updateUI(appearance: Appearance) {
self.appearance = appearance
isUserInteractionEnabled = true
titleLabel.textColor = .mfBattleshipGrey()
hideBorder = false
feedback = showError ? errorMessage : nil
switch appearance {
case .original:
borderStrokeColor = .mfSilver()
bottomBar.backgroundColor = UIColor.black.cgColor
case .error:
borderStrokeColor = .mfPumpkin()
bottomBar.backgroundColor = UIColor.mfPumpkin().cgColor
case .lock:
isUserInteractionEnabled = false
hideBorder = true
bottomBar.backgroundColor = UIColor.clear.cgColor
case .select:
borderStrokeColor = .black
bottomBar.backgroundColor = UIColor.black.cgColor
case .disable:
isUserInteractionEnabled = false
borderStrokeColor = .mfSilver()
titleLabel.textColor = self.isEnabled ? .mfBattleshipGrey() : .mfSilver()
bottomBar.backgroundColor = self.isEnabled ? UIColor.black.cgColor : UIColor.mfSilver().cgColor
}
refreshBorderUI(bottomBarSize: appearance == .error ? 4 : 1)
}
}
// MARK: - Molecular
@ -371,12 +277,8 @@ extension EntryField {
errorMessage = errMessage
}
if let hideBorder = dictionary["hideBorder"] as? Bool {
self.hideBorder = hideBorder
}
if let appearance = dictionary["appearance"] as? String, let kind = Appearance(rawValue: appearance) {
updateUI(appearance: kind)
if let isLocked = dictionary["isLocked"] as? Bool {
self.isLocked = isLocked
}
// Key used to send text value to server

View File

@ -174,7 +174,7 @@ import UIKit
open func clearErrorState() {
textField.accessibilityValue = nil
updateUI(appearance: .original)
entryContainer.originalUI()
}
public func setBothTextDelegates(to delegate: (UITextFieldDelegate & TextFieldDelegate)?) {
@ -224,7 +224,7 @@ import UIKit
if isValid {
clearErrorState()
bottomBar.backgroundColor = UIColor.black.cgColor
entryContainer.bottomBar.backgroundColor = UIColor.black.cgColor
} else if let errMessage = errorMessage {
feedback = errMessage
@ -233,10 +233,7 @@ import UIKit
@objc func startEditing() {
if appearance != .original {
updateUI(appearance: .original)
}
entryContainer.originalUI()
textField.becomeFirstResponder()
}
}

View File

@ -9,18 +9,25 @@
import UIKit
@objcMembers open class View: UIView {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
open var json: [AnyHashable: Any]?
private var initialSetupPerformed = false
//--------------------------------------------------
// MARK: - Initialization
//--------------------------------------------------
public override init(frame: CGRect) {
super.init(frame: .zero)
initialSetup()
}
public init() {
super.init(frame: .zero)
initialSetup()
public convenience init() {
self.init(frame: .zero)
}
public required init?(coder: NSCoder) {
@ -29,6 +36,7 @@ import UIKit
}
public func initialSetup() {
if !initialSetupPerformed {
initialSetupPerformed = true
setupView()
@ -36,7 +44,9 @@ import UIKit
}
}
/// MARK:- MVMCoreViewProtocol
extension View: MVMCoreViewProtocol {
open func updateView(_ size: CGFloat) {}
/// Will be called only once.
@ -46,8 +56,10 @@ extension View: MVMCoreViewProtocol {
}
}
/// MARK:- MVMCoreUIMoleculeViewProtocol
extension View: MVMCoreUIMoleculeViewProtocol {
open func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) {
open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
self.json = json
if let backgroundColorString = json?.optionalStringForKey(KeyBackgroundColor) {

View File

@ -0,0 +1,171 @@
//
// FormView.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 11/12/19.
// Copyright © 2019 Verizon Wireless. All rights reserved.
//
import UIKit
@objcMembers open class FormView: View {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public var bottomBar: CAShapeLayer = {
let layer = CAShapeLayer()
layer.backgroundColor = UIColor.black.cgColor
layer.drawsAsynchronously = true
layer.anchorPoint = CGPoint(x: 0.5, y: 1.0);
return layer
}()
public var borderStrokeColor: UIColor = .mfSilver()
public var borderPath: UIBezierPath = UIBezierPath()
/// Determines if a border should be drawn.
public var hideBorder = false
public var showError = false {
didSet {
_ = showError ? errorUI() : originalUI()
}
}
public var isEnabled = true {
didSet {
isEnabled ? originalUI() : disabledUI()
}
}
//--------------------------------------------------
// MARK: - Delegate
//--------------------------------------------------
weak var delegateObject: MVMCoreUIDelegateObject?
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open override func layoutSubviews() {
super.layoutSubviews()
refreshUI(bottomBarSize: showError ? 4 : 1)
}
open override func updateView(_ size: CGFloat) {
super.updateView(size)
refreshUI()
}
open override func draw(_ rect: CGRect) {
super.draw(rect)
borderPath.removeAllPoints()
if !hideBorder {
// Brings the other half of the line inside the view to prevent cropping.
let origin = frame.origin
let size = frame.size
let insetLean: CGFloat = 0.5
borderPath.lineWidth = 1
borderPath.move(to: CGPoint(x: origin.x + insetLean, y: origin.y + size.height))
borderPath.addLine(to: CGPoint(x: origin.x + insetLean, y: origin.y + insetLean))
borderPath.addLine(to: CGPoint(x: origin.x + size.width - insetLean, y: origin.y + insetLean))
borderPath.addLine(to: CGPoint(x: origin.x + size.width - insetLean, y: origin.y + size.height))
borderStrokeColor.setStroke()
borderPath.stroke()
}
layoutIfNeeded()
}
override open func setupView() {
super.setupView()
layer.addSublayer(bottomBar)
}
//--------------------------------------------------
// MARK: - Draw States
//--------------------------------------------------
open func originalUI() {
isUserInteractionEnabled = true
hideBorder = false
borderStrokeColor = .mfSilver()
bottomBar.backgroundColor = UIColor.black.cgColor
refreshUI(bottomBarSize: 1)
}
open func errorUI() {
isUserInteractionEnabled = true
hideBorder = false
borderStrokeColor = .mfPumpkin()
bottomBar.backgroundColor = UIColor.mfPumpkin().cgColor
refreshUI(bottomBarSize: 4)
}
open func selectedUI() {
isUserInteractionEnabled = true
hideBorder = false
borderStrokeColor = .black
bottomBar.backgroundColor = UIColor.black.cgColor
refreshUI(bottomBarSize: 1)
}
open func lockedUI() {
isUserInteractionEnabled = false
hideBorder = true
bottomBar.backgroundColor = UIColor.clear.cgColor
refreshUI(bottomBarSize: 1)
}
open func disabledUI() {
isUserInteractionEnabled = false
borderStrokeColor = .mfSilver()
hideBorder = false
bottomBar.backgroundColor = self.isEnabled ? UIColor.black.cgColor : UIColor.mfSilver().cgColor
refreshUI(bottomBarSize: 1)
}
open func refreshUI(bottomBarSize: CGFloat? = nil) {
let size: CGFloat = showError ? 4 : 1
bottomBar.frame = CGRect(x: 0, y: bounds.height - size, width: bounds.width, height: size)
delegateObject?.moleculeDelegate?.moleculeLayoutUpdated?(self)
setNeedsDisplay()
layoutIfNeeded()
}
//--------------------------------------------------
// MARK: - Molecular
//--------------------------------------------------
override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
self.delegateObject = delegateObject
guard let dictionary = json, !dictionary.isEmpty else { return }
if let hideBorder = dictionary["hideBorder"] as? Bool {
self.hideBorder = hideBorder
}
}
}