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.

724 lines
18 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. //
  2. // DevikTextField.swift
  3. // GMETextFIeld
  4. //
  5. // Created by InKwon Devik Kim on 16/04/2019.
  6. // Copyright © 2019 devikkim. All rights reserved.
  7. //
  8. import UIKit
  9. import RxSwift
  10. @IBDesignable open class ValidationTextField: UITextField {
  11. @objc public enum ValidateTextFieldType: Int {
  12. case dropdown
  13. case `default`
  14. }
  15. // MARK: Left Flag View
  16. private var leftViewPadding: CGFloat = 8
  17. private var widthOfImageInLeftView: CGFloat = 35
  18. private var leftFlagImageWidth: CGFloat = 0
  19. // MARK: Heights
  20. private var lineHeight: CGFloat = 0
  21. private var selectedLineHeight: CGFloat = 0
  22. // MARK: IBInspectable
  23. @IBInspectable open var isShowTitle: Bool = true {
  24. didSet {
  25. update()
  26. }
  27. }
  28. @IBInspectable open var isUseTitle: Bool = true {
  29. didSet {
  30. update()
  31. }
  32. }
  33. @IBInspectable open var isLineHidden: Bool = false {
  34. didSet {
  35. update()
  36. }
  37. }
  38. @IBInspectable open var titleText: String = "TITLE" {
  39. didSet {
  40. update()
  41. }
  42. }
  43. @IBInspectable open var titleFont: UIFont = .sanfrancisco(.regular, size: 12) {
  44. didSet {
  45. update()
  46. createTitleLabel()
  47. }
  48. }
  49. @IBInspectable open var titleColor: UIColor = .themeTitleTextColor {
  50. didSet {
  51. update()
  52. }
  53. }
  54. @IBInspectable open var errorColor: UIColor = .themeRed {
  55. didSet {
  56. update()
  57. }
  58. }
  59. @IBInspectable open var errorFont: UIFont = .sanfrancisco(.regular, size: 12) {
  60. didSet {
  61. update()
  62. createTitleLabel()
  63. }
  64. }
  65. @IBInspectable open var errorMessage: String? {
  66. didSet {
  67. update()
  68. }
  69. }
  70. @IBInspectable open var disabledColor: UIColor = UIColor(white: 0.88, alpha: 1.0) {
  71. didSet {
  72. update()
  73. updatePlaceholder()
  74. }
  75. }
  76. @IBInspectable open override var placeholder: String? {
  77. didSet {
  78. setNeedsDisplay()
  79. update()
  80. }
  81. }
  82. @IBInspectable open var placeholderColor: UIColor = UIColor(red:0.49, green:0.49, blue:0.49, alpha:1.0) {
  83. didSet {
  84. updatePlaceholder()
  85. }
  86. }
  87. @IBInspectable open var leftImage: UIImage? {
  88. didSet {
  89. let width = textHeight() * 1.5
  90. let height = textHeight()
  91. leftImageView = UIImageView(frame:
  92. CGRect(x: 0, y: 0, width: width, height: height)
  93. )
  94. let view = UIView()
  95. view.addSubview(leftImageView)
  96. leftImageView.image = leftImage
  97. leftImageView.translatesAutoresizingMaskIntoConstraints = false
  98. leftImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
  99. leftImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
  100. leftImageView.widthAnchor.constraint(equalToConstant: width).isActive = true
  101. leftImageView.heightAnchor.constraint(equalToConstant: height).isActive = true
  102. leftImageView.contentMode = .scaleAspectFit
  103. leftContainerView = view
  104. leftView = leftContainerView
  105. leftViewMode = .always
  106. leftFlagImageWidth = width
  107. }
  108. }
  109. @IBInspectable open var leftImageTintColor: UIColor? {
  110. didSet {
  111. guard let color = leftImageTintColor else {
  112. return
  113. }
  114. leftImageView.image = leftImage?.withRenderingMode(UIImage.RenderingMode.alwaysTemplate)
  115. leftImageView.tintColor = color
  116. }
  117. }
  118. // MARK: Properties
  119. open var validCondition: ((String) -> Bool)? {
  120. didSet {
  121. editingChanged()
  122. }
  123. }
  124. open var mobileNumberText: String? {
  125. set {
  126. text = newValue?.replacingOccurrences(of: countryCode?.phoneCode ?? "", with: "")
  127. }
  128. get {
  129. return "\(countryCode?.phoneCode ?? "")\(text ?? "")"
  130. }
  131. }
  132. private var countryCode: CountryEnum? {
  133. didSet {
  134. mobileNumberText = text
  135. }
  136. }
  137. open var isValid = false
  138. open var valid = BehaviorSubject<Bool>(value: false)
  139. private lazy var leftImageView = UIImageView()
  140. private var leftContainerView: UIView?
  141. private lazy var titleLabel = UILabel()
  142. private lazy var errorLabel = UILabel()
  143. private lazy var lineView = UIView()
  144. private var bankCode: BankEnum?
  145. private var titleFadeInDuration: TimeInterval = 0.3
  146. private var titleFadeOutDuration: TimeInterval = 0.5
  147. private var placeholderFont: UIFont? {
  148. didSet {
  149. updatePlaceholder()
  150. }
  151. }
  152. private var isTitleVisible: Bool {
  153. return hasText
  154. }
  155. private var editingOrSelected: Bool {
  156. return super.isEditing || isSelected
  157. }
  158. // MARK: DropDown
  159. private var type: ValidateTextFieldType = .default {
  160. didSet {
  161. switch type {
  162. case .default:
  163. inputView = nil
  164. tintColor = .themeText
  165. case .dropdown:
  166. inputView = UIView()
  167. tintColor = .clear
  168. let dropdownImage = UIImageView()
  169. dropdownImage.setDropDownImage(tintColor: .themeTextColor)
  170. rightView = dropdownImage
  171. rightViewMode = .always
  172. }
  173. }
  174. }
  175. private lazy var tablePresenterWireframe = TablePresenterWireframe()
  176. private var dropwDownItems: [TablePresenterProtocol]?
  177. private var configure: TablePresenterConfiguration?
  178. var selectedItem = BehaviorSubject<TablePresenterProtocol?>(value: nil)
  179. // MARK: Initialize
  180. override init(frame: CGRect) {
  181. super.init(frame: frame)
  182. initValidationTextField()
  183. }
  184. required public init?(coder aDecoder: NSCoder) {
  185. super.init(coder: aDecoder)
  186. initValidationTextField()
  187. }
  188. private func initValidationTextField() {
  189. borderStyle = .none
  190. createTitleLabel()
  191. createLineView()
  192. createErrorLabel()
  193. addTarget(self, action: #selector(editingDidBegin), for: .editingDidBegin)
  194. addTarget(self, action: #selector(editingChanged), for: [.editingChanged, .valueChanged])
  195. }
  196. @objc private func editingDidBegin() {
  197. switch type {
  198. case .dropdown:
  199. guard let parentVC = parentContainerViewController() else {
  200. return
  201. }
  202. if dropwDownItems != nil {
  203. keyboardToolbar.isHidden = true
  204. tablePresenterWireframe.openWith(
  205. delegate: self,
  206. model: dropwDownItems,
  207. source: parentVC
  208. )
  209. }
  210. default: ()
  211. }
  212. }
  213. @objc private func editingChanged() {
  214. if let isValid = validCondition?(self.text ?? ""), !isValid {
  215. self.isValid = isValid
  216. valid.onNext(isValid)
  217. } else {
  218. isValid = true
  219. valid.onNext(isValid)
  220. }
  221. update()
  222. updateTitleVisibility(true)
  223. }
  224. private func createTitleLabel() {
  225. titleLabel = UILabel()
  226. titleLabel.autoresizingMask = [.flexibleWidth,. flexibleHeight]
  227. titleLabel.font = titleFont
  228. titleLabel.textColor = titleColor
  229. addSubview(titleLabel)
  230. }
  231. private func createLineView() {
  232. lineView.isUserInteractionEnabled = false
  233. lineView.backgroundColor = titleColor
  234. configureDefaultLineHeight()
  235. lineView.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
  236. addSubview(lineView)
  237. }
  238. private func createErrorLabel() {
  239. errorLabel.minimumScaleFactor = 0.5
  240. errorLabel.adjustsFontSizeToFitWidth = true
  241. errorLabel.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
  242. addSubview(errorLabel)
  243. }
  244. private func titleHeight() -> CGFloat {
  245. return titleLabel.font.lineHeight
  246. }
  247. private func errorHeight() -> CGFloat {
  248. return errorLabel.font.lineHeight
  249. }
  250. private func configureDefaultLineHeight() {
  251. let pixel: CGFloat = 1.0 / UIScreen.main.scale
  252. lineHeight = 2.0 * pixel
  253. selectedLineHeight = 2.0 * lineHeight
  254. }
  255. private func titleRectForBounds(_ bounds: CGRect, editing: Bool) -> CGRect {
  256. if isShowTitle {
  257. return CGRect(
  258. x: 0,
  259. y: 0,
  260. width: bounds.size.width,
  261. height: titleHeight()
  262. )
  263. } else {
  264. if editing {
  265. return CGRect(
  266. x: 0,
  267. y: 0,
  268. width: bounds.size.width,
  269. height: titleHeight()
  270. )
  271. } else {
  272. return CGRect(
  273. x: 0,
  274. y: titleHeight(),
  275. width: bounds.size.width,
  276. height: titleHeight()
  277. )
  278. }
  279. }
  280. }
  281. private func lineViewRectForBounds(_ bounds: CGRect, editing: Bool) -> CGRect {
  282. let height = editing ? selectedLineHeight : lineHeight
  283. return CGRect(
  284. x: 0,
  285. y: bounds.size.height - height,
  286. width: bounds.size.width,
  287. height: height
  288. )
  289. }
  290. private func errorLabelRectForBounds(_ bounds: CGRect) -> CGRect {
  291. if isValid {
  292. return CGRect(
  293. x: 0,
  294. y: bounds.height + errorHeight(),
  295. width: 0,
  296. height: 0
  297. )
  298. } else {
  299. return CGRect(
  300. x: 0,
  301. y: bounds.height,
  302. width: bounds.size.width,
  303. height: errorHeight()
  304. )
  305. }
  306. }
  307. private func textHeight() -> CGFloat {
  308. guard let font = self.font else { return 0.0 }
  309. return font.lineHeight + 3.0
  310. }
  311. private func updateTitleVisibility(_ animated: Bool = false) {
  312. let alpha: CGFloat
  313. if isShowTitle {
  314. alpha = 1.0
  315. } else {
  316. alpha = isTitleVisible ? 1.0 : 0.0
  317. }
  318. let frame = titleRectForBounds(bounds, editing: isTitleVisible)
  319. let errorAlpha: CGFloat = isValid || text == "" ? 0.0 : 1.0
  320. let errorLabelFrame = errorLabelRectForBounds(bounds)
  321. let updateBlock = {() -> Void in
  322. if !self.isShowTitle {
  323. self.titleLabel.alpha = alpha
  324. self.titleLabel.frame = frame
  325. }
  326. self.errorLabel.alpha = errorAlpha
  327. self.errorLabel.frame = errorLabelFrame
  328. }
  329. if animated {
  330. #if swift(>=4.2)
  331. let animationOptions: UIView.AnimationOptions = .curveEaseOut
  332. #else
  333. let animationOptions: UIViewAnimationOptions = .curveEaseOut
  334. #endif
  335. let duration = isTitleVisible ? titleFadeInDuration : titleFadeOutDuration
  336. UIView.animate(
  337. withDuration: duration,
  338. delay: 0,
  339. options: animationOptions,
  340. animations: { () -> Void in
  341. updateBlock()
  342. },
  343. completion: nil)
  344. }
  345. }
  346. private func update() {
  347. lineView.isHidden = isLineHidden
  348. if !isValid && text != "" {
  349. errorLabel.text = errorMessage
  350. errorLabel.textColor = errorColor
  351. errorLabel.font = errorFont
  352. lineView.backgroundColor = errorColor
  353. } else {
  354. lineView.backgroundColor = titleColor
  355. }
  356. if isUseTitle {
  357. titleLabel.isHidden = false
  358. titleLabel.alpha = 1.0
  359. } else {
  360. titleLabel.isHidden = true
  361. titleLabel.alpha = 0.0
  362. }
  363. titleLabel.text = titleText
  364. titleLabel.textColor = titleColor
  365. titleLabel.font = titleFont
  366. updateTitleVisibility(true)
  367. if !isEnabled {
  368. lineView.backgroundColor = disabledColor
  369. }
  370. }
  371. private func updatePlaceholder() {
  372. guard let placeholder = placeholder, let font = placeholderFont ?? font else {
  373. return
  374. }
  375. let color = isEnabled ? placeholderColor : disabledColor
  376. #if swift(>=4.2)
  377. attributedPlaceholder = NSAttributedString(
  378. string: placeholder,
  379. attributes: [
  380. NSAttributedString.Key.foregroundColor: color, NSAttributedString.Key.font: font
  381. ]
  382. )
  383. #elseif swift(>=4.0)
  384. attributedPlaceholder = NSAttributedString(
  385. string: placeholder,
  386. attributes: [
  387. NSAttributedStringKey.foregroundColor: color, NSAttributedStringKey.font: font
  388. ]
  389. )
  390. #else
  391. attributedPlaceholder = NSAttributedString(
  392. string: placeholder,
  393. attributes: [NSForegroundColorAttributeName: color, NSFontAttributeName: font]
  394. )
  395. #endif
  396. }
  397. override open func textRect(forBounds bounds: CGRect) -> CGRect {
  398. let superRect = super.textRect(forBounds: bounds)
  399. let titleHeight = self.titleHeight()
  400. let padding: CGFloat = leftImage == nil ? 0 : 10
  401. return CGRect(
  402. x: superRect.origin.x + padding,
  403. y: titleHeight,
  404. width: superRect.size.width - padding,
  405. height: superRect.size.height - titleHeight - selectedLineHeight
  406. )
  407. }
  408. override open func editingRect(forBounds bounds: CGRect) -> CGRect {
  409. let superRect = super.editingRect(forBounds: bounds)
  410. let titleHeight = self.titleHeight()
  411. let padding: CGFloat = leftImage == nil ? 0 : 10
  412. return CGRect(
  413. x: superRect.origin.x + padding,
  414. y: titleHeight,
  415. width: superRect.size.width,
  416. height: superRect.size.height - titleHeight - selectedLineHeight
  417. )
  418. }
  419. override open func placeholderRect(forBounds bounds: CGRect) -> CGRect {
  420. let superRect = super.editingRect(forBounds: bounds)
  421. let titleHeight = self.titleHeight()
  422. let padding: CGFloat = leftImage == nil ? 0 : 10
  423. let rect = CGRect(
  424. x: superRect.origin.x + padding,
  425. y: titleHeight,
  426. width: bounds.size.width - leftFlagImageWidth - padding,
  427. height: bounds.size.height - titleHeight - selectedLineHeight
  428. )
  429. return rect
  430. }
  431. override open func leftViewRect(forBounds bounds: CGRect) -> CGRect {
  432. let titleHeight = self.titleHeight()
  433. let rect = CGRect (
  434. x: 0,
  435. y: titleHeight,
  436. width: leftFlagImageWidth,
  437. height: bounds.size.height - titleHeight - selectedLineHeight
  438. )
  439. return rect
  440. }
  441. override open func rightViewRect(forBounds bounds: CGRect) -> CGRect {
  442. let titleHeight = self.titleHeight()
  443. let size = bounds.size.height - titleHeight - selectedLineHeight
  444. return CGRect(
  445. x: bounds.width - size,
  446. y: titleHeight,
  447. width: size,
  448. height: size
  449. )
  450. }
  451. override open func prepareForInterfaceBuilder() {
  452. super.prepareForInterfaceBuilder()
  453. borderStyle = .none
  454. isSelected = true
  455. invalidateIntrinsicContentSize()
  456. }
  457. override open func layoutSubviews() {
  458. super.layoutSubviews()
  459. self.titleLabel.frame = titleRectForBounds(
  460. bounds,
  461. editing: isTitleVisible
  462. )
  463. self.lineView.frame = lineViewRectForBounds(
  464. bounds,
  465. editing: editingOrSelected
  466. )
  467. self.errorLabel.frame = errorLabelRectForBounds(bounds)
  468. }
  469. override open var intrinsicContentSize: CGSize {
  470. return CGSize(width: bounds.size.width, height: titleHeight() + textHeight() + 10)
  471. }
  472. }
  473. extension ValidationTextField {
  474. func setCountry(with code: String?, isShowCode: Bool = true) {
  475. guard
  476. let code = code?.lowercased(),
  477. let countryCode = CountryEnum(rawValue: code) else {
  478. leftView = nil
  479. return
  480. }
  481. setCountry(with: countryCode, isShowCode: isShowCode)
  482. }
  483. func setCountry(with code: CountryEnum, isShowCode: Bool = true) {
  484. countryCode = code
  485. widthOfImageInLeftView = 35
  486. let textArea = textRect(forBounds: bounds)
  487. let imageView = UIImageView(
  488. frame: CGRect(
  489. x: 0,
  490. y: 0,
  491. width: widthOfImageInLeftView,
  492. height: textArea.height
  493. )
  494. )
  495. imageView.contentMode = .scaleAspectFit
  496. imageView.image = code.flag
  497. let label = UILabel()
  498. label.font = font ?? UIFont.systemFont(ofSize: 14)
  499. label.textColor = textColor ?? .black
  500. label.text = code.phoneCode
  501. label.frame = CGRect(
  502. x: widthOfImageInLeftView + leftViewPadding,
  503. y: 0,
  504. width: label.intrinsicContentSize.width,
  505. height: textArea.height
  506. )
  507. var width: CGFloat = widthOfImageInLeftView +
  508. leftViewPadding +
  509. label.intrinsicContentSize.width +
  510. leftViewPadding
  511. if !isShowCode {
  512. width = widthOfImageInLeftView + leftViewPadding
  513. label.isHidden = !isShowCode
  514. }
  515. let paddingView = UIView(
  516. frame: CGRect(
  517. x: 0,
  518. y: textArea.origin.y,
  519. width: width,
  520. height: textArea.height
  521. )
  522. )
  523. paddingView.addSubview(imageView)
  524. paddingView.addSubview(label)
  525. paddingView.backgroundColor = .clear
  526. leftFlagImageWidth = paddingView.frame.width
  527. leftViewMode = .always
  528. leftView = paddingView
  529. }
  530. func setBank(with code: String?) {
  531. guard
  532. let code = code?.lowercased(),
  533. let bankCode = BankEnum(rawValue: code) else {
  534. leftView = nil
  535. return
  536. }
  537. setBank(with: bankCode)
  538. }
  539. func setBank(with code: BankEnum) {
  540. leftImageView.removeFromSuperview()
  541. leftImage = code.ciImage
  542. }
  543. }
  544. extension ValidationTextField {
  545. func useAsDropDown(with configure: TablePresenterConfiguration? = nil, items: [TablePresenterProtocol]?) {
  546. func isEqual(target1: [TablePresenterProtocol]?, target2: [TablePresenterProtocol]?) -> Bool {
  547. guard let model1 = target1, let model2 = target2 else { return false}
  548. if model1.count != model2.count {
  549. return false
  550. }
  551. for i in 0..<model1.count where model1[i].cellTitle != model2[i].cellTitle {
  552. return false
  553. }
  554. return true
  555. }
  556. if !isEqual(target1: dropwDownItems, target2: items) {
  557. selectedItem.onNext(nil)
  558. text = nil
  559. sendActions(for: .editingChanged)
  560. }
  561. self.type = .dropdown
  562. self.dropwDownItems = items
  563. self.configure = configure
  564. }
  565. func didSelect(item: TablePresenterProtocol?) {
  566. selectedItem.onNext(item)
  567. text = item?.cellTitle
  568. sendActions(for: .editingChanged)
  569. }
  570. }
  571. extension ValidationTextField: TablePresenterDelegate {
  572. func tablePresenterView(_ viewController: TablePresenterViewController) -> TablePresenterConfiguration {
  573. guard let configure = self.configure else {
  574. return TablePresenterConfiguration(
  575. presenterTitle: "DropDown",
  576. closeButtonTitle: "cancel_text".localized(),
  577. notFoundTitle: "no_result_found_text".localized(),
  578. searchBarPlaceHolder: "Search"
  579. )
  580. }
  581. return configure
  582. }
  583. func tablePresenterView(
  584. _ viewController: TablePresenterViewController,
  585. didSelectModel model: TablePresenterProtocol?
  586. ) {
  587. didSelect(item: model)
  588. }
  589. }