Compare commits

..

No commits in common. "master" and "0.1.0" have entirely different histories.

24 changed files with 278 additions and 536 deletions

3
.gitignore vendored
View File

@ -30,5 +30,4 @@ Carthage
# Note: if you ignore the Pods directory, make sure to uncomment
# `pod install` in .travis.yml
#
Pods/
Podfile.lock
# Pods/

View File

@ -1 +0,0 @@
4.0

View File

@ -1,5 +0,0 @@
# Changelog
## 0.2.0
- **Fix**: date setting after picker presented.

View File

@ -12,7 +12,7 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
12D59B30305289F1A0BAB148 /* UIAnimatedTextField.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = UIAnimatedTextField.podspec; path = ../UIAnimatedTextField.podspec; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
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; 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>"; };
@ -110,6 +110,7 @@
607FACE21AFB9204008FA782 /* Frameworks */,
607FACE31AFB9204008FA782 /* Resources */,
4EB68626CEDBCD2943AF13ED /* [CP] Embed Pods Frameworks */,
AFE96432D6D5B8E3FF680C55 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@ -127,12 +128,12 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0720;
LastUpgradeCheck = 1000;
LastUpgradeCheck = 0820;
ORGANIZATIONNAME = CocoaPods;
TargetAttributes = {
607FACE41AFB9204008FA782 = {
CreatedOnToolsVersion = 6.3.1;
LastSwiftMigration = 0910;
LastSwiftMigration = 0820;
TestTargetID = 607FACCF1AFB9204008FA782;
};
};
@ -172,34 +173,43 @@
files = (
);
inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-UIAnimatedTextField_Tests/Pods-UIAnimatedTextField_Tests-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/UIAnimatedTextField/UIAnimatedTextField.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/UIAnimatedTextField.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-UIAnimatedTextField_Tests/Pods-UIAnimatedTextField_Tests-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
AFE96432D6D5B8E3FF680C55 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-UIAnimatedTextField_Tests/Pods-UIAnimatedTextField_Tests-resources.sh\"\n";
showEnvVarsInLog = 0;
};
D458C72BE72F89E2185E2C11 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-UIAnimatedTextField_Tests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
@ -224,22 +234,14 @@
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
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_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
@ -279,22 +281,14 @@
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
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_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
@ -332,8 +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_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 3.0;
};
name = Debug;
};
@ -346,8 +339,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_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 3.0;
};
name = Release;
};

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1000"
LastUpgradeVersion = "0820"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:UIAnimatedTextField.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -1,4 +1,4 @@
Copyright (c) 2016 Touch Instinct
Copyright (c) 2016 Ivan Zinovyev <ivan.zinovyev@touchin.ru>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,99 +1,24 @@
# UIAnimatedTextField
This custom control can be used as a replacement for UITextField.
When an user taps on it, a placeholder rises smoothly.
It comes with 5 different text types: simple, password, url, tappable, date.
## Example
To run the example project, clone the repo, and run `pod install` from the Example directory first.
## Requirements
## Installation
UIAnimatedTextField is available through [CocoaPods](http://cocoapods.org). To install
it, simply add the pod 'UIAnimatedTextField' and the source for podspecs to your Podfile. For example:
it, simply add the following line to your Podfile:
```ruby
source "https://github.com/iznv/Podspecs.git"
platform :ios, '9.0'
use_frameworks!
target "ProjectName" do
pod 'UIAnimatedTextField', '0.1.7'
end
pod "UIAnimatedTextField"
```
## Usage
Set height of UIView to 50 (optionally, to make UIAnimatedTextField look pretty). Create IBOutlet:
```swift
@IBOutlet weak var textField: UIAnimatedTextField!
```
## Author
In order to enable placeholder, set placeholder property:
```swift
textField.placeholder = "Enter something"
```
### Simple type
By default you use simple type. It is just a text field.
<img src="https://raw.githubusercontent.com/iznv/UIAnimatedTextField/master/UIAnimatedTextField/Screenshots/simple1.png" width="300">
<img src="https://raw.githubusercontent.com/iznv/UIAnimatedTextField/master/UIAnimatedTextField/Screenshots/simple2.png" width="300">
### Password type
In order to use UIAnimatedTextField for password input, specify its type as .password
```swift
textField.type = .password
```
<img src="https://raw.githubusercontent.com/iznv/UIAnimatedTextField/master/UIAnimatedTextField/Screenshots/password.png" width="300">
### Date type
In order to use UIAnimatedTextField for date input, specify its type as .date
```swift
textField.type = .date
```
Also you can set date format and done button title:
```swift
// "Done" by default
textField.doneTitle = "Ok"
// "dd/MM/YYYY" by default
textField.dateFormat = "dd MMMM YYYY"
```
<img src="https://raw.githubusercontent.com/iznv/UIAnimatedTextField/master/UIAnimatedTextField/Screenshots/date.png" width="300">
### Tappable type
In order to choose somewhere something that will be displayed in text field, specify type as .tappable and designate an action, for example:
```swift
textField.type = .tappable(action: {textField in textField.text = "Selected thing" })
```
Tap on the field, do an action, display a result in text field.
## Customization
### Color
You can change color of placeholder, entered text, line like this:
```swift
textField.placeholderTopColor = .blue
textField.placeholderBottomColor = .brown
textField.enteredTextColor = .orange
textField.lineColor = .green
```
Result:
<img src="https://raw.githubusercontent.com/iznv/UIAnimatedTextField/master/UIAnimatedTextField/Screenshots/custom1.png" width="300">
<img src="https://raw.githubusercontent.com/iznv/UIAnimatedTextField/master/UIAnimatedTextField/Screenshots/custom2.png" width="300">
### Text Alignment
In order to change text alignment of placeholder and text field use this property:
```swift
textField.isLeftTextAlignment = true
```
Result:
<img src="https://raw.githubusercontent.com/iznv/UIAnimatedTextField/master/UIAnimatedTextField/Screenshots/custom3.png" width="300">
Ivan Zinovyev, ivan.zinovyev@touchin.ru
## License
Copyright (c) 2016 Touch Instinct
UIAnimatedTextField is available under the MIT license. See the LICENSE file for more info.

View File

@ -1,14 +1,14 @@
Pod::Spec.new do |s|
s.name = 'UIAnimatedTextField'
s.version = '0.3.0'
s.summary = 'UITextField with animated placeholder'
s.version = '0.1.0'
s.summary = 'UIAnimatedTextField'
s.description = <<-DESC
This custom control can be used as a replacement for UITextField. It comes with 5 different text types: simple, password, url, tappable, date.
Animated text field
DESC
s.homepage = 'https://github.com/TouchInstinct/UIAnimatedTextField'
s.homepage = 'https://github.com/iznv/UIAnimatedTextField'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = "Touch Instinct"
s.source = { :git => 'https://github.com/TouchInstinct/UIAnimatedTextField.git', :tag => s.version.to_s }
s.author = { 'Ivan Zinovyev' => 'ivan.zinovyev@touchin.ru' }
s.source = { :git => 'https://github.com/iznv/UIAnimatedTextField.git', :tag => s.version.to_s }
s.ios.deployment_target = '8.0'
s.source_files = 'UIAnimatedTextField/Source/**/*'
s.frameworks = 'UIKit'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

View File

@ -1,33 +0,0 @@
//
// TIDateFormatter.swift
//
// Copyright (c) 2016 Touch Instinct
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
extension Date {
func toString(withFormat format: String) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = format
return dateFormatter.string(from: self)
}
}

