diff --git a/FloatLabelExample/FloatLabelExample.xcodeproj/project.pbxproj b/FloatLabelExample/FloatLabelExample.xcodeproj/project.pbxproj index 87413f9..c1eb163 100644 --- a/FloatLabelExample/FloatLabelExample.xcodeproj/project.pbxproj +++ b/FloatLabelExample/FloatLabelExample.xcodeproj/project.pbxproj @@ -114,7 +114,7 @@ attributes = { LastSwiftMigration = 0730; LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 0730; + LastUpgradeCheck = 1130; ORGANIZATIONNAME = "RookSoft Ltd."; TargetAttributes = { DD72F5E61A2845270097D54A = { @@ -128,6 +128,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, ); @@ -192,17 +193,28 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 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; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -211,6 +223,7 @@ ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -228,6 +241,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -236,17 +250,28 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 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; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -254,6 +279,7 @@ ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; @@ -263,6 +289,8 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.1; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -276,7 +304,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "org.farook.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -288,7 +316,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "org.farook.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/FloatLabelExample/FloatLabelExample.xcodeproj/xcuserdata/pawanjoshi.xcuserdatad/xcschemes/xcschememanagement.plist b/FloatLabelExample/FloatLabelExample.xcodeproj/xcuserdata/pawanjoshi.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..27d5d13 --- /dev/null +++ b/FloatLabelExample/FloatLabelExample.xcodeproj/xcuserdata/pawanjoshi.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + FloatLabelExample.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/FloatLabelExample/FloatLabelExample/AppDelegate.swift b/FloatLabelExample/FloatLabelExample/AppDelegate.swift index bc9ceff..21cc0ef 100644 --- a/FloatLabelExample/FloatLabelExample/AppDelegate.swift +++ b/FloatLabelExample/FloatLabelExample/AppDelegate.swift @@ -12,7 +12,7 @@ import UIKit class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(_ application:UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey:Any]? = nil) -> Bool { + func application(_ application:UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey:Any]? = nil) -> Bool { // Override point for customization after application launch. return true } diff --git a/FloatLabelFields/FloatLabelTextField.swift b/FloatLabelFields/FloatLabelTextField.swift index 57c9462..2441208 100644 --- a/FloatLabelFields/FloatLabelTextField.swift +++ b/FloatLabelFields/FloatLabelTextField.swift @@ -15,185 +15,189 @@ import UIKit @IBDesignable class FloatLabelTextField: UITextField { - let animationDuration = 0.3 - var title = UILabel() - - // MARK:- Properties - override var accessibilityLabel:String? { - get { - if let txt = text , txt.isEmpty { - return title.text - } else { - return text - } - } - set { - self.accessibilityLabel = newValue - } - } - - override var placeholder:String? { - didSet { - title.text = placeholder - title.sizeToFit() - } - } - - override var attributedPlaceholder:NSAttributedString? { - didSet { - title.text = attributedPlaceholder?.string - title.sizeToFit() - } - } - - var titleFont:UIFont = UIFont.systemFont(ofSize: 12.0) { - didSet { - title.font = titleFont - title.sizeToFit() - } - } - - @IBInspectable var hintYPadding:CGFloat = 0.0 - - @IBInspectable var titleYPadding:CGFloat = 0.0 { - didSet { - var r = title.frame - r.origin.y = titleYPadding - title.frame = r - } - } - - @IBInspectable var titleTextColour:UIColor = UIColor.gray { - didSet { - if !isFirstResponder { - title.textColor = titleTextColour - } - } - } - - @IBInspectable var titleActiveTextColour:UIColor! { - didSet { - if isFirstResponder { - title.textColor = titleActiveTextColour - } - } - } - - // MARK:- Init - required init?(coder aDecoder:NSCoder) { - super.init(coder:aDecoder) - setup() - } - - override init(frame:CGRect) { - super.init(frame:frame) - setup() - } - - // MARK:- Overrides - override func layoutSubviews() { - super.layoutSubviews() - setTitlePositionForTextAlignment() - let isResp = isFirstResponder - if let txt = text , !txt.isEmpty && isResp { - title.textColor = titleActiveTextColour - } else { - title.textColor = titleTextColour - } - // Should we show or hide the title label? - if let txt = text , txt.isEmpty { - // Hide - hideTitle(isResp) - } else { - // Show - showTitle(isResp) - } - } - - override func textRect(forBounds bounds:CGRect) -> CGRect { - var r = super.textRect(forBounds: bounds) - if let txt = text , !txt.isEmpty { - var top = ceil(title.font.lineHeight + hintYPadding) - top = min(top, maxTopInset()) - r = UIEdgeInsetsInsetRect(r, UIEdgeInsetsMake(top, 0.0, 0.0, 0.0)) - } - return r.integral - } - - override func editingRect(forBounds bounds:CGRect) -> CGRect { - var r = super.editingRect(forBounds: bounds) - if let txt = text , !txt.isEmpty { - var top = ceil(title.font.lineHeight + hintYPadding) - top = min(top, maxTopInset()) - r = UIEdgeInsetsInsetRect(r, UIEdgeInsetsMake(top, 0.0, 0.0, 0.0)) - } - return r.integral - } - - override func clearButtonRect(forBounds bounds:CGRect) -> CGRect { - var r = super.clearButtonRect(forBounds: bounds) - if let txt = text , !txt.isEmpty { - var top = ceil(title.font.lineHeight + hintYPadding) - top = min(top, maxTopInset()) - r = CGRect(x:r.origin.x, y:r.origin.y + (top * 0.5), width:r.size.width, height:r.size.height) - } - return r.integral - } - - // MARK:- Public Methods - - // MARK:- Private Methods - fileprivate func setup() { - borderStyle = UITextBorderStyle.none - titleActiveTextColour = tintColor - // Set up title label - title.alpha = 0.0 - title.font = titleFont - title.textColor = titleTextColour - if let str = placeholder , !str.isEmpty { - title.text = str - title.sizeToFit() - } - self.addSubview(title) - } - - fileprivate func maxTopInset()->CGFloat { - if let fnt = font { - return max(0, floor(bounds.size.height - fnt.lineHeight - 4.0)) - } - return 0 - } - - fileprivate func setTitlePositionForTextAlignment() { - let r = textRect(forBounds: bounds) - var x = r.origin.x - if textAlignment == NSTextAlignment.center { - x = r.origin.x + (r.size.width * 0.5) - title.frame.size.width - } else if textAlignment == NSTextAlignment.right { - x = r.origin.x + r.size.width - title.frame.size.width - } - title.frame = CGRect(x:x, y:title.frame.origin.y, width:title.frame.size.width, height:title.frame.size.height) - } - - fileprivate func showTitle(_ animated:Bool) { - let dur = animated ? animationDuration : 0 - UIView.animate(withDuration: dur, delay:0, options: [UIViewAnimationOptions.beginFromCurrentState, UIViewAnimationOptions.curveEaseOut], animations:{ - // Animation - self.title.alpha = 1.0 - var r = self.title.frame - r.origin.y = self.titleYPadding - self.title.frame = r - }, completion:nil) - } - - fileprivate func hideTitle(_ animated:Bool) { - let dur = animated ? animationDuration : 0 - UIView.animate(withDuration: dur, delay:0, options: [UIViewAnimationOptions.beginFromCurrentState, UIViewAnimationOptions.curveEaseIn], animations:{ - // Animation - self.title.alpha = 0.0 - var r = self.title.frame - r.origin.y = self.title.font.lineHeight + self.hintYPadding - self.title.frame = r - }, completion:nil) - } + let animationDuration = 0.3 + var title = UILabel() + + // MARK: - Properties + + override var accessibilityLabel: String? { + get { + if let txt = text, txt.isEmpty { + return title.text + } else { + return text + } + } + set { + self.accessibilityLabel = newValue + } + } + + override var placeholder: String? { + didSet { + title.text = placeholder + title.sizeToFit() + } + } + + override var attributedPlaceholder: NSAttributedString? { + didSet { + title.text = attributedPlaceholder?.string + title.sizeToFit() + } + } + + var titleFont: UIFont = UIFont.systemFont(ofSize: 12.0) { + didSet { + title.font = titleFont + title.sizeToFit() + } + } + + @IBInspectable var hintYPadding: CGFloat = 0.0 + + @IBInspectable var titleYPadding: CGFloat = 0.0 { + didSet { + var r = title.frame + r.origin.y = titleYPadding + title.frame = r + } + } + + @IBInspectable var titleTextColour: UIColor = .gray { + didSet { + if !isFirstResponder { + title.textColor = titleTextColour + } + } + } + + @IBInspectable var titleActiveTextColour: UIColor! { + didSet { + if isFirstResponder { + title.textColor = titleActiveTextColour + } + } + } + + // MARK: - Init + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setup() + } + + override init(frame: CGRect) { + super.init(frame: frame) + setup() + } + + // MARK: - Overrides + + override func layoutSubviews() { + super.layoutSubviews() + setTitlePositionForTextAlignment() + let isResp = isFirstResponder + if let txt = text, !txt.isEmpty, isResp { + title.textColor = titleActiveTextColour + } else { + title.textColor = titleTextColour + } + // Should we show or hide the title label? + if let txt = text, txt.isEmpty { + // Hide + hideTitle(isResp) + } else { + // Show + showTitle(isResp) + } + } + + override func textRect(forBounds bounds: CGRect) -> CGRect { + var r = super.textRect(forBounds: bounds) + if let txt = text, !txt.isEmpty { + var top = ceil(title.font.lineHeight + hintYPadding) + top = min(top, maxTopInset()) + r = r.inset(by: UIEdgeInsets(top: top, left: 0.0, bottom: 0.0, right: 0.0)) + } + return r.integral + } + + override func editingRect(forBounds bounds: CGRect) -> CGRect { + var r = super.editingRect(forBounds: bounds) + if let txt = text, !txt.isEmpty { + var top = ceil(title.font.lineHeight + hintYPadding) + top = min(top, maxTopInset()) + r = r.inset(by: UIEdgeInsets(top: top, left: 0.0, bottom: 0.0, right: 0.0)) + } + return r.integral + } + + override func clearButtonRect(forBounds bounds: CGRect) -> CGRect { + var r = super.clearButtonRect(forBounds: bounds) + if let txt = text, !txt.isEmpty { + var top = ceil(title.font.lineHeight + hintYPadding) + top = min(top, maxTopInset()) + r = CGRect(x: r.origin.x, y: r.origin.y + (top * 0.5), width: r.size.width, height: r.size.height) + } + return r.integral + } + + // MARK: - Public Methods + + // MARK: - Private Methods + + fileprivate func setup() { + borderStyle = .none + titleActiveTextColour = tintColor + // Set up title label + title.alpha = 0.0 + title.font = titleFont + title.textColor = titleTextColour + if let str = placeholder, !str.isEmpty { + title.text = str + title.sizeToFit() + } + addSubview(title) + } + + fileprivate func maxTopInset() -> CGFloat { + if let fnt = font { + return max(0, floor(bounds.size.height - fnt.lineHeight - 4.0)) + } + return 0 + } + + fileprivate func setTitlePositionForTextAlignment() { + let r = textRect(forBounds: bounds) + var x = r.origin.x + if textAlignment == .center { + x = r.origin.x + (r.size.width * 0.5) - title.frame.size.width + } else if textAlignment == .right { + x = r.origin.x + r.size.width - title.frame.size.width + } + title.frame = CGRect(x: x, y: title.frame.origin.y, width: title.frame.size.width, height: title.frame.size.height) + } + + fileprivate func showTitle(_ animated: Bool) { + let dur = animated ? animationDuration : 0 + UIView.animate(withDuration: dur, delay: 0, options: [.beginFromCurrentState, .curveEaseOut], animations: { + // Animation + self.title.alpha = 1.0 + var r = self.title.frame + r.origin.y = self.titleYPadding + self.title.frame = r + }, completion: nil) + } + + fileprivate func hideTitle(_ animated: Bool) { + let dur = animated ? animationDuration : 0 + UIView.animate(withDuration: dur, delay: 0, options: [.beginFromCurrentState, .curveEaseIn], animations: { + // Animation + self.title.alpha = 0.0 + var r = self.title.frame + r.origin.y = self.title.font.lineHeight + self.hintYPadding + self.title.frame = r + }, completion: nil) + } } diff --git a/FloatLabelFields/FloatLabelTextView.swift b/FloatLabelFields/FloatLabelTextView.swift index 3b24ae5..f18ef4e 100644 --- a/FloatLabelFields/FloatLabelTextView.swift +++ b/FloatLabelFields/FloatLabelTextView.swift @@ -9,205 +9,208 @@ import UIKit @IBDesignable class FloatLabelTextView: UITextView { - let animationDuration = 0.3 - let placeholderTextColor = UIColor.lightGray.withAlphaComponent(0.65) - fileprivate var isIB = false - fileprivate var title = UILabel() - fileprivate var hintLabel = UILabel() - fileprivate var initialTopInset:CGFloat = 0 - - // MARK:- Properties - override var accessibilityLabel:String? { - get { - if text.isEmpty { - return title.text! - } else { - return text - } - } - set { - } - } - - var titleFont:UIFont = UIFont.systemFont(ofSize: 12.0) { - didSet { - title.font = titleFont - } - } - - @IBInspectable var hint:String = "" { - didSet { - title.text = hint - title.sizeToFit() - var r = title.frame - r.size.width = frame.size.width - title.frame = r - hintLabel.text = hint - hintLabel.sizeToFit() - } - } - - @IBInspectable var hintYPadding:CGFloat = 0.0 { - didSet { - adjustTopTextInset() - } - } - - @IBInspectable var titleYPadding:CGFloat = 0.0 { - didSet { - var r = title.frame - r.origin.y = titleYPadding - title.frame = r - } - } - - @IBInspectable var titleTextColour:UIColor = UIColor.gray { - didSet { - if !isFirstResponder { - title.textColor = titleTextColour - } - } - } - - @IBInspectable var titleActiveTextColour:UIColor = UIColor.cyan { - didSet { - if isFirstResponder { - title.textColor = titleActiveTextColour - } - } - } - - // MARK:- Init - required init?(coder aDecoder:NSCoder) { - super.init(coder:aDecoder) - setup() - } - - override init(frame:CGRect, textContainer:NSTextContainer?) { - super.init(frame:frame, textContainer:textContainer) - setup() - } - - deinit { - if !isIB { - let nc = NotificationCenter.default - nc.removeObserver(self, name:NSNotification.Name.UITextViewTextDidChange, object:self) - nc.removeObserver(self, name:NSNotification.Name.UITextViewTextDidBeginEditing, object:self) - nc.removeObserver(self, name:NSNotification.Name.UITextViewTextDidEndEditing, object:self) - } - } - - // MARK:- Overrides - override func prepareForInterfaceBuilder() { - isIB = true - setup() - } - - override func layoutSubviews() { - super.layoutSubviews() - adjustTopTextInset() - hintLabel.alpha = text.isEmpty ? 1.0 : 0.0 - let r = textRect() - hintLabel.frame = CGRect(x:r.origin.x, y:r.origin.y, width:hintLabel.frame.size.width, height:hintLabel.frame.size.height) - setTitlePositionForTextAlignment() - let isResp = isFirstResponder - if isResp && !text.isEmpty { - title.textColor = titleActiveTextColour - } else { - title.textColor = titleTextColour - } - // Should we show or hide the title label? - if text.isEmpty { - // Hide - hideTitle(isResp) - } else { - // Show - showTitle(isResp) - } - } - - // MARK:- Private Methods - fileprivate func setup() { - initialTopInset = textContainerInset.top - textContainer.lineFragmentPadding = 0.0 - titleActiveTextColour = tintColor - // Placeholder label - hintLabel.font = font - hintLabel.text = hint - hintLabel.numberOfLines = 0 - hintLabel.lineBreakMode = NSLineBreakMode.byWordWrapping - hintLabel.backgroundColor = UIColor.clear - hintLabel.textColor = placeholderTextColor - insertSubview(hintLabel, at:0) - // Set up title label - title.alpha = 0.0 - title.font = titleFont - title.textColor = titleTextColour - title.backgroundColor = backgroundColor - if !hint.isEmpty { - title.text = hint - title.sizeToFit() - } - self.addSubview(title) - // Observers - if !isIB { - let nc = NotificationCenter.default - nc.addObserver(self, selector:#selector(UIView.layoutSubviews), name:NSNotification.Name.UITextViewTextDidChange, object:self) - nc.addObserver(self, selector:#selector(UIView.layoutSubviews), name:NSNotification.Name.UITextViewTextDidBeginEditing, object:self) - nc.addObserver(self, selector:#selector(UIView.layoutSubviews), name:NSNotification.Name.UITextViewTextDidEndEditing, object:self) - } - } - - fileprivate func adjustTopTextInset() { - var inset = textContainerInset - inset.top = initialTopInset + title.font.lineHeight + hintYPadding - textContainerInset = inset - } - - fileprivate func textRect()->CGRect { - var r = UIEdgeInsetsInsetRect(bounds, contentInset) - r.origin.x += textContainer.lineFragmentPadding - r.origin.y += textContainerInset.top - return r.integral - } - - fileprivate func setTitlePositionForTextAlignment() { - var titleLabelX = textRect().origin.x - var placeholderX = titleLabelX - if textAlignment == NSTextAlignment.center { - titleLabelX = (frame.size.width - title.frame.size.width) * 0.5 - placeholderX = (frame.size.width - hintLabel.frame.size.width) * 0.5 - } else if textAlignment == NSTextAlignment.right { - titleLabelX = frame.size.width - title.frame.size.width - placeholderX = frame.size.width - hintLabel.frame.size.width - } - var r = title.frame - r.origin.x = titleLabelX - title.frame = r - r = hintLabel.frame - r.origin.x = placeholderX - hintLabel.frame = r - } - - fileprivate func showTitle(_ animated:Bool) { - let dur = animated ? animationDuration : 0 - UIView.animate(withDuration: dur, delay:0, options: [UIViewAnimationOptions.beginFromCurrentState, UIViewAnimationOptions.curveEaseOut], animations:{ - // Animation - self.title.alpha = 1.0 - var r = self.title.frame - r.origin.y = self.titleYPadding + self.contentOffset.y - self.title.frame = r - }, completion:nil) - } - - fileprivate func hideTitle(_ animated:Bool) { - let dur = animated ? animationDuration : 0 - UIView.animate(withDuration: dur, delay:0, options: [UIViewAnimationOptions.beginFromCurrentState, UIViewAnimationOptions.curveEaseIn], animations:{ - // Animation - self.title.alpha = 0.0 - var r = self.title.frame - r.origin.y = self.title.font.lineHeight + self.hintYPadding - self.title.frame = r - }, completion:nil) - } + let animationDuration = 0.3 + let placeholderTextColor = UIColor.lightGray.withAlphaComponent(0.65) + fileprivate var isIB = false + fileprivate var title = UILabel() + fileprivate var hintLabel = UILabel() + fileprivate var initialTopInset: CGFloat = 0 + + // MARK: - Properties + + override var accessibilityLabel: String? { + get { + if text.isEmpty { + return title.text! + } else { + return text + } + } + set {} + } + + var titleFont: UIFont = UIFont.systemFont(ofSize: 12.0) { + didSet { + title.font = titleFont + } + } + + @IBInspectable var hint: String = "" { + didSet { + title.text = hint + title.sizeToFit() + var r = title.frame + r.size.width = frame.size.width + title.frame = r + hintLabel.text = hint + hintLabel.sizeToFit() + } + } + + @IBInspectable var hintYPadding: CGFloat = 0.0 { + didSet { + adjustTopTextInset() + } + } + + @IBInspectable var titleYPadding: CGFloat = 0.0 { + didSet { + var r = title.frame + r.origin.y = titleYPadding + title.frame = r + } + } + + @IBInspectable var titleTextColour: UIColor = UIColor.gray { + didSet { + if !isFirstResponder { + title.textColor = titleTextColour + } + } + } + + @IBInspectable var titleActiveTextColour: UIColor = UIColor.cyan { + didSet { + if isFirstResponder { + title.textColor = titleActiveTextColour + } + } + } + + // MARK: - Init + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setup() + } + + override init(frame: CGRect, textContainer: NSTextContainer?) { + super.init(frame: frame, textContainer: textContainer) + setup() + } + + deinit { + if !isIB { + let nc = NotificationCenter.default + nc.removeObserver(self, name: UITextView.textDidChangeNotification, object: self) + nc.removeObserver(self, name: UITextView.textDidBeginEditingNotification, object: self) + nc.removeObserver(self, name: UITextView.textDidEndEditingNotification, object: self) + } + } + + // MARK: - Overrides + + override func prepareForInterfaceBuilder() { + isIB = true + setup() + } + + override func layoutSubviews() { + super.layoutSubviews() + adjustTopTextInset() + hintLabel.alpha = text.isEmpty ? 1.0 : 0.0 + let r = textRect() + hintLabel.frame = CGRect(x: r.origin.x, y: r.origin.y, width: hintLabel.frame.size.width, height: hintLabel.frame.size.height) + setTitlePositionForTextAlignment() + let isResp = isFirstResponder + if isResp, !text.isEmpty { + title.textColor = titleActiveTextColour + } else { + title.textColor = titleTextColour + } + // Should we show or hide the title label? + if text.isEmpty { + // Hide + hideTitle(isResp) + } else { + // Show + showTitle(isResp) + } + } + + // MARK: - Private Methods + + fileprivate func setup() { + initialTopInset = textContainerInset.top + textContainer.lineFragmentPadding = 0.0 + titleActiveTextColour = tintColor + // Placeholder label + hintLabel.font = font + hintLabel.text = hint + hintLabel.numberOfLines = 0 + hintLabel.lineBreakMode = NSLineBreakMode.byWordWrapping + hintLabel.backgroundColor = UIColor.clear + hintLabel.textColor = placeholderTextColor + insertSubview(hintLabel, at: 0) + // Set up title label + title.alpha = 0.0 + title.font = titleFont + title.textColor = titleTextColour + title.backgroundColor = backgroundColor + if !hint.isEmpty { + title.text = hint + title.sizeToFit() + } + addSubview(title) + // Observers + if !isIB { + let nc = NotificationCenter.default + nc.addObserver(self, selector: #selector(UIView.layoutSubviews), name: UITextView.textDidChangeNotification, object: self) + nc.addObserver(self, selector: #selector(UIView.layoutSubviews), name: UITextView.textDidBeginEditingNotification, object: self) + nc.addObserver(self, selector: #selector(UIView.layoutSubviews), name: UITextView.textDidEndEditingNotification, object: self) + } + } + + fileprivate func adjustTopTextInset() { + var inset = textContainerInset + inset.top = initialTopInset + title.font.lineHeight + hintYPadding + textContainerInset = inset + } + + fileprivate func textRect() -> CGRect { + var r = bounds.inset(by: contentInset) + r.origin.x += textContainer.lineFragmentPadding + r.origin.y += textContainerInset.top + return r.integral + } + + fileprivate func setTitlePositionForTextAlignment() { + var titleLabelX = textRect().origin.x + var placeholderX = titleLabelX + if textAlignment == NSTextAlignment.center { + titleLabelX = (frame.size.width - title.frame.size.width) * 0.5 + placeholderX = (frame.size.width - hintLabel.frame.size.width) * 0.5 + } else if textAlignment == NSTextAlignment.right { + titleLabelX = frame.size.width - title.frame.size.width + placeholderX = frame.size.width - hintLabel.frame.size.width + } + var r = title.frame + r.origin.x = titleLabelX + title.frame = r + r = hintLabel.frame + r.origin.x = placeholderX + hintLabel.frame = r + } + + fileprivate func showTitle(_ animated: Bool) { + let dur = animated ? animationDuration : 0 + UIView.animate(withDuration: dur, delay: 0, options: [UIView.AnimationOptions.beginFromCurrentState, UIView.AnimationOptions.curveEaseOut], animations: { + // Animation + self.title.alpha = 1.0 + var r = self.title.frame + r.origin.y = self.titleYPadding + self.contentOffset.y + self.title.frame = r + }, completion: nil) + } + + fileprivate func hideTitle(_ animated: Bool) { + let dur = animated ? animationDuration : 0 + UIView.animate(withDuration: dur, delay: 0, options: [UIView.AnimationOptions.beginFromCurrentState, UIView.AnimationOptions.curveEaseIn], animations: { + // Animation + self.title.alpha = 0.0 + var r = self.title.frame + r.origin.y = self.title.font.lineHeight + self.hintYPadding + self.title.frame = r + }, completion: nil) + } }