Skip to content

Instantly share code, notes, and snippets.

@archfear
Created April 9, 2015 21:10
Show Gist options
  • Save archfear/bb5f21b88e2d57258747 to your computer and use it in GitHub Desktop.
Save archfear/bb5f21b88e2d57258747 to your computer and use it in GitHub Desktop.
Subclass of UITextView that allows placeholder text
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