View File

@ -1,56 +1,17 @@
//
// EditableTextField.swift
// Pods
//
// Copyright (c) 2016 Touch Instinct
// Created by Ivan Zinovyev on 12/19/16.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
public enum EditableActionType {
case selectAll
case select
case cut
case copy
case paste
public static let allActions: [EditableActionType] = [.selectAll, .select, .cut, .paste, .copy]
}
open class EditableTextField: UITextField {
/// Actions, that will be disabled for this textField.
/// By default no actions are disabled.
open var disabledActions: [EditableActionType] = []
/// Allows to disable moving cursor for user
open var pinCursorToEnd: Bool = false
open var getType: (() -> TextType?)?
// MARK: - Private
private var disabledSelectors: [Selector] {
return disabledActions.map { selector(from: $0) }
}
class EditableTextField: UITextField {
var getType: (() -> TextType?)?
private let menuSelectors = [
#selector(selectAll(_:)),
#selector(select(_:)),
@ -58,14 +19,8 @@ open class EditableTextField: UITextField {
#selector(copy(_:)),
#selector(paste(_:))
]
// MARK: - Overriden
override open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if disabledSelectors.contains(action) {
return false
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
guard let type = getType?() else {
return super.canPerformAction(action, withSender: sender)
}
@ -79,7 +34,7 @@ open class EditableTextField: UITextField {
return super.canPerformAction(action, withSender: sender)
}
override open func caretRect(for position: UITextPosition) -> CGRect {
override func caretRect(for position: UITextPosition) -> CGRect {
guard let type = getType?() else {
return super.caretRect(for: position)
}
@ -90,33 +45,5 @@ open class EditableTextField: UITextField {
return super.caretRect(for: position)
}
override open func closestPosition(to point: CGPoint) -> UITextPosition? {
if pinCursorToEnd {
return endOfDocument
}
return super.closestPosition(to: point)
}
}
// MARK: - Private extensions
private extension UITextField {
func selector(from actionTyoe: EditableActionType) -> Selector {
switch actionTyoe {
case .selectAll:
return #selector(selectAll(_:))
case .select:
return #selector(select(_:))
case .cut:
return #selector(cut(_:))
case .copy:
return #selector(copy(_:))
case .paste:
return #selector(paste(_:))
}
}
}

View File

@ -1,31 +1,23 @@
//
// String+Extensions.swift
//
// Copyright (c) 2016 Touch Instinct
// Pods
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// Created by Ivan Zinovyev on 12/19/16.
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
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)

View File

@ -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)
}
}

View File

@ -1,31 +1,15 @@
//
// UIAnimatedTextField.swift
// Pods
//
// Copyright (c) 2016 Touch Instinct
// Created by Ivan Zinovyev on 12/19/16.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit
@objc public protocol UIAnimatedTextFieldDelegate: class {
@objc protocol UIAnimatedTextFieldDelegate: class {
@objc optional func animatedTextFieldValueDidChange(_ animatedTextField: UIAnimatedTextField)
@objc optional func animatedTextFieldWillReactForTap()
@objc optional func animatedTextFieldShouldBeginEditing(_ animatedTextField: UIAnimatedTextField) -> Bool
@ -37,15 +21,15 @@ import UIKit
replacementString string: String) -> Bool
@objc optional func animatedTextFieldShouldClear(_ animatedTextField: UIAnimatedTextField) -> Bool
@objc optional func animatedTextFieldShouldReturn(_ animatedTextField: UIAnimatedTextField) -> Bool
}
public enum AnimatedTextFieldState {
enum AnimatedTextFieldState {
case placeholder
case text
}
public enum TextType {
enum TextType {
case simple
case password
case url
@ -54,73 +38,23 @@ public enum TextType {
}
@IBDesignable
open class UIAnimatedTextField: UIView {
// MARK: - Constants
struct Constants {
static let done = "Done"
static let space = " "
static let defaultDateFormat = "dd/MM/yyyy"
}
public class UIAnimatedTextField: UIView {
// MARK: - Delegate
weak public var delegate: UIAnimatedTextFieldDelegate?
weak var delegate: UIAnimatedTextFieldDelegate?
// MARK: - UI Properties
private(set) public var textField: EditableTextField!
private(set) public var placeholderLabel: UILabel!
private(set) public var lineView: UIView!
private(set) var textField: EditableTextField!
private(set) var placeholderLabel: UILabel!
private(set) var lineView: UIView!
private var disclosureIndicatorImageView: UIImageView!
// MARK: - @IBInspectable Properties
@IBInspectable public var placeholder: String? {
get {
return placeholderLabel.text
}
set {
placeholderLabel.text = newValue
}
}
@IBInspectable public var isLeftTextAlignment: Bool {
get {
return textField.textAlignment == .left
}
set {
let alignment: NSTextAlignment = newValue ? .left : .center
textField.textAlignment = alignment
placeholderLabel.textAlignment = alignment
}
}
@IBInspectable public var placeholderTopColor: UIColor = UIColor.gray {
didSet {
setState(toState: state)
}
}
@IBInspectable public var placeholderBottomColor: UIColor = UIColor.gray {
didSet {
setState(toState: state)
}
}
@IBInspectable public var enteredTextColor: UIColor {
get { return textField.textColor ?? UIColor.black }
set { textField.textColor = newValue }
}
@IBInspectable public var lineColor: UIColor {
get { return lineView.backgroundColor ?? UIColor.gray }
set { lineView.backgroundColor = newValue }
}
// MARK: - Public Properties
public var text: String? {
// MARK: - Properties
var text: String? {
get {
return textField.text
}
@ -130,8 +64,17 @@ open class UIAnimatedTextField: UIView {
layoutSubviews()
}
}
public var font: UIFont? {
@IBInspectable var placeholder: String? {
get {
return placeholderLabel.text
}
set {
placeholderLabel.text = newValue
}
}
var font: UIFont? {
get {
return placeholderLabel.font
}
@ -140,14 +83,25 @@ open class UIAnimatedTextField: UIView {
placeholderLabel.font = newValue
}
}
public var isDisclosureIndicatorVisible: Bool = false {
var isDisclosureIndicatorVisible: Bool = false {
didSet {
disclosureIndicatorImageView.isHidden = !isDisclosureIndicatorVisible
}
}
public var type: TextType = .simple {
@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
@ -156,15 +110,15 @@ open class UIAnimatedTextField: UIView {
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
@ -178,7 +132,7 @@ open class UIAnimatedTextField: UIView {
tapAction = action
textField.isUserInteractionEnabled = false
isDisclosureIndicatorVisible = true
tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapGestureRecognizerAction(_:)))
if let tapGestureRecognizer = tapGestureRecognizer {
addGestureRecognizer(tapGestureRecognizer)
@ -193,55 +147,53 @@ open class UIAnimatedTextField: UIView {
}
}
}
@objc public dynamic var selectedDate: Date?
public var dateFormat: String = Constants.defaultDateFormat
public var doneTitle: String = Constants.done {
didSet {
textField.inputAccessoryView = getDateInputAccessoryView()
}
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 }
}
public var doneTitleColor: UIColor = .black {
didSet {
textField.inputAccessoryView = getDateInputAccessoryView()
}
@IBInspectable var lineColor: UIColor {
get { return lineView.backgroundColor ?? UIColor.gray }
set { lineView.backgroundColor = newValue }
}
// MARK: - Static Properties
static public let animationDuration: TimeInterval = 0.3
static public let disclosureIndicatorWidth = 15.0
// 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?.count ?? 0 > 0 || textField.isFirstResponder {
if textField.text?.characters.count ?? 0 > 0 || textField.isFirstResponder {
state = .text
}
return state
}
// MARK: - Initialization
override public init(frame: CGRect) {
override init(frame: CGRect) {
super.init(frame: frame)
initialization()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialization()
}
private func initialization() {
textField = EditableTextField()
textField.delegate = self
@ -251,7 +203,7 @@ open class UIAnimatedTextField: UIView {
return self?.type
}
addSubview(textField)
placeholderLabel = UILabel()
placeholderLabel.isUserInteractionEnabled = false
placeholderLabel.textColor = placeholderBottomColor
@ -259,50 +211,50 @@ open class UIAnimatedTextField: UIView {
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),
@ -311,7 +263,7 @@ open class UIAnimatedTextField: UIView {
height: fieldFrame.height)
return frame
}
private func placeholderLabelTransform(state: AnimatedTextFieldState) -> CGAffineTransform {
if state == .placeholder {
return CGAffineTransform.identity
@ -319,27 +271,27 @@ open class UIAnimatedTextField: UIView {
return CGAffineTransform(scaleX: 0.8, y: 0.8)
}
}
override open func layoutSubviews() {
override public 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
public func setState(toState state: AnimatedTextFieldState, duration: TimeInterval = 0) {
func setState(toState state: AnimatedTextFieldState, duration: TimeInterval) {
UIView.animate(
withDuration: duration,
delay: 0,
@ -348,15 +300,9 @@ open class UIAnimatedTextField: UIView {
guard let strongSelf = self else {
return
}
if strongSelf.isLeftTextAlignment {
strongSelf.placeholderLabel.transform = strongSelf.placeholderLabelTransform(state: state)
strongSelf.placeholderLabel.frame = strongSelf.placeholderLabelFrame(state: state)
} else {
strongSelf.placeholderLabel.frame = strongSelf.placeholderLabelFrame(state: state)
strongSelf.placeholderLabel.transform = strongSelf.placeholderLabelTransform(state: state)
}
strongSelf.placeholderLabel.frame = strongSelf.placeholderLabelFrame(state: state)
strongSelf.placeholderLabel.transform = strongSelf.placeholderLabelTransform(state: state)
switch state {
case .placeholder:
strongSelf.placeholderLabel.textColor = strongSelf.placeholderBottomColor
@ -366,50 +312,50 @@ open class UIAnimatedTextField: UIView {
},
completion: nil)
}
// MARK: - Actions
@objc private func tapGestureRecognizerAction(_ sender: UITapGestureRecognizer) {
delegate?.animatedTextFieldWillReactForTap?()
tapAction?(self)
}
open func validateText() -> Bool {
func validateText() -> Bool {
guard let text = textField.text else {
return false
}
switch type {
case .url:
return text.isValidEmail
default:
break
}
return true
}
open func showInfo(infoText: String) {
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,
@ -420,131 +366,118 @@ open class UIAnimatedTextField: UIView {
})
}
}
// 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(selectedDate ?? currentDate, animated: true)
datePicker.setDate(currentDate, animated: true)
datePicker.maximumDate = currentDate
datePicker.addTarget(self, action: #selector(datePickerValueChanged(_:)), for: .valueChanged)
return datePicker
}
@objc private func datePickerValueChanged(_ datePicker: UIDatePicker) {
updateText(from: datePicker)
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: doneTitle,
let doneItem = UIBarButtonItem(title: "Done",
style: .done,
target: self,
action: #selector(datePickerDoneAction))
let attributes = [
NSAttributedString.Key.foregroundColor: doneTitleColor
NSForegroundColorAttributeName: UIColor.black
]
doneItem.setTitleTextAttributes(attributes, for: .normal)
toolbar.setItems([spacerItem, doneItem], animated: false)
toolbar.items = [spacerItem, doneItem]
return toolbar
}
fileprivate func updateText(from datePicker: UIDatePicker) {
selectedDate = datePicker.date
text = datePicker.date.toString(withFormat: dateFormat)
}
@objc private func datePickerDoneAction() {
if let datePicker = textField.inputView as? UIDatePicker {
updateText(from: datePicker)
}
textField.resignFirstResponder()
}
@objc private func textFieldValueDidChange() {
delegate?.animatedTextFieldValueDidChange?(self)
}
}
// MARK: - Extension
extension UIAnimatedTextField: UITextFieldDelegate {
open func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
var result = true
if let delegateResult = delegate?.animatedTextFieldShouldBeginEditing?(self) {
result = delegateResult
}
return result
}
open func textFieldDidBeginEditing(_ textField: UITextField) {
if textField.text?.count ?? 0 == 0 {
public 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 = datePicker.date.toString(withFormat: dateFormat)
textField.text = TIDateFormatter.longDate(from: datePicker.date)
}
}
delegate?.animatedTextFieldDidBeginEditing?(self)
}
open func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
public func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
var result = true
if let delegateResult = delegate?.animatedTextFieldShouldEndEditing?(self) {
result = delegateResult
}
return result
}
open func textFieldDidEndEditing(_ textField: UITextField) {
if textField.text?.count ?? 0 == 0 {
public func textFieldDidEndEditing(_ textField: UITextField) {
if textField.text?.characters.count ?? 0 == 0 {
setState(toState: .placeholder, duration: UIAnimatedTextField.animationDuration)
}
if let datePicker = textField.inputView as? UIDatePicker {
updateText(from: datePicker)
}
delegate?.animatedTextFieldDidEndEditing?(self)
}
open func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
public 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 == Constants.space {
if textField.text?.isEmpty ?? true {
if string == " " {
if textField.text?.characters.count ?? 0 == 0 {
result = false
}
switch type {
case .password, .url:
result = false
@ -552,28 +485,28 @@ extension UIAnimatedTextField: UITextFieldDelegate {
break
}
}
return result
}
open func textFieldShouldClear(_ textField: UITextField) -> Bool {
public func textFieldShouldClear(_ textField: UITextField) -> Bool {
var result = true
if let delegateResult = delegate?.animatedTextFieldShouldClear?(self) {
result = delegateResult
}
return result
}
open func textFieldShouldReturn(_ textField: UITextField) -> Bool {
public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
var result = true
if let delegateResult = delegate?.animatedTextFieldShouldReturn?(self) {
result = delegateResult
}
return result
}
}

View File

@ -1,25 +1,9 @@
//
// UIView+Extensions.swift
// Pods
//
// Copyright (c) 2016 Touch Instinct
// Created by Ivan Zinovyev on 12/19/16.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import UIKit

1
_Pods.xcodeproj Symbolic link
View File

@ -0,0 +1 @@
Example/Pods/Pods.xcodeproj