Beginning stages of swiftification.

This commit is contained in:
Kevin G Christiano 2019-10-03 10:44:56 -04:00
parent 52119f7b31
commit 4915bda4b9
2 changed files with 622 additions and 0 deletions

View File

@ -18,6 +18,7 @@
01DF567021FA5AB300CC099B /* TextFieldListFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */; };
01E569D3223FFFA500327251 /* ThreeLayerViewController.swift in Headers */ = {isa = PBXBuildFile; fileRef = D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */; };
0A41BA7F23453A6400D4C0BC /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA7E23453A6400D4C0BC /* TextField.swift */; };
948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948DB67D2326DCD90011F916 /* MultiProgress.swift */; };
B8200E152280C4CF007245F4 /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E142280C4CF007245F4 /* ProgressBar.swift */; };
B8200E192281DC1A007245F4 /* CornerLabels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E182281DC1A007245F4 /* CornerLabels.swift */; };
@ -203,6 +204,7 @@
01DF55DF21F8FAA800CC099B /* MFTextFieldListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MFTextFieldListView.swift; sourceTree = "<group>"; };
01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldListFormViewController.swift; sourceTree = "<group>"; };
0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImage.swift; sourceTree = "<group>"; };
0A41BA7E23453A6400D4C0BC /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = "<group>"; };
948DB67D2326DCD90011F916 /* MultiProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiProgress.swift; sourceTree = "<group>"; };
B8200E142280C4CF007245F4 /* ProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBar.swift; sourceTree = "<group>"; };
B8200E182281DC1A007245F4 /* CornerLabels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerLabels.swift; sourceTree = "<group>"; };
@ -740,6 +742,7 @@
D29DF24321E6A176003B2FB9 /* MFDigitTextField.h */,
D29DF24821E6A177003B2FB9 /* MFDigitTextField.m */,
D29DF24A21E6A177003B2FB9 /* MFDigitTextField.xib */,
0A41BA7E23453A6400D4C0BC /* TextField.swift */,
);
path = TextFields;
sourceTree = "<group>";
@ -1046,6 +1049,7 @@
D22479962316AF6E003FCCF9 /* HeadlineBodyTextButton.swift in Sources */,
D2E1FADD2268B25E00AEFD8C /* MoleculeTableViewCell.swift in Sources */,
D29DF2AE21E7B3A4003B2FB9 /* MFTextView.m in Sources */,
0A41BA7F23453A6400D4C0BC /* TextField.swift in Sources */,
D29DF18121E69E50003B2FB9 /* MFView.m in Sources */,
D29DF18321E69E54003B2FB9 /* SeparatorView.m in Sources */,
D29DF17A21E69E1F003B2FB9 /* MFCustomButton.m in Sources */,

View File

@ -0,0 +1,618 @@
//
// TextField.swift
// MVMCoreUI
//
// Created by Kevin Christiano on 10/2/19.
// Copyright © 2019 Verizon Wireless. All rights reserved.
//
import MVMCoreUI
import UIKit
@objc protocol MFTextFieldDelegate: NSObjectProtocol {
// Called when the entered text becomes valid based on the validation block
@objc optional func entryIsValid(_ textfield: MFTextField?)
// Called when the entered text becomes invalid based on the validation block
@objc optional func entryIsInvalid(_ textfield: MFTextField?)
// Dismisses the keyboard.
@objc optional func dismissFieldInput(_ sender: Any?)
}
class MFTextField: ViewConstrainingView, MVMCoreUIMoleculeViewProtocol, FormValidationProtocol {
weak var view: UIView?
@IBOutlet weak var textFieldContainerView: UIView?
@IBOutlet weak var backgroundView: UIView?
@IBOutlet weak var textField: UITextField?
@IBOutlet weak var formLabel: Label?
@IBOutlet weak var separatorView: UIView?
/*make it public so outsider class can know the posistion of it. */ @IBOutlet weak var heightConstraint: NSLayoutConstraint?
@IBOutlet weak var formLabelRightPin: NSLayoutConstraint?
var enabled = false
// To set the placeholder and text
weak var text: String?
weak var formText: String?
weak var fieldKey: String?
weak var placeholder: String?
/* will move out in Feb release */ var hideBorder = false
weak var datePicker: UIDatePicker?
private(set) weak var toolbar: UIToolbar?
//helper
var formatter: DateFormatter?
// If you're using a MFViewController, you must set this to it
weak var uiTextFieldDelegate: UITextFieldDelegate?
// The delegate and block for validation. Validates if the text that the user has entered is valid or not. Checked after each change if there is a delegate.
weak var mfTextFieldDelegate: MFTextFieldDelegate?
var valid = false
var validationBlock: ((_ enteredValue: String?) -> Bool)?
// custom text colors
var customEnabledTextColor: UIColor?
var customDisabledTextColor: UIColor?
//default error message
var errMessage: String?
var editCompleteAction: ((_ text: String?) -> Void)?
private var customPlaceHolderColor: UIColor?
@IBOutlet private weak var separatorHeightConstraint: NSLayoutConstraint!
private var borderPath: UIBezierPath?
private var calendar: Calendar?
@IBOutlet private weak var textContainerLeftPin: NSLayoutConstraint!
@IBOutlet private weak var errorLableLeftPin: NSLayoutConstraint!
@IBOutlet private weak var formLabelLeftPin: NSLayoutConstraint!
@IBOutlet private weak var textContainerRightPin: NSLayoutConstraint!
@IBOutlet private weak var errorLableRightPin: NSLayoutConstraint!
// MARK: - setup
func updateView(_ size: CGFloat) {
super.updateView(size)
MVMCoreDispatchUtility.performBlock(onMainThread: {
self.formLabel.updateView(size)
self.label.font = MFStyler.fontForTextFieldUnderLabel()
MFStyler.styleTextField(self.textField)
self.dashLine.updateView(size)
})
}
func setupView() {
if !self.view {
backgroundColor = UIColor.clear
let nib = getNib()
let views = nib?.instantiate(withOwner: self, options: nil)
let view = views?.first as? UIView
view?.translatesAutoresizingMaskIntoConstraints = false
view?.setContentHuggingPriority(.required, for: .vertical)
view?.setContentCompressionResistancePriority(.required, for: .vertical)
self.view = view
view?.frame = frame
if let view = view {
addSubview(view)
}
pinView(toSuperView: view)
textField.font = MFStyler.fontForTextField()
translatesAutoresizingMaskIntoConstraints = false
formLabel.font = MFStyler.fontB3()
formLabel.textColor = UIColor.mfBattleshipGrey()
label.font = MFStyler.fontForTextFieldUnderLabel()
label.textColor = UIColor.black
dropDownCarrotLabel.hidden = true
separatorView.backgroundColor = UIColor.black
dashLine.backgroundColor = UIColor.white
dashLine.hidden = true
MFStyler.styleTextField(textField)
dropDownCarrotLabel.isUserInteractionEnabled = true
let tapOnCarrot = UITapGestureRecognizer(target: self, action: #selector(startEditing))
dropDownCarrotLabel.addGestureRecognizer(tapOnCarrot)
enabled = true
// Disable SmartQuotes
if #available(iOS 11.0, *) {
textField.smartQuotesType = .no
textField.smartDashesType = .no
textField.smartInsertDeleteType = .no
}
}
}
func getNib() -> UINib? {
return UINib(nibName: NSStringFromClass(type(of: self).self), bundle: MVMCoreUIUtility.bundleForMVMCoreUI())
}
class func mfTextField() -> Self? {
let view = self.init()
view?.hasDropDown = false
return view
}
class func mfTextField(withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) -> Self? {
let textField = self.mfTextField()
textField?.bothTextFieldDelegates = delegate
return textField
}
class func mfTextField(withMap map: [AnyHashable : Any]?, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) -> Self? {
let textField = self.mfTextField()
textField?.translatesAutoresizingMaskIntoConstraints = false
textField?.setWithMap(map, bothDelegates: delegate)
return textField
}
class func mfTextFieldForDropDown() -> Self? {
let textField = self.mfTextField()
textField?.dropDownCarrotLabel().hidden = false
textField?.hasDropDown = true
return textField
}
class func mfTextFieldForDropDown(withBothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) -> Self? {
let textField = self.mfTextFieldForDropDown()
textField?.bothTextFieldDelegates = delegate
return textField
}
// MARK: - Utilities
func createDatePicker() {
//tool bar
MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: textField.delegate)
//date picker
datePicker = MVMCoreUICommonViewsUtility.addDatePicker(to: textField)
let calendar = Calendar.current
calendar.timeZone = NSTimeZone.system
self.calendar = calendar
}
func inputFromDatePicker(from fromDate: Date?, to toDate: Date?, showFromDateAsDefaultInput show: Bool) {
createDatePicker()
if show {
if let fromDate = fromDate {
if calendar.isDate(fromDate, inSameDayAs: Date()) {
text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string")
} else {
self.text = formatter.string(from: fromDate)
}
}
}
datePicker.minimumDate = fromDate
datePicker.maximumDate = toDate
}
func setDatePickerFrom(_ fromDate: Date?, to toDate: Date?) {
if let fromDate = fromDate {
if calendar.isDate(fromDate, inSameDayAs: Date()) {
text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string")
} else {
self.text = formatter.string(from: fromDate)
}
}
datePicker.minimumDate = fromDate
datePicker.maximumDate = toDate
datePicker.timeZone = NSTimeZone.system
}
func dismissDatePicker() -> Date? {
let pickedDate = datePicker.date
if let pickedDate = pickedDate {
if calendar.isDate(pickedDate, inSameDayAs: Date()) {
text = MVMCoreUIUtility.hardcodedString(withKey: "textfield_today_string")
} else {
self.text = formatter.string(from: pickedDate)
}
}
textField.resignFirstResponder()
return pickedDate
}
func dismissPicker() {
textField.resignFirstResponder()
}
func setErrorMessage(_ errorMessage: String?) {
MVMCoreDispatchUtility.performBlock(onMainThread: {
if self.enabled == true {
self.separatorHeightConstraint.constant = 4
self.errorShowing = true
self.separatorView.backgroundColor = UIColor.mfPumpkin()
self.label.text() = errorMessage
self.label.numberOfLines = 0
self.textField.accessibilityValue() = String(format: MVMCoreUIUtility.hardcodedString(withKey: "textfield_error_message"), self.textField.text() ?? "", errorMessage ?? "")
self.setNeedsDisplay()
self.layoutIfNeeded()
}
})
}
func pushAccessibilityNotification() {
MVMCoreDispatchUtility.performBlock(onMainThread: {
UIAccessibilityPostNotification(UIAccessibility.Notification.layoutChanged, self.textField)
})
}
func hideError() {
MVMCoreDispatchUtility.performBlock(onMainThread: {
self.separatorHeightConstraint.constant = 1
self.separatorView.backgroundColor = UIColor.black
self.layoutIfNeeded()
self.errorShowing = false
self.label.textColor = UIColor.black
self.label.text() = ""
self.textField.accessibilityValue() = nil
self.setNeedsDisplay()
self.layoutIfNeeded()
})
}
func placeholder() -> String? {
return textField.attributedPlaceholder.string
}
func text() -> String? {
return textField.text()
}
// MARK: - Setters
func setPlaceholder(_ placeholder: String?, with color: UIColor?) {
customPlaceHolderColor = color
// fixed crash issue
if placeholder != nil {
if let color = color {
textField.attributedPlaceholder = NSAttributedString(string: placeholder ?? "", attributes: [
NSAttributedString.Key.foregroundColor: color
])
}
}
if textField.text.length > 0 && !errorShowing {
label.text = placeholder
} else if !errorShowing {
label.text = ""
}
setAccessibilityString(placeholder)
}
func setAccessibilityString(_ accessibilityString: String?) {
var accessibilityString = accessibilityString
// adding missing accessibilityLabel value
// if we have some value in accessibilityLabel,
// then only can append regular and picker item
if hasDropDown {
// MFDLog(@"Label: %@", self.textField.accessibilityLabel);
accessibilityString = accessibilityString ?? "" + (MVMCoreUIUtility.hardcodedString(withKey: "textfield_picker_item"))
// MFDLog(@"Label: %@", self.textField.accessibilityLabel);
} else {
accessibilityString = accessibilityString ?? "" + (MVMCoreUIUtility.hardcodedString(withKey: "textfield_regular"))
// MFDLog(@"Label: %@", self.textField.accessibilityLabel);
}
textField.accessibilityLabel() = "\(accessibilityString ?? "") \(textField.isEnabled ? "" : MVMCoreUIUtility.hardcodedString(withKey: "textfield_disabled_state"))"
}
func setFormText(_ formText: String?) {
formLabel.text = formText
setAccessibilityString(formText)
}
func setPlaceholder(_ placeholder: String?) {
setPlaceholder(placeholder, with: customPlaceHolderColor ?? UIColor.black)
}
func setText(_ text: String?) {
textField.text = text
valueChanged()
}
func setMfTextFieldDelegate(_ mfTextFieldDelegate: MFTextFieldDelegate?) {
self.mfTextFieldDelegate = mfTextFieldDelegate
if mfTextFieldDelegate != nil && !observingForChanges {
observingForChanges = true
NotificationCenter.default.addObserver(self, selector: #selector(valueChanged), name: UITextField.textDidChangeNotification, object: textField)
NotificationCenter.default.addObserver(self, selector: #selector(endInputing), name: UITextField.textDidEndEditingNotification, object: textField)
NotificationCenter.default.addObserver(self, selector: #selector(startEditing), name: UITextField.textDidBeginEditingNotification, object: textField)
} else if mfTextFieldDelegate == nil && observingForChanges {
observingForChanges = false
NotificationCenter.default.removeObserver(self, name: UITextField.textDidChangeNotification, object: textField)
NotificationCenter.default.removeObserver(self, name: UITextField.textDidEndEditingNotification, object: textField)
NotificationCenter.default.removeObserver(self, name: UITextField.textDidBeginEditingNotification, object: textField)
}
}
func setUiTextFieldDelegate(_ uiTextFieldDelegate: UITextFieldDelegate?) {
self.uiTextFieldDelegate = uiTextFieldDelegate
textField.delegate = uiTextFieldDelegate
}
func setBothTextFieldDelegates(_ delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) {
mfTextFieldDelegate = delegate
uiTextFieldDelegate = delegate
}
func setWithMap(_ map: [AnyHashable : Any]?) {
if map?.count == 0 {
return
}
var string = map?.string(KeyLabel)
if (string?.count ?? 0) > 0 {
formText = string
}
string = map?.string(KeyValue)
if (string?.count ?? 0) > 0 {
text = string
}
string = map?.string(forKey: KeyDisable)
if string?.isEqual(StringY) ?? false || map?.bool(forKey: KeyDisable) != nil {
enable(false)
}
string = map?.string(KeyErrorMessage)
if (string?.count ?? 0) > 0 {
errMessage = string
}
// key used to send text value to server
string = map?.string(KeyFieldKey)
if (string?.count ?? 0) > 0 {
fieldKey = string
}
string = map?.string(KeyType)
if (string == "dropDown") {
dropDownCarrotLabel().hidden = false
self.hasDropDown = true
} else if (string == "password") {
textField.isSecureTextEntry = true
} else if (string == "number") {
textField.keyboardType = .numberPad
} else if (string == "email") {
textField.keyboardType = .emailAddress
}
string = map?.string("regex")
if (string?.count ?? 0) != 0 {
validationBlock = { enteredValue in
return MVMCoreUIUtility.validate(enteredValue, withRegularExpression: string)
}
} else {
setDefaultValidationBlock()
}
}
func setWithMap(_ map: [AnyHashable : Any]?, bothDelegates delegate: (UITextFieldDelegate & MFTextFieldDelegate)?) {
MVMCoreUICommonViewsUtility.addDismissToolbar(textField, delegate: delegate)
self.bothTextFieldDelegates = delegate
setWithMap(map)
}
func setValidationBlock(_ validationBlock: @escaping (String?) -> Bool) {
self.validationBlock = validationBlock
valueChanged()
}
func setDefaultValidationBlock() {
self.validationBlock = { enteredValue in
if (enteredValue?.count ?? 0) > 0 {
return true
} else {
return false
}
}
}
func setLeftPinConstant(_ constant: CGFloat) {
textContainerLeftPin.constant = constant
errorLableLeftPin.constant = constant
formLabelLeftPin.constant = constant
}
func setRightPinConstant(_ constant: CGFloat) {
textContainerRightPin.constant = constant
errorLableRightPin.constant = constant
formLabelRightPin.constant = constant
}
deinit {
self.bothTextFieldDelegates = nil
}
// MARK: - XIB Helpers
func showDropDown(_ show: Bool) {
if hasDropDown {
dropDownCarrotLabel.hidden = !show
dropDownCarrotWidth.active = !show
setNeedsLayout()
layoutIfNeeded()
}
}
func enable(_ enable: Bool) {
enabled = enable //Set outside the dispatch so that registerAnimations can know about it
MVMCoreDispatchUtility.performBlock(onMainThread: {
self.isUserInteractionEnabled = enable
self.textField.userInteractionEnabled = enable
self.textField.isEnabled = enable
if enable {
self.textField.textColor = self.customEnabledTextColor ?? UIColor.black
self.formLabel.textColor = UIColor.mfBattleshipGrey()
self.label.textColor = UIColor.black
if self.errorShowing {
self.separatorView.backgroundColor = UIColor.mfPumpkin()
} else {
self.separatorView.backgroundColor = UIColor.black
}
self.showDropDown(true)
} else {
self.textField.textColor = self.customDisabledTextColor ?? UIColor.mfSilver()
self.formLabel.textColor = UIColor.mfSilver()
self.label.textColor = UIColor.mfSilver()
self.showDropDown(false)
self.hideError() //should not have error if the field is disabled
self.separatorView.backgroundColor = UIColor.mfSilver()
}
})
}
func showLabel(_ show: Bool) {
label.hidden = !show
}
func dashSeperatorView(_ dash: Bool) {
if dash {
dashLine.hidden = false
//never hide seperator view because it could be possiblely used by other classes for positioning
separatorView.backgroundColor = UIColor.clear
} else {
dashLine.hidden = true
separatorView.backgroundColor = UIColor.black
}
}
// MARK: - Observing for change
func validateBlock() {
valueChanged()
}
func valueChanged() {
// update label for placeholder
if !errorShowing {
label.text = ""
}
// Check validity.
let previousValidity = valid
if validationBlock {
valid = validationBlock(text)
} else {
//if validation not set, input will always be valid
valid = true
}
if previousValidity && !valid {
if errMessage {
self.errorMessage = errMessage
}
if mfTextFieldDelegate.responds(to: #selector(entryIsInvalid(_:))) {
mfTextFieldDelegate.entryIsInvalid(self)
}
} else if !previousValidity && valid {
hideError()
if mfTextFieldDelegate.responds(to: #selector(entryIsValid(_:))) {
mfTextFieldDelegate.entryIsValid(self)
}
}
}
func endInputing() {
if isValid {
hideError()
separatorView.backgroundColor = UIColor.black
} else {
if errMessage {
self.errorMessage = errMessage
}
}
}
// MARK: - helper
func startEditing() {
textField.becomeFirstResponder()
if !errorShowing {
separatorView.backgroundColor = UIColor.black
separatorHeightConstraint.constant = 1
}
}
class func getEnabledTextfields(_ textFieldToDetermine: [MFTextField]?) -> [AnyHashable]? {
var enabledTextFields: [AnyHashable] = []
for textfield in textFieldToDetermine ?? [] {
if textfield.isEnabled {
enabledTextFields.append(textfield)
}
}
return enabledTextFields
}
//#pragma mark - Accessibility
func formatter() -> DateFormatter? {
if !formatter {
formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeZone = NSTimeZone.system
formatter.locale = NSLocale.current
formatter.formatterBehavior = .default
}
return formatter
}
func draw(_ rect: CGRect) {
super.draw(rect)
borderPath.removeAllPoints()
if !hideBorder {
let frame = textFieldContainerView.frame
borderPath = UIBezierPath()
borderPath.move(to: CGPoint(x: frame.origin.x, y: frame.origin.y + frame.size.height))
borderPath.addLine(to: CGPoint(x: frame.origin.x, y: frame.origin.y))
borderPath.addLine(to: CGPoint(x: frame.origin.x + frame.size.width, y: frame.origin.y))
borderPath.addLine(to: CGPoint(x: frame.origin.x + frame.size.width, y: frame.origin.y + frame.size.height))
borderPath.lineWidth = 1
var strokeColor: UIColor?
if errorShowing {
strokeColor = UIColor.mfPumpkin()
} else {
strokeColor = UIColor.mfSilver()
}
strokeColor?.setStroke()
borderPath.stroke()
}
}
//////////////////////
#pragma mark - MVMCoreUIMoleculeViewProtocol
- (void)setWithJSON:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject additionalData:(NSDictionary *)additionalData {
if ([delegateObject isKindOfClass:[MVMCoreUIDelegateObject class]]) {
[FormValidator setupValidationWithMolecule:self delegate:delegateObject.formValidationProtocol];
FormValidator *formValidator = [FormValidator getFormValidatorForDelegate:delegateObject.formValidationProtocol];
[self setWithMap:json];
self.mfTextFieldDelegate = formValidator;
self.uiTextFieldDelegate = delegateObject.uiTextFieldDelegate;
[MVMCoreUICommonViewsUtility addDismissToolbar:self.textField delegate:self.uiTextFieldDelegate];
}
}
+ (CGFloat)estimatedHeightForRow:(NSDictionary *)json delegateObject:(MVMCoreUIDelegateObject *)delegateObject {
return 76;
}
#pragma mark - FormValidationProtocol
- (BOOL)isValidField {
return self.isValid;
}
- (nullable NSString *)formFieldName {
return self.fieldKey;
}
- (nullable id)formFieldValue {
return self.text;
}
}