Initial commit
This commit is contained in:
parent
a7cc24ecfc
commit
2d1557f657
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@
|
|||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
12D59B30305289F1A0BAB148 /* UIAnimatedTextField.podspec */ = {isa = PBXFileReference; includeInIndex = 1; name = UIAnimatedTextField.podspec; path = ../UIAnimatedTextField.podspec; sourceTree = "<group>"; };
|
||||
12D59B30305289F1A0BAB148 /* UIAnimatedTextField.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = UIAnimatedTextField.podspec; path = ../UIAnimatedTextField.podspec; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
3EE19024E4B841DD8F1FEA61 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; name = README.md; path = ../README.md; sourceTree = "<group>"; };
|
||||
305D07AC233331112B594740 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
|
||||
3EE19024E4B841DD8F1FEA61 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
607FACEB1AFB9204008FA782 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0720"
|
||||
LastUpgradeVersion = "0820"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
|||
|
|
@ -1,42 +1,15 @@
|
|||
#
|
||||
# Be sure to run `pod lib lint UIAnimatedTextField.podspec' to ensure this is a
|
||||
# valid spec before submitting.
|
||||
#
|
||||
# Any lines starting with a # are optional, but their use is encouraged
|
||||
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
|
||||
#
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'UIAnimatedTextField'
|
||||
s.version = '0.1.0'
|
||||
s.summary = 'A short description of UIAnimatedTextField.'
|
||||
|
||||
# This description is used to generate tags and improve search results.
|
||||
# * Think: What does it do? Why did you write it? What is the focus?
|
||||
# * Try to keep it short, snappy and to the point.
|
||||
# * Write the description between the DESC delimiters below.
|
||||
# * Finally, don't worry about the indent, CocoaPods strips it!
|
||||
|
||||
s.summary = 'UIAnimatedTextField'
|
||||
s.description = <<-DESC
|
||||
TODO: Add long description of the pod here.
|
||||
Animated text field
|
||||
DESC
|
||||
|
||||
s.homepage = 'https://github.com/<GITHUB_USERNAME>/UIAnimatedTextField'
|
||||
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
|
||||
s.homepage = 'https://github.com/iznv/UIAnimatedTextField'
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
s.author = { 'Ivan Zinovyev' => 'ivan.zinovyev@touchin.ru' }
|
||||
s.source = { :git => 'https://github.com/<GITHUB_USERNAME>/UIAnimatedTextField.git', :tag => s.version.to_s }
|
||||
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue