diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..446cc81 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## 0.2.0 + +- **Fix**: date setting after picker presented. \ No newline at end of file diff --git a/Example/UIAnimatedTextField.xcodeproj/project.pbxproj b/Example/UIAnimatedTextField.xcodeproj/project.pbxproj index edc96d0..aa9d180 100644 --- a/Example/UIAnimatedTextField.xcodeproj/project.pbxproj +++ b/Example/UIAnimatedTextField.xcodeproj/project.pbxproj @@ -128,12 +128,12 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0820; + LastUpgradeCheck = 0910; ORGANIZATIONNAME = CocoaPods; TargetAttributes = { 607FACE41AFB9204008FA782 = { CreatedOnToolsVersion = 6.3.1; - LastSwiftMigration = 0820; + LastSwiftMigration = 0910; TestTargetID = 607FACCF1AFB9204008FA782; }; }; @@ -173,9 +173,12 @@ 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; @@ -203,13 +206,16 @@ 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_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"; + 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"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -234,14 +240,20 @@ 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_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_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; @@ -281,14 +293,20 @@ 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_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_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; @@ -326,7 +344,8 @@ 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; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -339,7 +358,8 @@ 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; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/Example/UIAnimatedTextField.xcodeproj/xcshareddata/xcschemes/UIAnimatedTextField-Example.xcscheme b/Example/UIAnimatedTextField.xcodeproj/xcshareddata/xcschemes/UIAnimatedTextField-Example.xcscheme index 42ae55d..6fcae08 100644 --- a/Example/UIAnimatedTextField.xcodeproj/xcshareddata/xcschemes/UIAnimatedTextField-Example.xcscheme +++ b/Example/UIAnimatedTextField.xcodeproj/xcshareddata/xcschemes/UIAnimatedTextField-Example.xcscheme @@ -1,6 +1,6 @@ TextType?)? - +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) } + } + private let menuSelectors = [ #selector(selectAll(_:)), #selector(select(_:)), @@ -35,8 +58,14 @@ public class EditableTextField: UITextField { #selector(copy(_:)), #selector(paste(_:)) ] + + // MARK: - Overriden - override public func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { + override open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { + if disabledSelectors.contains(action) { + return false + } + guard let type = getType?() else { return super.canPerformAction(action, withSender: sender) } @@ -50,7 +79,7 @@ public class EditableTextField: UITextField { return super.canPerformAction(action, withSender: sender) } - override public func caretRect(for position: UITextPosition) -> CGRect { + override open func caretRect(for position: UITextPosition) -> CGRect { guard let type = getType?() else { return super.caretRect(for: position) } @@ -61,5 +90,33 @@ public 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(_:)) + } + } +} diff --git a/UIAnimatedTextField/Source/UIAnimatedTextField.swift b/UIAnimatedTextField/Source/UIAnimatedTextField.swift index 814bd6f..e0e3fa8 100644 --- a/UIAnimatedTextField/Source/UIAnimatedTextField.swift +++ b/UIAnimatedTextField/Source/UIAnimatedTextField.swift @@ -25,7 +25,7 @@ import UIKit @objc public protocol UIAnimatedTextFieldDelegate: class { - + @objc optional func animatedTextFieldValueDidChange(_ animatedTextField: UIAnimatedTextField) @objc optional func animatedTextFieldWillReactForTap() @objc optional func animatedTextFieldShouldBeginEditing(_ animatedTextField: UIAnimatedTextField) -> Bool @@ -37,7 +37,7 @@ import UIKit replacementString string: String) -> Bool @objc optional func animatedTextFieldShouldClear(_ animatedTextField: UIAnimatedTextField) -> Bool @objc optional func animatedTextFieldShouldReturn(_ animatedTextField: UIAnimatedTextField) -> Bool - + } public enum AnimatedTextFieldState { @@ -55,28 +55,28 @@ 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" } - + // MARK: - Delegate - + weak public 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 var disclosureIndicatorImageView: UIImageView! - + // MARK: - @IBInspectable Properties - + @IBInspectable public var placeholder: String? { get { return placeholderLabel.text @@ -85,7 +85,7 @@ open class UIAnimatedTextField: UIView { placeholderLabel.text = newValue } } - + @IBInspectable public var isLeftTextAlignment: Bool { get { return textField.textAlignment == .left @@ -96,7 +96,7 @@ open class UIAnimatedTextField: UIView { placeholderLabel.textAlignment = alignment } } - + @IBInspectable public var placeholderTopColor: UIColor = UIColor.gray { didSet { setState(toState: state) @@ -107,19 +107,19 @@ open class UIAnimatedTextField: UIView { 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? { get { return textField.text @@ -140,7 +140,7 @@ open class UIAnimatedTextField: UIView { placeholderLabel.font = newValue } } - + public var isDisclosureIndicatorVisible: Bool = false { didSet { disclosureIndicatorImageView.isHidden = !isDisclosureIndicatorVisible @@ -156,15 +156,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 +178,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,48 +193,55 @@ open class UIAnimatedTextField: UIView { } } } - - public dynamic var selectedDate: Date? + + @objc public dynamic var selectedDate: Date? public var dateFormat: String = Constants.defaultDateFormat + public var doneTitle: String = Constants.done { didSet { textField.inputAccessoryView = getDateInputAccessoryView() } } - + + public var doneTitleColor: UIColor = .black { + didSet { + textField.inputAccessoryView = getDateInputAccessoryView() + } + } + // MARK: - Static Properties - + static public let animationDuration: TimeInterval = 0.3 static public let disclosureIndicatorWidth = 15.0 - + // MARK: - Private Properties - + 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 { + + if textField.text?.count ?? 0 > 0 || textField.isFirstResponder { state = .text } - + return state } - + // MARK: - Initialization - + override public 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 @@ -244,7 +251,7 @@ open class UIAnimatedTextField: UIView { return self?.type } addSubview(textField) - + placeholderLabel = UILabel() placeholderLabel.isUserInteractionEnabled = false placeholderLabel.textColor = placeholderBottomColor @@ -252,50 +259,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), @@ -304,7 +311,7 @@ open class UIAnimatedTextField: UIView { height: fieldFrame.height) return frame } - + private func placeholderLabelTransform(state: AnimatedTextFieldState) -> CGAffineTransform { if state == .placeholder { return CGAffineTransform.identity @@ -312,26 +319,26 @@ open class UIAnimatedTextField: UIView { return CGAffineTransform(scaleX: 0.8, y: 0.8) } } - + override open 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) { UIView.animate( withDuration: duration, @@ -342,8 +349,13 @@ open class UIAnimatedTextField: UIView { return } - strongSelf.placeholderLabel.transform = strongSelf.placeholderLabelTransform(state: state) - strongSelf.placeholderLabel.frame = strongSelf.placeholderLabelFrame(state: state) + 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) + } switch state { case .placeholder: @@ -354,50 +366,50 @@ open class UIAnimatedTextField: UIView { }, completion: nil) } - + // MARK: - Actions - + @objc private func tapGestureRecognizerAction(_ sender: UITapGestureRecognizer) { delegate?.animatedTextFieldWillReactForTap?() tapAction?(self) } - + open 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) { 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, @@ -408,46 +420,46 @@ 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(currentDate, animated: true) + datePicker.setDate(selectedDate ?? 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) } - + 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, style: .done, target: self, action: #selector(datePickerDoneAction)) let attributes = [ - NSForegroundColorAttributeName: UIColor.black + NSAttributedStringKey.foregroundColor: doneTitleColor ] - + doneItem.setTitleTextAttributes(attributes, for: .normal) - - toolbar.items = [spacerItem, doneItem] - + + toolbar.setItems([spacerItem, doneItem], animated: false) + return toolbar } @@ -455,60 +467,60 @@ open class UIAnimatedTextField: UIView { 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 { var result = true - + if let delegateResult = delegate?.animatedTextFieldShouldBeginEditing?(self) { result = delegateResult } - + return result } - + open func textFieldDidBeginEditing(_ textField: UITextField) { - if textField.text?.characters.count ?? 0 == 0 { + if textField.text?.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) } } - + delegate?.animatedTextFieldDidBeginEditing?(self) } - + open 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?.characters.count ?? 0 == 0 { + if textField.text?.count ?? 0 == 0 { setState(toState: .placeholder, duration: UIAnimatedTextField.animationDuration) } @@ -518,21 +530,21 @@ extension UIAnimatedTextField: UITextFieldDelegate { delegate?.animatedTextFieldDidEndEditing?(self) } - + open func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, - replacementString string: String) -> Bool { + 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 { result = false } - + switch type { case .password, .url: result = false @@ -540,28 +552,28 @@ extension UIAnimatedTextField: UITextFieldDelegate { break } } - + return result } - + open func textFieldShouldClear(_ textField: UITextField) -> Bool { var result = true - + if let delegateResult = delegate?.animatedTextFieldShouldClear?(self) { result = delegateResult } - + return result } - + open func textFieldShouldReturn(_ textField: UITextField) -> Bool { var result = true - + if let delegateResult = delegate?.animatedTextFieldShouldReturn?(self) { result = delegateResult } - + return result } - + }