293 lines
11 KiB
Swift
293 lines
11 KiB
Swift
//
|
|
// CommonViewTextField.swift
|
|
// NavigationAnimationTest
|
|
//
|
|
// Created by Igor on 10.02.2021.
|
|
//
|
|
|
|
import UIKit
|
|
|
|
@objc protocol CommonTextFieldDelegate: AnyObject {
|
|
@objc optional func textFieldShouldBeginEditing(_ textField: CommonTextField) -> Bool
|
|
@objc optional func textFieldDidBeginEditing(_ textField: CommonTextField)
|
|
@objc optional func textFieldShouldEndEditing(_ textField: CommonTextField) -> Bool
|
|
@objc optional func textFieldDidEndEditing(_ textField: CommonTextField)
|
|
@objc optional func textFieldDidChange(_ textField: CommonTextField)
|
|
@objc optional func textField(_ textField: CommonTextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
|
|
@objc optional func textFieldShouldClear(_ textField: CommonTextField) -> Bool
|
|
@objc optional func textFieldShouldSubmit(_ textField: CommonTextField) -> Bool
|
|
@objc optional func textFieldDidSubmit(_ textField: CommonTextField)
|
|
}
|
|
|
|
class CommonTextField: CommonControlCustom, CommonMenuSender {
|
|
|
|
@IBOutlet private weak var titleLbl: UILabel!
|
|
@IBOutlet weak var textField: UITextField!
|
|
@IBOutlet private weak var placeholderLbl: UILabel!
|
|
@IBOutlet weak var line1View: UIView!
|
|
@IBOutlet weak var line2View: UIView!
|
|
@IBOutlet private weak var textLbl: UILabel!
|
|
@IBOutlet private weak var titleOffset: NSLayoutConstraint!
|
|
@IBOutlet private weak var textOffset: NSLayoutConstraint!
|
|
@IBOutlet private weak var lineOffset1: NSLayoutConstraint!
|
|
@IBOutlet private weak var lineOffset2: NSLayoutConstraint!
|
|
|
|
@IBOutlet weak var delegate: CommonTextFieldDelegate?
|
|
|
|
private let fontSize: CGFloat = 20
|
|
|
|
private var _value = "" {
|
|
didSet { updateText() }
|
|
}
|
|
var value: String {
|
|
get { _value }
|
|
set { set(value: newValue, animated: false) }
|
|
}
|
|
|
|
func set(value: String, animated: Bool) {
|
|
_value = value
|
|
update(animated: animated)
|
|
}
|
|
|
|
var suffix = "" {
|
|
didSet { updateText() }
|
|
}
|
|
|
|
var symbolsToBeRemovedOnDuplication: Set<Character> {
|
|
self.keyboardType == .decimalPad ? [",", "."] : []
|
|
}
|
|
|
|
/// Updating text and **cursor** to evaluate text position [SHOULD BE REFACTORED WITH THIS CLASS]
|
|
private func updateText() {
|
|
let value = action?.menu?.title ?? _value
|
|
|
|
guard !value.isEmpty else {
|
|
self.textField.text = .init()
|
|
return
|
|
}
|
|
|
|
guard textField.text != value else { return }
|
|
|
|
let oldOffset = (textField.selectedTextRange?.start ?? textField.endOfDocument)
|
|
let shiftDirection = (textField.text?.count ?? 0) < value.count ? UITextLayoutDirection.right : .left
|
|
|
|
textField.text = value.isEmpty ? nil : (isFirstResponder ? value : value.appending(suffix))
|
|
|
|
if isFirstResponder {
|
|
let newPosition = textField.position(from: oldOffset, in: shiftDirection, offset: 1)
|
|
textField.selectedTextRange = textField.textRange(from: newPosition ?? textField.endOfDocument,
|
|
to: newPosition ?? textField.endOfDocument)
|
|
}
|
|
}
|
|
|
|
@IBInspectable var lzTitle: String? {
|
|
didSet { update(animated: false) }
|
|
}
|
|
@IBInspectable var lzPlaceholder: String? {
|
|
didSet { update(animated: false) }
|
|
}
|
|
private var _lzText: String?
|
|
@IBInspectable var lzText: String? {
|
|
get { _lzText }
|
|
set { set(text: newValue, animated: false) }
|
|
}
|
|
private var _error: String?
|
|
@IBInspectable var error: String? {
|
|
get { _error }
|
|
set { set(error: newValue, animated: false) }
|
|
}
|
|
var precision: Int?
|
|
|
|
@discardableResult
|
|
override func resignFirstResponder() -> Bool { textField.resignFirstResponder() }
|
|
override var isFirstResponder: Bool { textField.isFirstResponder }
|
|
@discardableResult
|
|
override func becomeFirstResponder() -> Bool { textField.becomeFirstResponder() }
|
|
|
|
var keyboardType: UIKeyboardType {
|
|
get { textField.keyboardType }
|
|
set { textField.keyboardType = newValue }
|
|
}
|
|
|
|
override open var isEnabled: Bool {
|
|
get { textField.isEnabled }
|
|
set { textField.isEnabled = newValue }
|
|
}
|
|
|
|
var autocorrectionType: UITextAutocorrectionType {
|
|
get { textField.autocorrectionType }
|
|
set { textField.autocorrectionType = newValue }
|
|
}
|
|
|
|
var returnKeyType: UIReturnKeyType {
|
|
get { textField.returnKeyType }
|
|
set { textField.returnKeyType = newValue }
|
|
}
|
|
|
|
var autocapitalizationType: UITextAutocapitalizationType {
|
|
get { textField.autocapitalizationType }
|
|
set { textField.autocapitalizationType = newValue }
|
|
}
|
|
|
|
var isSecure: Bool {
|
|
get { textField.isSecureTextEntry }
|
|
set { textField.isSecureTextEntry = newValue }
|
|
}
|
|
|
|
var accessoryView: UIView? {
|
|
didSet { update(animated: false) }
|
|
}
|
|
|
|
var action: CommonMenuAction? {
|
|
didSet { update(animated: false) }
|
|
}
|
|
|
|
var isLoading: Bool = false {
|
|
didSet { update(animated: true) }
|
|
}
|
|
|
|
override func setup() {
|
|
super.setup()
|
|
backgroundColor = .clear
|
|
textField.clearButtonMode = .never
|
|
textField.autocorrectionType = .no
|
|
textField.rightViewMode = .always
|
|
textField.returnKeyType = .done
|
|
textField.autocapitalizationType = .none
|
|
textField.font = FontFamily.GolosUI.regular.font(size: fontSize)
|
|
textField.textColor = Asset.textCoal.color
|
|
line2View.backgroundColor = Asset.deepWater.color
|
|
update(animated: false)
|
|
}
|
|
|
|
func update(animated: Bool) {
|
|
let frame = self.frame
|
|
if isLoading {
|
|
let indicator = UIActivityIndicatorView(style: .medium)
|
|
indicator.startAnimating()
|
|
textField.rightView = indicator
|
|
layoutIfNeeded()
|
|
} else if isUserInteractionEnabled && ((isFirstResponder && !value.isEmpty) || !(error?.isEmpty ?? true)) {
|
|
let btn = UIButton(type: .custom)
|
|
btn.setImage(Asset.commonTextfieldClear.image, for: .normal)
|
|
btn.frame.size = Asset.commonTextfieldClear.image.size
|
|
btn.addTarget(self, action: #selector(onClear), for: .touchUpInside)
|
|
textField.rightView = btn
|
|
layoutIfNeeded()
|
|
} else if let view = accessoryView {
|
|
if let button = view as? UIButton {
|
|
textField.rightView = button
|
|
} else {
|
|
textField.rightView = view
|
|
}
|
|
layoutIfNeeded()
|
|
} else if let icon = action?.icon {
|
|
textField.rightView = UIImageView(image: icon)
|
|
layoutIfNeeded()
|
|
} else {
|
|
textField.rightView = nil
|
|
}
|
|
updateText()
|
|
let value = action?.menu?.title ?? _value
|
|
titleOffset.constant = value.isEmpty ? 10 : 0
|
|
titleLbl.attributedText = lzTitle?.localized.attributed(style: .regular, size: 14, color: Asset.textGranite.color)
|
|
placeholderLbl.attributedText = lzPlaceholder?.localized.attributed(style: .regular, size: fontSize, color: Asset.textPebble.color)
|
|
if let error = error, !error.isEmpty {
|
|
textOffset.constant = 4
|
|
textLbl.attributedText = error.localized.attributed(style: .regular, size: 12, color: Asset.textBrick.color)
|
|
line1View.backgroundColor = Asset.brick.color
|
|
} else {
|
|
textLbl.attributedText = (action?.menu?.text ?? lzText)?.localized.attributed(style: .regular, size: 12, color: Asset.textGranite.color)
|
|
line1View.backgroundColor = Asset.pebble.color
|
|
textOffset.constant = (action?.menu?.text ?? lzText ?? "").isEmpty ? 0 : 4
|
|
}
|
|
lineOffset1.constant = isFirstResponder ? 0 : frame.width/2
|
|
lineOffset2.constant = isFirstResponder ? 0 : frame.width/2
|
|
placeholderLbl.isHidden = !value.isEmpty
|
|
animateIfNeeded(duration: animated ? .slow : .none) { [weak self] in
|
|
guard let self else { return }
|
|
self.titleLbl.alpha = value.isEmpty ? 0 : 1
|
|
self.line1View.alpha = self.isFirstResponder ? 0 : 1
|
|
}
|
|
}
|
|
|
|
@objc private func onClear() {
|
|
_error = nil
|
|
set(value: "", animated: true)
|
|
delegate?.textFieldDidChange?(self)
|
|
becomeFirstResponder()
|
|
}
|
|
|
|
func set(text: String?, animated: Bool) {
|
|
_lzText = text
|
|
update(animated: animated)
|
|
}
|
|
|
|
func set(error: String?, animated: Bool) {
|
|
_error = error
|
|
update(animated: animated)
|
|
}
|
|
}
|
|
|
|
extension CommonTextField: UITextFieldDelegate {
|
|
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
|
|
condition(
|
|
action == nil,
|
|
true: { self.delegate?.textFieldShouldBeginEditing?(self) ?? true },
|
|
false: { self.action?.perform(sender: self);return false }
|
|
) ?? false
|
|
}
|
|
|
|
func textFieldDidBeginEditing(_ textField: UITextField) {
|
|
set(error: nil, animated: true)
|
|
delegate?.textFieldDidBeginEditing?(self)
|
|
}
|
|
|
|
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { delegate?.textFieldShouldEndEditing?(self) ?? true }
|
|
func textFieldDidEndEditing(_ textField: UITextField) {
|
|
delegate?.textFieldDidEndEditing?(self)
|
|
update(animated: true)
|
|
}
|
|
|
|
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
|
condition(
|
|
delegate?.textField?(self, shouldChangeCharactersIn: range, replacementString: string) ?? true,
|
|
true: {
|
|
self._error = nil
|
|
if self.keyboardType == .decimalPad, let precision = self.precision {
|
|
self.set(value: self.value.replacingCharacters(in: range, with: string)
|
|
.removeDuplication(of: self.symbolsToBeRemovedOnDuplication)
|
|
.removingWhitespaceAndNewlines()
|
|
.normalized(precision: precision), animated: true)
|
|
} else if self.keyboardType == .decimalPad {
|
|
self.set(value: self.value.replacingCharacters(in: range, with: string)
|
|
.removeDuplication(of: self.symbolsToBeRemovedOnDuplication)
|
|
.removingWhitespaceAndNewlines(),
|
|
animated: true)
|
|
} else {
|
|
self.set(value: self.value.replacingCharacters(in: range, with: string)
|
|
.removeDuplication(of: self.symbolsToBeRemovedOnDuplication),
|
|
animated: true)
|
|
}
|
|
self.delegate?.textFieldDidChange?(self)
|
|
|
|
return false
|
|
}
|
|
) ?? false
|
|
}
|
|
|
|
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
|
condition(
|
|
delegate?.textFieldShouldSubmit?(self) ?? true,
|
|
true: {
|
|
self.resignFirstResponder()
|
|
self.delegate?.textFieldDidSubmit?(self)
|
|
|
|
return false
|
|
}
|
|
) ?? false
|
|
}
|
|
|
|
func textFieldShouldClear(_ textField: UITextField) -> Bool { delegate?.textFieldShouldClear?(self) ?? true }
|
|
}
|