You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

769 lines
23 KiB

//
// DevikTextField.swift
// GMETextFIeld
//
// Created by InKwon Devik Kim on 16/04/2019.
// Copyright © 2019 devikkim. All rights reserved.
//
import UIKit
import RxSwift
@IBDesignable open class ValidationTextField: UITextField {
@objc public enum ValidateTextFieldType: Int {
case dropdown
case `default`
}
// MARK: Left Flag View
private var leftViewPadding: CGFloat = 8
private var widthOfImageInLeftView: CGFloat = 35
private var leftFlagImageWidth: CGFloat = 0
// MARK: Heights
private var lineHeight: CGFloat = 0
private var selectedLineHeight: CGFloat = 0
// MARK: IBInspectable
@IBInspectable open var isShowTitle: Bool = true {
didSet {
update()
}
}
@IBInspectable open var isUseTitle: Bool = true {
didSet {
update()
}
}
@IBInspectable open var isLineHidden: Bool = false {
didSet {
update()
}
}
@IBInspectable open var titleText: String = "" {
didSet {
update()
}
}
@IBInspectable open var titleFont: UIFont = .sanfrancisco(.regular, size: 12) {
didSet {
update()
createTitleLabel()
}
}
@IBInspectable open var titleColor: UIColor = .themeText {
didSet {
update()
}
}
@IBInspectable open var errorColor: UIColor = .themeRed {
didSet {
update()
}
}
@IBInspectable open var errorFont: UIFont = .sanfrancisco(.regular, size: 12) {
didSet {
update()
createTitleLabel()
}
}
@IBInspectable open var errorMessage: String? {
didSet {
update()
}
}
@IBInspectable open var disabledColor: UIColor = UIColor(white: 0.88, alpha: 1.0) {
didSet {
update()
updatePlaceholder()
}
}
@IBInspectable open override var placeholder: String? {
didSet {
setNeedsDisplay()
update()
}
}
@IBInspectable open var placeholderColor: UIColor = UIColor(red:0.49, green:0.49, blue:0.49, alpha:1.0) {
didSet {
updatePlaceholder()
}
}
@IBInspectable open var leftImage: UIImage? {
didSet {
let width = textHeight() * 1.5
let height = textHeight()
leftImageView = UIImageView(frame:
CGRect(x: 0, y: 0, width: width, height: height)
)
let view = UIView()
view.addSubview(leftImageView)
leftImageView.image = leftImage
leftImageView.translatesAutoresizingMaskIntoConstraints = false
leftImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
leftImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
leftImageView.widthAnchor.constraint(equalToConstant: width).isActive = true
leftImageView.heightAnchor.constraint(equalToConstant: height).isActive = true
leftImageView.contentMode = .scaleAspectFit
leftContainerView = view
leftView = leftContainerView
leftViewMode = .always
leftFlagImageWidth = width
}
}
@IBInspectable open var leftImageTintColor: UIColor? {
didSet {
guard let color = leftImageTintColor else {
return
}
leftImageView.image = leftImage?.withRenderingMode(UIImage.RenderingMode.alwaysTemplate)
leftImageView.tintColor = color
}
}
// MARK: Properties
open var validCondition: ((String) -> Bool)? {
didSet {
editingChanged()
}
}
open var mobileNumberText: String? {
set {
text = newValue?.replacingOccurrences(of: countryCode?.phoneCode ?? "", with: "")
}
get {
return "\(countryCode?.phoneCode ?? "")\(text ?? "")"
}
}
private var countryCode: CountryEnum? {
didSet {
mobileNumberText = text
}
}
open var isValid = false
open var valid = BehaviorSubject<Bool>(value: false)
private lazy var leftImageView = UIImageView()
private var leftContainerView: UIView?
private lazy var titleLabel = UILabel()
private lazy var titleView = UIView()
private lazy var errorView = UIView()
private lazy var errorLabel = UILabel()
private lazy var lineView = UIView()
private var bankCode: BankEnum?
private var titleFadeInDuration: TimeInterval = 0.3
private var titleFadeOutDuration: TimeInterval = 0.5
private var placeholderFont: UIFont? {
didSet {
updatePlaceholder()
}
}
private var isTitleVisible: Bool {
return hasText
}
private var editingOrSelected: Bool {
return super.isEditing || isSelected
}
// MARK: DropDown
private var type: ValidateTextFieldType = .default {
didSet {
switch type {
case .default:
inputView = nil
tintColor = .themeText
case .dropdown:
inputView = UIView()
tintColor = .clear
let dropdownImage = UIImageView()
dropdownImage.setDropDownImage(tintColor: .themeTextColor)
rightView = dropdownImage
rightViewMode = .always
}
}
}
private lazy var tablePresenterWireframe = TablePresenterWireframe()
private var dropwDownItems: [TablePresenterProtocol]?
private var configure: TablePresenterConfiguration?
var selectedItem = BehaviorSubject<TablePresenterProtocol?>(value: nil)
// MARK: Initialize
override init(frame: CGRect) {
super.init(frame: frame)
initValidationTextField()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initValidationTextField()
}
private func initValidationTextField() {
borderStyle = .none
createLineView()
createErrorLabel()
createTitleLabel()
addTarget(self, action: #selector(editingDidBegin), for: .editingDidBegin)
addTarget(self, action: #selector(editingChanged), for: [.editingChanged, .valueChanged])
}
@objc private func editingDidBegin() {
isShowTitle = true
switch type {
case .dropdown:
guard let parentVC = parentContainerViewController() else {
return
}
if dropwDownItems != nil {
keyboardToolbar.isHidden = true
tablePresenterWireframe.openWith(
delegate: self,
model: dropwDownItems,
source: parentVC
)
}
default: ()
}
}
@objc private func editingChanged() {
if let isValid = validCondition?(self.text ?? ""), !isValid {
self.isValid = isValid
valid.onNext(isValid)
} else {
isValid = true
valid.onNext(isValid)
}
update()
updateTitleVisibility(true)
}
private func createTitleLabel() {
titleView = UIView()
titleView.backgroundColor = .white
titleLabel = UILabel()
titleView.autoresizingMask = [.flexibleWidth,. flexibleHeight]
titleView.addSubview(titleLabel)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
titleLabel.centerYAnchor.constraint(equalTo: titleView.centerYAnchor).isActive = true
titleLabel.centerXAnchor.constraint(equalTo: titleView.centerXAnchor).isActive = true
titleLabel.font = titleFont
titleLabel.textColor = titleColor
addSubview(titleView)
}
private func createLineView() {
lineView.isUserInteractionEnabled = false
lineView.backgroundColor = .clear
lineView.layer.borderColor = titleColor.cgColor
let pixel: CGFloat = 1.0 / UIScreen.main.scale
lineView.layer.borderWidth = 2.0 * pixel
lineView.layer.cornerRadius = 5
configureDefaultLineHeight()
lineView.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
addSubview(lineView)
}
private func createErrorLabel() {
errorView = UIView()
errorView.autoresizingMask = [.flexibleWidth,. flexibleHeight]
errorView.addSubview(errorLabel)
errorLabel.numberOfLines = 2
errorLabel.translatesAutoresizingMaskIntoConstraints = false
errorLabel.leadingAnchor.constraint(equalTo: errorView.leadingAnchor).isActive = true
errorLabel.trailingAnchor.constraint(equalTo: errorView.trailingAnchor).isActive = true
errorLabel.topAnchor.constraint(equalTo: errorView.topAnchor).isActive = true
// errorLabel.bottomAnchor.constraint(equalTo: errorView.bottomAnchor).isActive = true
addSubview(errorView)
}
private func titleHeight() -> CGFloat {
return self.titleLabel.font.lineHeight
}
private func titleWidth() -> CGFloat {
let width = CGFloat((titleText.count * 6))
return width
}
private func errorHeight() -> CGFloat {
return 30
}
private func configureDefaultLineHeight() {
let pixel: CGFloat = 1.0 / UIScreen.main.scale
lineHeight = 2.0 * pixel
selectedLineHeight = 2.0 * lineHeight
}
private func titleRectForBounds(_ bounds: CGRect, editing: Bool) -> CGRect {
if isShowTitle {
return CGRect(
x: 12,
y: -titleHeight()/2,
width: titleWidth(),
height: titleHeight()
)
} else {
if editing {
return CGRect(
x: 12,
y: -titleHeight()/2,
width: titleWidth(),
height: titleHeight()
)
} else {
return CGRect(
x: 12,
y: -titleHeight()/2,
width: titleWidth(),
height: titleHeight()
)
}
}
}
private func lineViewRectForBounds(_ bounds: CGRect, editing: Bool) -> CGRect {
// let height = editing ? selectedLineHeight : lineHeight
return CGRect(
x: 0,
y: 0,
width: bounds.size.width,
height: bounds.size.height
)
}
private func errorLabelRectForBounds(_ bounds: CGRect) -> CGRect {
if isValid {
return CGRect(
x: 12,
y: bounds.height + errorHeight(),
width: 0,
height: 0
)
} else {
return CGRect(
x: 10,
y: bounds.height,
width: bounds.size.width,
height: errorHeight()
)
}
}
private func textHeight() -> CGFloat {
guard let font = self.font else { return 0.0 }
return font.lineHeight + 3.0
}
private func updateTitleVisibility(_ animated: Bool = false) {
let alpha: CGFloat
if isShowTitle {
alpha = 1.0
} else {
alpha = isTitleVisible ? 1.0 : 0.0
}
let frame = titleRectForBounds(bounds, editing: isTitleVisible)
let errorAlpha: CGFloat = isValid || text == "" ? 0.0 : 1.0
let errorLabelFrame = errorLabelRectForBounds(bounds)
let updateBlock = {() -> Void in
if !self.isShowTitle {
self.titleView.alpha = alpha
self.titleView.frame = frame
}
self.errorView.alpha = errorAlpha
self.errorView.frame = errorLabelFrame
}
if animated {
#if swift(>=4.2)
let animationOptions: UIView.AnimationOptions = .curveEaseOut
#else
let animationOptions: UIViewAnimationOptions = .curveEaseOut
#endif
let duration = isTitleVisible ? titleFadeInDuration : titleFadeOutDuration
UIView.animate(
withDuration: duration,
delay: 0,
options: animationOptions,
animations: { () -> Void in
updateBlock()
},
completion: nil)
}
}
private func update() {
lineView.isHidden = isLineHidden
if !isValid && text != "" {
errorLabel.text = errorMessage
errorLabel.textColor = errorColor
errorLabel.font = errorFont
lineView.layer.borderColor = UIColor.themeRed.cgColor
titleLabel.textColor = UIColor.themeRed
}
if isUseTitle {
titleView.isHidden = false
titleView.alpha = 1.0
} else {
titleView.isHidden = true
titleView.alpha = 0.0
}
titleLabel.text = titleText
// titleLabel.textColor = titleColor
titleLabel.font = titleFont
updateTitleVisibility(true)
if !isEnabled {
lineView.backgroundColor = disabledColor
}
}
private func updatePlaceholder() {
guard let placeholder = placeholder, let font = placeholderFont ?? font else {
return
}
let color = isEnabled ? placeholderColor : disabledColor
#if swift(>=4.2)
attributedPlaceholder = NSAttributedString(
string: placeholder,
attributes: [
NSAttributedString.Key.foregroundColor: color, NSAttributedString.Key.font: font
]
)
#elseif swift(>=4.0)
attributedPlaceholder = NSAttributedString(
string: placeholder,
attributes: [
NSAttributedStringKey.foregroundColor: color, NSAttributedStringKey.font: font
]
)
#else
attributedPlaceholder = NSAttributedString(
string: placeholder,
attributes: [NSForegroundColorAttributeName: color, NSFontAttributeName: font]
)
#endif
}
override open func textRect(forBounds bounds: CGRect) -> CGRect {
let superRect = super.textRect(forBounds: bounds)
let titleHeight = self.titleHeight()
let padding: CGFloat = leftImage == nil ? 10 : 10
return CGRect(
x: superRect.origin.x + padding,
y: titleHeight - 5,
width: superRect.size.width - padding,
height: superRect.size.height - titleHeight - selectedLineHeight
)
}
override open func editingRect(forBounds bounds: CGRect) -> CGRect {
let superRect = super.editingRect(forBounds: bounds)
let titleHeight = self.titleHeight()
let padding: CGFloat = leftImage == nil ? 10 : 10
return CGRect(
x: superRect.origin.x + padding,
y: titleHeight - 5,
width: superRect.size.width,
height: superRect.size.height - titleHeight - selectedLineHeight
)
}
override open func placeholderRect(forBounds bounds: CGRect) -> CGRect {
let superRect = super.editingRect(forBounds: bounds)
let titleHeight = self.titleHeight()
let padding: CGFloat = leftImage == nil ? 10 : 10
let rect = CGRect(
x: superRect.origin.x + padding,
y: titleHeight - 5,
width: bounds.size.width - leftFlagImageWidth - padding,
height: bounds.size.height - titleHeight - selectedLineHeight
)
return rect
}
override open func leftViewRect(forBounds bounds: CGRect) -> CGRect {
let titleHeight = self.titleHeight()
let rect = CGRect (
x: 10,
y: titleHeight - 5,
width: leftFlagImageWidth,
height: bounds.size.height - titleHeight - selectedLineHeight
)
return rect
}
override open func rightViewRect(forBounds bounds: CGRect) -> CGRect {
let titleHeight = self.titleHeight()
let size = bounds.size.height - titleHeight - selectedLineHeight
return CGRect(
x: bounds.width - size - 10,
y: titleHeight - 5,
width: size,
height: size
)
}
override open func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
borderStyle = .none
isSelected = true
invalidateIntrinsicContentSize()
}
override open func layoutSubviews() {
super.layoutSubviews()
self.titleView.frame = titleRectForBounds(
bounds,
editing: isTitleVisible
)
self.textColor = .black
self.lineView.frame = lineViewRectForBounds(
bounds,
editing: editingOrSelected
)
if editingOrSelected {
if isValid{
lineView.layer.borderColor = UIColor.themeText.cgColor
titleLabel.textColor = UIColor.themeText
}else{
lineView.layer.borderColor = UIColor.themeRed.cgColor
titleLabel.textColor = UIColor.themeRed
}
} else if (!isValid && text != "") {
lineView.layer.borderColor = UIColor.themeRed.cgColor
titleLabel.textColor = UIColor.themeRed
} else if !editingOrSelected && text == ""{
titleView.isHidden = true
titleView.alpha = 0.0
lineView.layer.borderColor = UIColor.themeText.cgColor
}else {
lineView.layer.borderColor = UIColor.themeText.cgColor
titleLabel.textColor = UIColor.themeText
}
}
override open var intrinsicContentSize: CGSize {
return CGSize(width: bounds.size.width, height: titleHeight() + textHeight() + 10)
}
}
extension ValidationTextField {
func setCountry(with code: String?, isShowCode: Bool = true) {
guard
let code = code?.lowercased(),
let countryCode = CountryEnum(rawValue: code) else {
leftView = nil
return
}
setCountry(with: countryCode, isShowCode: isShowCode)
}
func setCountry(with code: CountryEnum, isShowCode: Bool = true) {
countryCode = code
widthOfImageInLeftView = 35
let textArea = textRect(forBounds: bounds)
let imageView = UIImageView(
frame: CGRect(
x: 0,
y: 0,
width: widthOfImageInLeftView,
height: textArea.height
)
)
imageView.contentMode = .scaleAspectFit
imageView.image = code.flag
let label = UILabel()
label.font = font ?? UIFont.systemFont(ofSize: 14)
label.textColor = textColor ?? .black
label.text = code.phoneCode
label.frame = CGRect(
x: widthOfImageInLeftView + leftViewPadding,
y: 0,
width: label.intrinsicContentSize.width,
height: textArea.height
)
var width: CGFloat = widthOfImageInLeftView +
leftViewPadding +
label.intrinsicContentSize.width +
leftViewPadding
if !isShowCode {
width = widthOfImageInLeftView + leftViewPadding
label.isHidden = !isShowCode
}
let paddingView = UIView(
frame: CGRect(
x: 0,
y: textArea.origin.y,
width: width,
height: textArea.height
)
)
paddingView.addSubview(imageView)
paddingView.addSubview(label)
paddingView.backgroundColor = .clear
leftFlagImageWidth = paddingView.frame.width
leftViewMode = .always
leftView = paddingView
}
func setBank(with code: String?) {
guard
let code = code?.lowercased(),
let bankCode = BankEnum(rawValue: code) else {
leftView = nil
return
}
setBank(with: bankCode)
}
func setBank(with code: BankEnum) {
leftImageView.removeFromSuperview()
leftImage = code.ciImage
}
}
extension ValidationTextField {
func useAsDropDown(with configure: TablePresenterConfiguration? = nil, items: [TablePresenterProtocol]?) {
func isEqual(target1: [TablePresenterProtocol]?, target2: [TablePresenterProtocol]?) -> Bool {
guard let model1 = target1, let model2 = target2 else { return false}
if model1.count != model2.count {
return false
}
for i in 0..<model1.count where model1[i].cellTitle != model2[i].cellTitle {
return false
}
return true
}
if !isEqual(target1: dropwDownItems, target2: items) {
selectedItem.onNext(nil)
text = nil
sendActions(for: .editingChanged)
}
self.type = .dropdown
self.dropwDownItems = items
self.configure = configure
}
func didSelect(item: TablePresenterProtocol?) {
selectedItem.onNext(item)
text = item?.cellTitle
sendActions(for: .editingChanged)
}
}
extension ValidationTextField: TablePresenterDelegate {
func tablePresenterView(_ viewController: TablePresenterViewController) -> TablePresenterConfiguration {
guard let configure = self.configure else {
return TablePresenterConfiguration(
presenterTitle: "DropDown",
closeButtonTitle: "cancel_text".localized(),
notFoundTitle: "no_result_found_text".localized(),
searchBarPlaceHolder: "Search"
)
}
return configure
}
func tablePresenterView(
_ viewController: TablePresenterViewController,
didSelectModel model: TablePresenterProtocol?
) {
didSelect(item: model)
}
}