Created
April 9, 2015 21:10
-
-
Save archfear/bb5f21b88e2d57258747 to your computer and use it in GitHub Desktop.
Subclass of UITextView that allows placeholder text
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
class PlaceholderTextView: UITextView { | |
var placeholder: String { | |
get { | |
return placeholderTextView.text | |
} | |
set { | |
placeholderTextView.text = newValue | |
resizePlaceholderFrame() | |
} | |
} | |
var placeholderTextColor: UIColor { | |
get { | |
return placeholderTextView.textColor | |
} | |
set { | |
placeholderTextView.textColor = newValue | |
} | |
} | |
private let placeholderKey = "placeholder" | |
private let fontKey = "font" | |
private let attributedTextKey = "attributedText" | |
private let textKey = "text" | |
private let exclusionPathsKey = "exclusionPaths" | |
private let lineFragmentPaddingKey = "lineFragmentPadding" | |
private let textContainerInsetKey = "textContainerInset" | |
private let textAlignmentKey = "textAlignment" | |
private let editableKey = "editable" | |
private lazy var placeholderTextView = UITextView() | |
private var isPlaceholderPrepared = false | |
required init(coder aDecoder: NSCoder) { | |
super.init(coder: aDecoder) | |
preparePlaceholder() | |
} | |
override init(frame: CGRect, textContainer: NSTextContainer?) { | |
super.init(frame: frame, textContainer: textContainer) | |
preparePlaceholder() | |
} | |
deinit { | |
NSNotificationCenter.defaultCenter().removeObserver(self) | |
removeObserver(self, forKeyPath: placeholderKey) | |
removeObserver(self, forKeyPath: fontKey) | |
removeObserver(self, forKeyPath: attributedTextKey) | |
removeObserver(self, forKeyPath: textKey) | |
removeObserver(self, forKeyPath: textAlignmentKey) | |
removeObserver(self, forKeyPath: editableKey) | |
textContainer.removeObserver(self, forKeyPath: exclusionPathsKey) | |
textContainer.removeObserver(self, forKeyPath: lineFragmentPaddingKey) | |
removeObserver(self, forKeyPath: textContainerInsetKey) | |
} | |
private func preparePlaceholder() { | |
assert(!isPlaceholderPrepared, "placeholder has been prepared already: \(placeholderTextView)") | |
isPlaceholderPrepared = true | |
// the label which displays the placeholder | |
// needs to inherit some properties from its parent text view | |
placeholderTextView.frame = bounds | |
placeholderTextView.opaque = false | |
placeholderTextView.backgroundColor = UIColor.clearColor() | |
placeholderTextView.textColor = UIColor(white: 0.7, alpha: 0.7) | |
placeholderTextView.textAlignment = textAlignment | |
placeholderTextView.editable = false | |
placeholderTextView.scrollEnabled = false | |
placeholderTextView.userInteractionEnabled = false | |
placeholderTextView.font = font | |
placeholderTextView.isAccessibilityElement = false | |
placeholderTextView.contentOffset = contentOffset | |
placeholderTextView.contentInset = contentInset | |
placeholderTextView.selectable = false | |
placeholderTextView.textContainer.exclusionPaths = textContainer.exclusionPaths | |
placeholderTextView.textContainer.lineFragmentPadding = textContainer.lineFragmentPadding | |
placeholderTextView.textContainerInset = textContainerInset | |
placeholderTextView.text = placeholder | |
placeholderTextView.hidden = !editable // hide the placeholder when the text view is not editable | |
setPlaceholderVisibleForText(text) | |
clipsToBounds = true | |
// add observers | |
NSNotificationCenter.defaultCenter().addObserver(self, selector: "textDidChange:", name: UITextViewTextDidChangeNotification, object: self) | |
addObserver(self, forKeyPath: placeholderKey, options: NSKeyValueObservingOptions.New, context: nil) | |
addObserver(self, forKeyPath: fontKey, options: NSKeyValueObservingOptions.New, context: nil) | |
addObserver(self, forKeyPath: attributedTextKey, options: NSKeyValueObservingOptions.New, context: nil) | |
addObserver(self, forKeyPath: textKey, options: NSKeyValueObservingOptions.New, context: nil) | |
addObserver(self, forKeyPath: textAlignmentKey, options: NSKeyValueObservingOptions.New, context: nil) | |
addObserver(self, forKeyPath: editableKey, options: NSKeyValueObservingOptions.New, context: nil) | |
textContainer.addObserver(self, forKeyPath: exclusionPathsKey, options: NSKeyValueObservingOptions.New, context: nil) | |
textContainer.addObserver(self, forKeyPath: lineFragmentPaddingKey, options: NSKeyValueObservingOptions.New, context: nil) | |
addObserver(self, forKeyPath: textContainerInsetKey, options: NSKeyValueObservingOptions.New, context: nil) | |
} | |
private func resizePlaceholderFrame() { | |
placeholderTextView.frame.size = bounds.size | |
} | |
private func setPlaceholderVisibleForText(text: String) { | |
if text.isEmpty { | |
addSubview(placeholderTextView) | |
sendSubviewToBack(placeholderTextView) | |
} else { | |
placeholderTextView.removeFromSuperview() | |
} | |
} | |
// MARK: - Observers | |
func textDidChange(notification: NSNotification) { | |
setPlaceholderVisibleForText(self.text) | |
} | |
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) { | |
switch keyPath { | |
case placeholderKey: | |
placeholderTextView.text = change[NSKeyValueChangeNewKey] as! NSString as String | |
case fontKey: | |
placeholderTextView.font = change[NSKeyValueChangeNewKey] as! UIFont | |
case attributedTextKey: | |
if let newAttributedText = change[NSKeyValueChangeNewKey] as? NSAttributedString { | |
setPlaceholderVisibleForText(newAttributedText.string) | |
} else { | |
setPlaceholderVisibleForText("") | |
} | |
case textKey: | |
let newText = change[NSKeyValueChangeNewKey] as! NSString | |
setPlaceholderVisibleForText(newText as! String) | |
case exclusionPathsKey: | |
placeholderTextView.textContainer.exclusionPaths = change[NSKeyValueChangeNewKey] as? [AnyObject] | |
resizePlaceholderFrame() | |
case lineFragmentPaddingKey: | |
placeholderTextView.textContainer.lineFragmentPadding = change[NSKeyValueChangeNewKey] as! CGFloat | |
resizePlaceholderFrame() | |
case textContainerInsetKey: | |
let value = change[NSKeyValueChangeNewKey] as! NSValue | |
placeholderTextView.textContainerInset = value.UIEdgeInsetsValue() | |
case textAlignmentKey: | |
let alignment = change[NSKeyValueChangeNewKey] as! NSNumber | |
placeholderTextView.textAlignment = NSTextAlignment(rawValue: alignment.integerValue)! | |
case editableKey: | |
let editable = change[NSKeyValueChangeNewKey] as! Bool | |
placeholderTextView.hidden = !editable | |
default: | |
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context) | |
} | |
} | |
// MARK: - Overrides | |
override func layoutSubviews() { | |
super.layoutSubviews() | |
resizePlaceholderFrame() | |
} | |
override func becomeFirstResponder() -> Bool { | |
setPlaceholderVisibleForText(self.text) | |
return super.becomeFirstResponder() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment