From 2a251649af2356fe2e9e1a488d13a4350f95dd72 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 28 Apr 2020 16:44:05 -0400 Subject: [PATCH] functioning border --- MVMCoreUI/BaseClasses/TextField.swift | 6 +- MVMCoreUI/BaseClasses/TextView.swift | 326 +++++++++++++++++++--- MVMCoreUI/BaseClasses/TextViewModel.swift | 26 +- 3 files changed, 303 insertions(+), 55 deletions(-) diff --git a/MVMCoreUI/BaseClasses/TextField.swift b/MVMCoreUI/BaseClasses/TextField.swift index ea56e68e..03d511dd 100644 --- a/MVMCoreUI/BaseClasses/TextField.swift +++ b/MVMCoreUI/BaseClasses/TextField.swift @@ -51,7 +51,7 @@ public protocol TextInputDidDeleteProtocol: class { public func initialSetup() { if !initialSetupPerformed { - tintColor = .black + tintColor = .mvmBlack initialSetupPerformed = true setupView() } @@ -87,7 +87,9 @@ extension TextField: MVMCoreViewProtocol { /// MARK:- MoleculeViewProtocol extension TextField: MoleculeViewProtocol { - open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + + open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + if let color = model.backgroundColor?.uiColor { backgroundColor = color } diff --git a/MVMCoreUI/BaseClasses/TextView.swift b/MVMCoreUI/BaseClasses/TextView.swift index cdebcda4..0a233187 100644 --- a/MVMCoreUI/BaseClasses/TextView.swift +++ b/MVMCoreUI/BaseClasses/TextView.swift @@ -9,7 +9,7 @@ import UIKit -@objc open class TextView: UITextView, UITextViewDelegate { +@objc open class TextView: UITextView, UITextViewDelegate, MVMCoreViewProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -28,6 +28,98 @@ import UIKit return model as? TextViewModel } + //-------------------------------------------------- + // MARK: - Drawing Properties + //-------------------------------------------------- + + /// Total control over the drawn top, bottom, left and right borders. + public var disableAllBorders = false + + private(set) var fieldState: FieldState = .original { + didSet (oldState) { + // Will not update if new state is the same as old. + if fieldState != oldState { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + self.fieldState.setStateUI(for: self) + } + } + } + } + + /// Determines if the top, left, and right borders should be drawn. + private var hideBorders = false + + public var borderStrokeColor: UIColor = .mvmCoolGray3 + public var bottomStrokeColor: UIColor = .mvmBlack + private var borderPath: UIBezierPath = UIBezierPath() + private var bottomPath: UIBezierPath = UIBezierPath() + + //-------------------------------------------------- + // MARK: - Property Observers + //-------------------------------------------------- + + private var _isEnabled: Bool = true + private var _showError: Bool = false + private var _isLocked: Bool = false + private var _isSelected: Bool = false + + public var isEnabled: Bool { + get { return _isEnabled } + set (enabled) { + + _isEnabled = enabled + _isLocked = false + _isSelected = false + _showError = false + + fieldState = enabled ? .original : .disabled + } + } + + public var showError: Bool { + get { return _showError } + set (error) { + + _showError = error + _isEnabled = true + _isLocked = false + _isSelected = false + + fieldState = error ? .error : .original + } + } + + public var isLocked: Bool { + get { return _isLocked } + set (locked) { + + _isLocked = locked + _isEnabled = true + _isSelected = false + _showError = false + + fieldState = locked ? .locked : .original + } + } + + public var isSelected: Bool { + get { return _isSelected } + set (selected) { + + _isSelected = selected + _isLocked = false + _isEnabled = true + + if _showError { + fieldState = selected ? .selectedError : .error + } else { + fieldState = selected ? .selected : .original + } + } + } + //-------------------------------------------------- // MARK: - Delegate //-------------------------------------------------- @@ -47,6 +139,8 @@ import UIKit } } + var delegateObject: MVMCoreUIDelegateObject? + //-------------------------------------------------- // MARK: - Constraint //-------------------------------------------------- @@ -77,7 +171,7 @@ import UIKit } //-------------------------------------------------- - // MARK: - Setup + // MARK: - Lifecycle //-------------------------------------------------- public func initialSetup() { @@ -89,6 +183,187 @@ import UIKit } } + open func updateView(_ size: CGFloat) { + + refreshUI() + } + + /// Will be called only once. + open func setupView() { + + translatesAutoresizingMaskIntoConstraints = false + insetsLayoutMarginsFromSafeArea = false + showsVerticalScrollIndicator = false + showsHorizontalScrollIndicator = false + contentInset = UIEdgeInsets(top: Padding.Three, left: Padding.Three, bottom: Padding.Three, right: Padding.Three) + backgroundColor = .mvmWhite + clipsToBounds = true + smartQuotesType = .no + smartDashesType = .no + smartInsertDeleteType = .no + font = textViewModel?.fontStyle.getFont() + isEditable = true + isOpaque = false + } + + open func reset() { + + backgroundColor = .mvmWhite + text = "" + inputAccessoryView?.removeFromSuperview() + contentInset = UIEdgeInsets(top: Padding.Three, left: Padding.Three, bottom: Padding.Three, right: Padding.Three) + } + + open override func layoutSubviews() { + super.layoutSubviews() + + refreshUI(bottomBarSize: showError ? 4 : 1) + } + + //-------------------------------------------------- + // MARK: - Draw + //-------------------------------------------------- + + /// This handles the top, left, and right border lines. + open override func draw(_ rect: CGRect) { + super.draw(rect) + + borderPath.removeAllPoints() + bottomPath.removeAllPoints() + + if !disableAllBorders && !hideBorders { + // Brings the other half of the line inside the view to prevent cropping. + let origin = bounds.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() + + bottomPath.lineWidth = 4 + bottomPath.move(to: CGPoint(x: origin.x + size.width, y: origin.y + size.height - 2)) + bottomPath.addLine(to: CGPoint(x: origin.x, y: origin.y + size.height - 2)) + + bottomStrokeColor.setStroke() + bottomPath.stroke() + } + } + + //-------------------------------------------------- + // MARK: - Draw States + //-------------------------------------------------- + + public enum FieldState { + case original + case error + case selectedError + case selected + case locked + case disabled + + public func setStateUI(for formField: TextView) { + + switch self { + case .original: + formField.originalUI() + + case .error: + formField.errorUI() + + case .selectedError: + formField.selectedErrorUI() + + case .selected: + formField.selectedUI() + + case .locked: + formField.lockedUI() + + case .disabled: + formField.disabledUI() + } + } + } + + open func originalUI() { + + isEditable = true + hideBorders = false + borderStrokeColor = .mvmCoolGray3 + bottomStrokeColor = .mvmBlack + refreshUI(bottomBarSize: 1) + } + + open func errorUI() { + + isEditable = true + hideBorders = false + borderStrokeColor = .mvmOrange + bottomStrokeColor = .mvmOrange + refreshUI(bottomBarSize: 4) + } + + open func selectedErrorUI() { + + isEditable = true + hideBorders = false + borderStrokeColor = .mvmBlack + bottomStrokeColor = .mvmOrange + refreshUI(bottomBarSize: 4) + } + + open func selectedUI() { + + isEditable = true + hideBorders = false + borderStrokeColor = .mvmBlack + bottomStrokeColor = .mvmBlack + refreshUI(bottomBarSize: 1) + } + + open func lockedUI() { + + isEditable = false + hideBorders = true + borderStrokeColor = .clear + bottomStrokeColor = .clear + refreshUI(bottomBarSize: 1) + } + + open func disabledUI() { + + isEditable = false + hideBorders = false + borderStrokeColor = .mvmCoolGray3 + bottomStrokeColor = .mvmCoolGray3 + refreshUI(bottomBarSize: 1) + } + + open func refreshUI(bottomBarSize: CGFloat? = nil, updateMoleculeLayout: Bool = false) { + + if !disableAllBorders { +// let size: CGFloat = bottomBarSize ?? (showError ? 4 : 1) +// var heightChanged = false + +// if let bottomHeight = bottomBar?.bounds.height { +// heightChanged = size != bottomHeight +// } + + if updateMoleculeLayout {//|| heightChanged { + delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self) + } + + setNeedsDisplay() + layoutIfNeeded() + } + } + //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- @@ -177,36 +452,12 @@ import UIKit } } -/// MARK:- MVMCoreViewProtocol -extension TextView: MVMCoreViewProtocol { - - open func updateView(_ size: CGFloat) { } - - /// Will be called only once. - open func setupView() { - - translatesAutoresizingMaskIntoConstraints = false - insetsLayoutMarginsFromSafeArea = false - showsVerticalScrollIndicator = false - showsHorizontalScrollIndicator = false - contentInset = UIEdgeInsets(top: Padding.Three, left: Padding.Three, bottom: Padding.Three, right: Padding.Three) - backgroundColor = .mvmWhite - clipsToBounds = true - smartQuotesType = .no - smartDashesType = .no - smartInsertDeleteType = .no - font = textViewModel?.fontStyle.getFont() - layer.borderWidth = 1 - layer.borderColor = UIColor.mvmBlack.cgColor - isEditable = true - } -} - -/// MARK:- MoleculeViewProtocol +// MARK:- MoleculeViewProtocol extension TextView: MoleculeViewProtocol { open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { self.model = model + self.delegateObject = delegateObject if let color = model.backgroundColor?.uiColor { backgroundColor = color @@ -223,8 +474,6 @@ extension TextView: MoleculeViewProtocol { isEditable = model.isEditable textAlignment = model.textAlignment textColor = model.textColor.uiColor - layer.borderColor = model.borderColor?.cgColor - layer.borderWidth = model.borderWidth text = model.text uiTextViewDelegate = delegateObject?.uiTextViewDelegate isShowingPlaceholder = model.text!.isEmpty @@ -237,17 +486,14 @@ extension TextView: MoleculeViewProtocol { setPlaceholderIfAvailable() if isEditable { + FormValidator.setupValidation(for: model, delegate: delegateObject?.formHolderDelegate) MVMCoreUICommonViewsUtility.addDismissToolbar(to: self, delegate: delegateObject?.uiTextViewDelegate) + + if model.selected ?? false { + isSelected = true + model.wasInitiallySelected = true + becomeFirstResponder() + } } } - - open func reset() { - - backgroundColor = .mvmWhite - text = "" - inputAccessoryView?.removeFromSuperview() - layer.borderColor = UIColor.mvmBlack.cgColor - contentInset = UIEdgeInsets(top: Padding.Three, left: Padding.Three, bottom: Padding.Three, right: Padding.Three) - layer.borderWidth = 0 - } } diff --git a/MVMCoreUI/BaseClasses/TextViewModel.swift b/MVMCoreUI/BaseClasses/TextViewModel.swift index 5efa4b5a..017ca1d1 100644 --- a/MVMCoreUI/BaseClasses/TextViewModel.swift +++ b/MVMCoreUI/BaseClasses/TextViewModel.swift @@ -27,8 +27,8 @@ open class TextViewModel: TextEntryFieldModel { public var placeholderFontStyle: Styler.Font = Styler.Font.RegularMicro public var showsPlaceholder: Bool = true public var isEditable: Bool = true - public var borderColor: Color? - public var borderWidth: CGFloat = 0 +// public var borderColor: Color? +// public var borderWidth: CGFloat = 0 //-------------------------------------------------- // MARK: - Keys @@ -44,12 +44,12 @@ open class TextViewModel: TextEntryFieldModel { case textAlignment case attributes case height - case borderColor - case borderWidth - case placeholder +// case borderColor +// case borderWidth +// case placeholder case placeholderFontStyle case isEditable = "editable" - case hideBlinkingCaret +// case hideBlinkingCaret } //-------------------------------------------------- @@ -76,15 +76,15 @@ open class TextViewModel: TextEntryFieldModel { self.isEditable = isEditable } - if let borderWidth = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .borderWidth) { - self.borderWidth = borderWidth - } +// if let borderWidth = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .borderWidth) { +// self.borderWidth = borderWidth +// } if let fontStyle = try typeContainer.decodeIfPresent(Styler.Font.self, forKey: .fontStyle) { self.fontStyle = fontStyle } - borderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .borderColor) +// borderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .borderColor) accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) height = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .height) } @@ -95,13 +95,13 @@ open class TextViewModel: TextEntryFieldModel { try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText) try container.encodeIfPresent(textColor, forKey: .textColor) try container.encodeIfPresent(fontStyle, forKey: .fontStyle) - try container.encodeIfPresent(borderColor, forKey: .borderColor) +// try container.encodeIfPresent(borderColor, forKey: .borderColor) try container.encodeIfPresent(height, forKey: .height) try container.encode(text, forKey: .text) - try container.encode(placeholder, forKey: .placeholder) +// try container.encode(placeholder, forKey: .placeholder) try container.encode(placeholderFontStyle, forKey: .placeholderFontStyle) try container.encode(textAlignment, forKey: .textAlignment) try container.encode(isEditable, forKey: .isEditable) - try container.encode(borderWidth, forKey: .borderWidth) +// try container.encode(borderWidth, forKey: .borderWidth) } }