Refactored UI drawing to new class. Reflected change.
This commit is contained in:
parent
30a56923c0
commit
48bcd87bc1
@ -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 */,
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
171
MVMCoreUI/Containers/views/FormView.swift
Normal file
171
MVMCoreUI/Containers/views/FormView.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user