From 2d1557f657c258e744c10d08d199722f6e915d0c Mon Sep 17 00:00:00 2001 From: Ivan Zinovyev Date: Mon, 19 Dec 2016 15:26:52 +0300 Subject: [PATCH] Initial commit --- Example/Tests/Tests.swift | 2 +- .../project.pbxproj | 29 +- .../UIAnimatedTextField-Example.xcscheme | 2 +- UIAnimatedTextField.podspec | 39 +- UIAnimatedTextField/Assets/.gitkeep | 0 UIAnimatedTextField/Classes/.gitkeep | 0 UIAnimatedTextField/Classes/ReplaceMe.swift | 0 .../Source/EditableTextField.swift | 49 ++ .../Source/String+Extensions.swift | 27 + .../Source/TIDateFormatter.swift | 46 ++ .../Source/UIAnimatedTextField.swift | 512 ++++++++++++++++++ .../Source/UIView+Extensions.swift | 44 ++ 12 files changed, 700 insertions(+), 50 deletions(-) delete mode 100644 UIAnimatedTextField/Assets/.gitkeep delete mode 100644 UIAnimatedTextField/Classes/.gitkeep delete mode 100644 UIAnimatedTextField/Classes/ReplaceMe.swift create mode 100644 UIAnimatedTextField/Source/EditableTextField.swift create mode 100644 UIAnimatedTextField/Source/String+Extensions.swift create mode 100644 UIAnimatedTextField/Source/TIDateFormatter.swift create mode 100644 UIAnimatedTextField/Source/UIAnimatedTextField.swift create mode 100644 UIAnimatedTextField/Source/UIView+Extensions.swift diff --git a/Example/Tests/Tests.swift b/Example/Tests/Tests.swift index 6a7536b..0e8b086 100644 --- a/Example/Tests/Tests.swift +++ b/Example/Tests/Tests.swift @@ -21,7 +21,7 @@ class Tests: XCTestCase { func testPerformanceExample() { // This is an example of a performance test case. - self.measureBlock() { + self.measure() { // Put the code you want to measure the time of here. } } diff --git a/Example/UIAnimatedTextField.xcodeproj/project.pbxproj b/Example/UIAnimatedTextField.xcodeproj/project.pbxproj index d053ee2..edc96d0 100644 --- a/Example/UIAnimatedTextField.xcodeproj/project.pbxproj +++ b/Example/UIAnimatedTextField.xcodeproj/project.pbxproj @@ -12,10 +12,10 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 12D59B30305289F1A0BAB148 /* UIAnimatedTextField.podspec */ = {isa = PBXFileReference; includeInIndex = 1; name = UIAnimatedTextField.podspec; path = ../UIAnimatedTextField.podspec; sourceTree = ""; }; + 12D59B30305289F1A0BAB148 /* UIAnimatedTextField.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = UIAnimatedTextField.podspec; path = ../UIAnimatedTextField.podspec; sourceTree = ""; }; 3023902FA98A29EAE27CE743 /* Pods_UIAnimatedTextField_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_UIAnimatedTextField_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 305D07AC233331112B594740 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; - 3EE19024E4B841DD8F1FEA61 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; name = README.md; path = ../README.md; sourceTree = ""; }; + 305D07AC233331112B594740 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; + 3EE19024E4B841DD8F1FEA61 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 607FACE51AFB9204008FA782 /* UIAnimatedTextField_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIAnimatedTextField_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 607FACEB1AFB9204008FA782 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = ""; }; @@ -128,14 +128,12 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0720; + LastUpgradeCheck = 0820; ORGANIZATIONNAME = CocoaPods; TargetAttributes = { - 607FACCF1AFB9204008FA782 = { - CreatedOnToolsVersion = 6.3.1; - }; 607FACE41AFB9204008FA782 = { CreatedOnToolsVersion = 6.3.1; + LastSwiftMigration = 0820; TestTargetID = 607FACCF1AFB9204008FA782; }; }; @@ -241,8 +239,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -286,8 +286,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -306,6 +308,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.3; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; VALIDATE_PRODUCT = YES; }; name = Release; @@ -314,10 +317,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 69636E88C8ED72BC27133F83 /* Pods-UIAnimatedTextField_Tests.debug.xcconfig */; buildSettings = { - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", @@ -326,6 +326,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -333,14 +334,12 @@ isa = XCBuildConfiguration; baseConfigurationReference = 6198DF963C0E86406868C2D9 /* Pods-UIAnimatedTextField_Tests.release.xcconfig */; buildSettings = { - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; INFOPLIST_FILE = Tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Release; }; diff --git a/Example/UIAnimatedTextField.xcodeproj/xcshareddata/xcschemes/UIAnimatedTextField-Example.xcscheme b/Example/UIAnimatedTextField.xcodeproj/xcshareddata/xcschemes/UIAnimatedTextField-Example.xcscheme index cd219d2..42ae55d 100644 --- a/Example/UIAnimatedTextField.xcodeproj/xcshareddata/xcschemes/UIAnimatedTextField-Example.xcscheme +++ b/Example/UIAnimatedTextField.xcodeproj/xcshareddata/xcschemes/UIAnimatedTextField-Example.xcscheme @@ -1,6 +1,6 @@ 'MIT', :file => 'LICENSE' } s.author = { 'Ivan Zinovyev' => 'ivan.zinovyev@touchin.ru' } - s.source = { :git => 'https://github.com//UIAnimatedTextField.git', :tag => s.version.to_s } - # s.social_media_url = 'https://twitter.com/' - + s.source = { :git => 'https://github.com/iznv/UIAnimatedTextField.git', :tag => s.version.to_s } s.ios.deployment_target = '8.0' - - s.source_files = 'UIAnimatedTextField/Classes/**/*' - - # s.resource_bundles = { - # 'UIAnimatedTextField' => ['UIAnimatedTextField/Assets/*.png'] - # } - - # s.public_header_files = 'Pod/Classes/**/*.h' - # s.frameworks = 'UIKit', 'MapKit' - # s.dependency 'AFNetworking', '~> 2.3' + s.source_files = 'UIAnimatedTextField/Source/**/*' + s.frameworks = 'UIKit' end diff --git a/UIAnimatedTextField/Assets/.gitkeep b/UIAnimatedTextField/Assets/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/UIAnimatedTextField/Classes/.gitkeep b/UIAnimatedTextField/Classes/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/UIAnimatedTextField/Classes/ReplaceMe.swift b/UIAnimatedTextField/Classes/ReplaceMe.swift deleted file mode 100644 index e69de29..0000000 diff --git a/UIAnimatedTextField/Source/EditableTextField.swift b/UIAnimatedTextField/Source/EditableTextField.swift new file mode 100644 index 0000000..8329772 --- /dev/null +++ b/UIAnimatedTextField/Source/EditableTextField.swift @@ -0,0 +1,49 @@ +// +// EditableTextField.swift +// Pods +// +// Created by Ivan Zinovyev on 12/19/16. +// +// + +import Foundation + +class EditableTextField: UITextField { + + var getType: (() -> TextType?)? + + private let menuSelectors = [ + #selector(selectAll(_:)), + #selector(select(_:)), + #selector(cut(_:)), + #selector(copy(_:)), + #selector(paste(_:)) + ] + + override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { + guard let type = getType?() else { + return super.canPerformAction(action, withSender: sender) + } + + if case .date = type { + if menuSelectors.contains(action) { + return false + } + } + + return super.canPerformAction(action, withSender: sender) + } + + override func caretRect(for position: UITextPosition) -> CGRect { + guard let type = getType?() else { + return super.caretRect(for: position) + } + + if case .date = type { + return CGRect.null + } + + return super.caretRect(for: position) + } + +} diff --git a/UIAnimatedTextField/Source/String+Extensions.swift b/UIAnimatedTextField/Source/String+Extensions.swift new file mode 100644 index 0000000..12bb675 --- /dev/null +++ b/UIAnimatedTextField/Source/String+Extensions.swift @@ -0,0 +1,27 @@ +// +// String+Extensions.swift +// Pods +// +// Created by Ivan Zinovyev on 12/19/16. +// +// + +import Foundation + +extension String { + + var localized: String { + return NSLocalizedString(self, comment: "") + } + + var isMultiline: Bool { + return range(of: "\n") != nil + } + + var isValidEmail: Bool { + let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}" + let emailPredicat = NSPredicate(format: "SELF MATCHES %@", emailRegex) + return emailPredicat.evaluate(with: self) + } + +} diff --git a/UIAnimatedTextField/Source/TIDateFormatter.swift b/UIAnimatedTextField/Source/TIDateFormatter.swift new file mode 100644 index 0000000..d5cef29 --- /dev/null +++ b/UIAnimatedTextField/Source/TIDateFormatter.swift @@ -0,0 +1,46 @@ +// +// TIDateFormatter.swift +// Pods +// +// Created by Ivan Zinovyev on 12/19/16. +// +// + +import Foundation + +class TIDateFormatter { + + fileprivate static let dateLongFormat = "dd/MM/YYYY" + fileprivate static let dateShortFormat = "dd/MM/YY" + fileprivate static let monthDayFormat = "MMMM d" + + fileprivate let longDateFormatter = DateFormatter() + fileprivate let shortDateFormatter = DateFormatter() + fileprivate let monthDayDateFormatter = DateFormatter() + + private static let shared = TIDateFormatter() + + // MARK: Init + + private init() { + longDateFormatter.dateFormat = TIDateFormatter.dateLongFormat + shortDateFormatter.dateFormat = TIDateFormatter.dateShortFormat + monthDayDateFormatter.dateFormat = TIDateFormatter.monthDayFormat + } + + // MARK: Public functions + + static func longDate(from date: Date) -> String { + return shared.longDateFormatter.string(from: date) + } + + static func shortDate(from date: Date) -> String { + return shared.shortDateFormatter.string(from: date) + } + + /// - Returns: example: "December 2" + static func monthDay(from date: Date) -> String { + return shared.monthDayDateFormatter.string(from: date) + } + +} diff --git a/UIAnimatedTextField/Source/UIAnimatedTextField.swift b/UIAnimatedTextField/Source/UIAnimatedTextField.swift new file mode 100644 index 0000000..8a5b2dc --- /dev/null +++ b/UIAnimatedTextField/Source/UIAnimatedTextField.swift @@ -0,0 +1,512 @@ +// +// UIAnimatedTextField.swift +// Pods +// +// Created by Ivan Zinovyev on 12/19/16. +// +// + +import UIKit + +@objc protocol UIAnimatedTextFieldDelegate: class { + + @objc optional func animatedTextFieldValueDidChange(_ animatedTextField: UIAnimatedTextField) + @objc optional func animatedTextFieldWillReactForTap() + @objc optional func animatedTextFieldShouldBeginEditing(_ animatedTextField: UIAnimatedTextField) -> Bool + @objc optional func animatedTextFieldDidBeginEditing(_ animatedTextField: UIAnimatedTextField) + @objc optional func animatedTextFieldShouldEndEditing(_ animatedTextField: UIAnimatedTextField) -> Bool + @objc optional func animatedTextFieldDidEndEditing(_ animatedTextField: UIAnimatedTextField) + @objc optional func animatedTextField(_ animatedTextField: UIAnimatedTextField, + shouldChangeCharactersInRange range: NSRange, + replacementString string: String) -> Bool + @objc optional func animatedTextFieldShouldClear(_ animatedTextField: UIAnimatedTextField) -> Bool + @objc optional func animatedTextFieldShouldReturn(_ animatedTextField: UIAnimatedTextField) -> Bool + +} + +enum AnimatedTextFieldState { + case placeholder + case text +} + +enum TextType { + case simple + case password + case url + case tappable(action: (_ animatedTextField: UIAnimatedTextField) -> Void) + case date +} + +@IBDesignable +class UIAnimatedTextField: UIView { + + // MARK: - Delegate + + weak var delegate: UIAnimatedTextFieldDelegate? + + // MARK: - UI Properties + + private(set) var textField: EditableTextField! + private(set) var placeholderLabel: UILabel! + + private(set) var lineView: UIView! + private var disclosureIndicatorImageView: UIImageView! + + // MARK: - Properties + + var text: String? { + get { + return textField.text + } + set { + textField.text = newValue + delegate?.animatedTextFieldValueDidChange?(self) + layoutSubviews() + } + } + + @IBInspectable var placeholder: String? { + get { + return placeholderLabel.text + } + set { + placeholderLabel.text = newValue + } + } + + var font: UIFont? { + get { + return placeholderLabel.font + } + set { + textField.font = newValue + placeholderLabel.font = newValue + } + } + + var isDisclosureIndicatorVisible: Bool = false { + didSet { + disclosureIndicatorImageView.isHidden = !isDisclosureIndicatorVisible + } + } + + @IBInspectable var isLeftTextAlignment: Bool { + get { + return textField.textAlignment == .left + } + set { + let alignment: NSTextAlignment = newValue ? .left : .center + textField.textAlignment = alignment + placeholderLabel.textAlignment = alignment + } + } + + var type: TextType = .simple { + didSet { + textField.isSecureTextEntry = false + textField.keyboardType = .default + textField.isUserInteractionEnabled = true + textField.autocorrectionType = .default + textField.autocapitalizationType = .words + textField.inputView = nil + textField.inputAccessoryView = nil + + tapAction = nil + isDisclosureIndicatorVisible = false + + if let tapGestureRecognizer = tapGestureRecognizer { + removeGestureRecognizer(tapGestureRecognizer) + self.tapGestureRecognizer = nil + } + + switch type { + case .password: + textField.isSecureTextEntry = true + textField.autocorrectionType = .no + textField.autocapitalizationType = .none + case .url: + textField.keyboardType = .emailAddress + textField.autocorrectionType = .no + textField.autocapitalizationType = .none + case .tappable(let action): + tapAction = action + textField.isUserInteractionEnabled = false + isDisclosureIndicatorVisible = true + + tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapGestureRecognizerAction(_:))) + if let tapGestureRecognizer = tapGestureRecognizer { + addGestureRecognizer(tapGestureRecognizer) + } + case .date: + textField.autocorrectionType = .no + textField.autocapitalizationType = .none + textField.inputView = getDateInputView() + textField.inputAccessoryView = getDateInputAccessoryView() + default: + break + } + } + } + + var selectedDate: Date? + + @IBInspectable var placeholderTopColor: UIColor = UIColor.gray + @IBInspectable var placeholderBottomColor: UIColor = UIColor.gray + + @IBInspectable var enteredTextColor: UIColor { + get { return textField.textColor ?? UIColor.black } + set { textField.textColor = newValue } + } + + @IBInspectable var lineColor: UIColor { + get { return lineView.backgroundColor ?? UIColor.gray } + set { lineView.backgroundColor = newValue } + } + + // MARK: - Private Properties + + static let animationDuration: TimeInterval = 0.3 + static let disclosureIndicatorWidth = 15.0 + + private var tapGestureRecognizer: UITapGestureRecognizer? + private var tapAction: ((_ animatedTextField: UIAnimatedTextField) -> Void)? + private var isShownInfo: Bool = false + + private var state: AnimatedTextFieldState { + var state: AnimatedTextFieldState = .placeholder + + if textField.text?.characters.count ?? 0 > 0 || textField.isFirstResponder { + state = .text + } + + return state + } + + // MARK: - Initialization + + override init(frame: CGRect) { + super.init(frame: frame) + initialization() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + initialization() + } + + private func initialization() { + textField = EditableTextField() + textField.delegate = self + textField.textAlignment = .center + textField.addTarget(self, action: #selector(textFieldValueDidChange), for: .editingChanged) + textField.getType = { [weak self] in + return self?.type + } + addSubview(textField) + + placeholderLabel = UILabel() + placeholderLabel.isUserInteractionEnabled = false + placeholderLabel.textColor = placeholderBottomColor + placeholderLabel.textAlignment = .center + placeholderLabel.adjustsFontSizeToFitWidth = true + placeholderLabel.minimumScaleFactor = 0.5 + addSubview(placeholderLabel) + + lineView = UIView() + lineView.backgroundColor = placeholderLabel.textColor + addSubview(lineView) + + disclosureIndicatorImageView = UIImageView() + disclosureIndicatorImageView.image = UIImage(named: "disclosureIndicator") + disclosureIndicatorImageView.contentMode = .center + disclosureIndicatorImageView.isHidden = true + addSubview(disclosureIndicatorImageView) + + layoutSubviews() + } + + // MARK: - Layout + + private func textFieldFrame() -> CGRect { + var size = bounds.size + var origin = bounds.origin + + size.height = 2/3 * size.height + origin.y = 1/3 * bounds.size.height + + if isDisclosureIndicatorVisible { + origin.x += CGFloat(UIAnimatedTextField.disclosureIndicatorWidth) + size.width -= CGFloat(UIAnimatedTextField.disclosureIndicatorWidth) * 2 + } + + let textFieldBounds = CGRect(origin: origin, size: size) + return textFieldBounds + } + + private func placeholderLabelFrame(state: AnimatedTextFieldState) -> CGRect { + if state == .placeholder { + return textFieldFrame() + } else { + var size = bounds.size + size.height = 1/3 * size.height + + let placeholderLabelBounds = CGRect(origin: bounds.origin, size: size) + return placeholderLabelBounds + } + } + + private func disclosureIndicatorFrame() -> CGRect { + let fieldFrame = textFieldFrame() + let frame = CGRect(x: bounds.width - CGFloat(UIAnimatedTextField.disclosureIndicatorWidth), + y: fieldFrame.origin.y, + width: CGFloat(UIAnimatedTextField.disclosureIndicatorWidth), + height: fieldFrame.height) + return frame + } + + private func placeholderLabelTransform(state: AnimatedTextFieldState) -> CGAffineTransform { + if state == .placeholder { + return CGAffineTransform.identity + } else { + return CGAffineTransform(scaleX: 0.8, y: 0.8) + } + } + + override func layoutSubviews() { + super.layoutSubviews() + + textField.frame = textFieldFrame() + + if isDisclosureIndicatorVisible { + disclosureIndicatorImageView.frame = disclosureIndicatorFrame() + } + + setState(toState: state, duration: 0) + + lineView.frame = CGRect(x: 0, + y: bounds.height - UIView.onePixelInPoints * 2, + width: bounds.width, + height: UIView.onePixelInPoints) + } + + // MARK: - Animation + + func setState(toState state: AnimatedTextFieldState, duration: TimeInterval) { + UIView.animate( + withDuration: duration, + delay: 0, + options: .beginFromCurrentState, + animations: { [weak self] in + guard let strongSelf = self else { + return + } + + strongSelf.placeholderLabel.frame = strongSelf.placeholderLabelFrame(state: state) + strongSelf.placeholderLabel.transform = strongSelf.placeholderLabelTransform(state: state) + switch state { + case .placeholder: + strongSelf.placeholderLabel.textColor = strongSelf.placeholderBottomColor + case .text: + strongSelf.placeholderLabel.textColor = strongSelf.placeholderTopColor + } + }, + completion: nil) + } + + // MARK: - Actions + + @objc private func tapGestureRecognizerAction(_ sender: UITapGestureRecognizer) { + delegate?.animatedTextFieldWillReactForTap?() + tapAction?(self) + } + + func validateText() -> Bool { + guard let text = textField.text else { + return false + } + + switch type { + case .url: + return text.isValidEmail + default: + break + } + + return true + } + + func showInfo(infoText: String) { + guard !isShownInfo else { + return + } + + isShownInfo = true + + let currentPlaceholder = placeholder + + UIView.transition(with: placeholderLabel, + duration: 0.5, + options: .transitionFlipFromTop, + animations: { [weak self] in + self?.placeholder = infoText + }, completion: nil) + + DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) { [weak self] in + guard let placeholderLabel = self?.placeholderLabel else { + return + } + + UIView.transition(with: placeholderLabel, + duration: 0.5, + options: .transitionFlipFromBottom, + animations: { + self?.placeholder = currentPlaceholder + }, completion: { [weak self] flag in + self?.isShownInfo = false + }) + } + } + + // MARK: - Private + + private func getDateInputView() -> UIDatePicker { + let currentDate = Date() + + let datePicker = UIDatePicker() + datePicker.timeZone = TimeZone(secondsFromGMT: 0) + datePicker.datePickerMode = .date + datePicker.backgroundColor = UIColor.white + datePicker.setDate(currentDate, animated: true) + datePicker.maximumDate = currentDate + datePicker.addTarget(self, action: #selector(datePickerValueChanged(_:)), for: .valueChanged) + + return datePicker + } + + @objc private func datePickerValueChanged(_ datePicker: UIDatePicker) { + selectedDate = datePicker.date + text = TIDateFormatter.longDate(from: datePicker.date) + } + + private func getDateInputAccessoryView() -> UIView { + let toolbar = UIToolbar() + toolbar.frame = CGRect(x: 0, y: 0, width: bounds.width, height: 44) + toolbar.autoresizingMask = [.flexibleWidth] + toolbar.barTintColor = UIColor.white + + let spacerItem = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) + let doneItem = UIBarButtonItem(title: "Done", + style: .done, + target: self, + action: #selector(datePickerDoneAction)) + let attributes = [ + NSForegroundColorAttributeName: UIColor.black + ] + + doneItem.setTitleTextAttributes(attributes, for: .normal) + + toolbar.items = [spacerItem, doneItem] + + return toolbar + } + + @objc private func datePickerDoneAction() { + textField.resignFirstResponder() + } + + @objc private func textFieldValueDidChange() { + delegate?.animatedTextFieldValueDidChange?(self) + } + +} + +extension UIAnimatedTextField: UITextFieldDelegate { + + func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + var result = true + + if let delegateResult = delegate?.animatedTextFieldShouldBeginEditing?(self) { + result = delegateResult + } + + return result + } + + func textFieldDidBeginEditing(_ textField: UITextField) { + if textField.text?.characters.count ?? 0 == 0 { + setState(toState: .text, duration: UIAnimatedTextField.animationDuration) + } + + if case .date = type { + if let datePicker = textField.inputView as? UIDatePicker { + textField.text = TIDateFormatter.longDate(from: datePicker.date) + } + } + + delegate?.animatedTextFieldDidBeginEditing?(self) + } + + func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + var result = true + + if let delegateResult = delegate?.animatedTextFieldShouldEndEditing?(self) { + result = delegateResult + } + + return result + } + + func textFieldDidEndEditing(_ textField: UITextField) { + if textField.text?.characters.count ?? 0 == 0 { + setState(toState: .placeholder, duration: UIAnimatedTextField.animationDuration) + } + + delegate?.animatedTextFieldDidEndEditing?(self) + } + + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, + replacementString string: String) -> Bool { + var result = true + + if let delegateResult = delegate?.animatedTextField?(self, shouldChangeCharactersInRange: range, + replacementString: string) { + result = delegateResult + } + + if string == " " { + if textField.text?.characters.count ?? 0 == 0 { + result = false + } + + switch type { + case .password, .url: + result = false + default: + break + } + } + + return result + } + + func textFieldShouldClear(_ textField: UITextField) -> Bool { + var result = true + + if let delegateResult = delegate?.animatedTextFieldShouldClear?(self) { + result = delegateResult + } + + return result + } + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + var result = true + + if let delegateResult = delegate?.animatedTextFieldShouldReturn?(self) { + result = delegateResult + } + + return result + } + +} diff --git a/UIAnimatedTextField/Source/UIView+Extensions.swift b/UIAnimatedTextField/Source/UIView+Extensions.swift new file mode 100644 index 0000000..9a2a5a0 --- /dev/null +++ b/UIAnimatedTextField/Source/UIView+Extensions.swift @@ -0,0 +1,44 @@ +// +// UIView+Extensions.swift +// Pods +// +// Created by Ivan Zinovyev on 12/19/16. +// +// + +import UIKit + +extension UIView { + + func addSubviewCentered(view: UIView) { + var viewFrame = view.frame + viewFrame.origin.x = (frame.width - viewFrame.width) / 2 + viewFrame.origin.y = (frame.height - viewFrame.height) / 2 + view.frame = viewFrame + view.autoresizingMask = [.flexibleLeftMargin, .flexibleTopMargin] + addSubview(view) + } + + class var onePixelInPoints: CGFloat { + return 1.0 / UIScreen.main.scale + } + + var allSubviews: [UIView] { + let allSubviews = subviews + subviews.flatMap { $0.allSubviews } + return allSubviews + } + + func viewFromSelfDescribingNib() -> UIView? { + let bundle = Bundle(for: type(of: self)) + let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle) + + guard let view = nib.instantiate(withOwner: self, options: nil).first as? UIView else { + return nil + } + + view.frame = bounds + view.autoresizingMask = [.flexibleWidth, .flexibleHeight] + return view + } + +}