diff --git a/Readme.markdown b/Readme.markdown index c81f28c..c1e877a 100644 --- a/Readme.markdown +++ b/Readme.markdown @@ -2,10 +2,11 @@ Add a placeholder to UITextView. -SAMTextView is tested on iOS 6 and requires ARC. Released under the [MIT license](LICENSE). +SAMTextView is tested on iOS 7-9 and requires ARC. Released under the [MIT license](LICENSE). ## Usage +#### Objective-C ``` objc // Initialize a text view SAMTextView *textView = [[SAMTextView alloc] initWithFrame:CGRectMake(20.0f, 20.0f, 280.0f, 280.0f)]; @@ -14,8 +15,17 @@ SAMTextView *textView = [[SAMTextView alloc] initWithFrame:CGRectMake(20.0f, 20. textView.placeholder = @"Type something…"; ``` -For more advanced control of the placeholder, you can set the `attributedPlaceholder` property instead. See the [header](SAMTextView/SAMTextView.h) for full documentation. +#### Swift +``` swift +// Initialize a text view +let textView = SAMTextView(frame:CGRectMake(20.0, 20.0, 280.0, 280.0)) + +// Add a placeholder +textView.placeholder = "Type something…" +``` + +For more advanced control of the placeholder, you can set the `attributedPlaceholder` property instead. See the [header](SAMTextView/SAMTextView.swift) for full documentation. ## Installation -Simply add the files in the `SAMTextView.h` and `SAMTextView.m` to your project or add `SAMTextView` to your Podfile if you're using CocoaPods. +Simply add the file `SAMTextView.swift` to your project or add `SAMTextView` to your Podfile if you're using CocoaPods. diff --git a/SAMTextView/SAMTextView.h b/SAMTextView/SAMTextView.h deleted file mode 100644 index bda3ee0..0000000 --- a/SAMTextView/SAMTextView.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// SAMTextView.h -// SAMTextView -// -// Created by Sam Soffes on 8/18/10. -// Copyright 2010-2014 Sam Soffes. All rights reserved. -// - -#import - -/** - UITextView subclass that adds placeholder support like UITextField has. - */ -@interface SAMTextView : UITextView - -/** - The string that is displayed when there is no other text in the text view. This property reads and writes the - attributed variant. - - The default value is `nil`. - */ -@property (nonatomic, strong) NSString *placeholder; - -/** - The attributed string that is displayed when there is no other text in the text view. - - The default value is `nil`. - */ -@property (nonatomic, strong) NSAttributedString *attributedPlaceholder; - -/** - Returns the drawing rectangle for the text views’s placeholder text. - - @param bounds The bounding rectangle of the receiver. - @return The computed drawing rectangle for the placeholder text. - */ -- (CGRect)placeholderRectForBounds:(CGRect)bounds; - -@end diff --git a/SAMTextView/SAMTextView.m b/SAMTextView/SAMTextView.m deleted file mode 100644 index 8b3344b..0000000 --- a/SAMTextView/SAMTextView.m +++ /dev/null @@ -1,170 +0,0 @@ -// -// SAMTextView.m -// SAMTextView -// -// Created by Sam Soffes on 8/18/10. -// Copyright 2010-2014 Sam Soffes. All rights reserved. -// - -#import "SAMTextView.h" - -@implementation SAMTextView - -#pragma mark - Accessors - -@synthesize attributedPlaceholder = _attributedPlaceholder; - -- (void)setText:(NSString *)string { - [super setText:string]; - [self setNeedsDisplay]; -} - - -- (void)insertText:(NSString *)string { - [super insertText:string]; - [self setNeedsDisplay]; -} - - -- (void)setAttributedText:(NSAttributedString *)attributedText { - [super setAttributedText:attributedText]; - [self setNeedsDisplay]; -} - - -- (void)setPlaceholder:(NSString *)string { - if ([string isEqualToString:self.attributedPlaceholder.string]) { - return; - } - - NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init]; - if ([self isFirstResponder] && self.typingAttributes) { - [attributes addEntriesFromDictionary:self.typingAttributes]; - } else { - attributes[NSFontAttributeName] = self.font; - attributes[NSForegroundColorAttributeName] = [UIColor colorWithWhite:0.702f alpha:1.0f]; - - if (self.textAlignment != NSTextAlignmentLeft) { - NSMutableParagraphStyle *paragraph = [[NSMutableParagraphStyle alloc] init]; - paragraph.alignment = self.textAlignment; - attributes[NSParagraphStyleAttributeName] = paragraph; - } - } - - self.attributedPlaceholder = [[NSAttributedString alloc] initWithString:string attributes:attributes]; -} - - -- (NSString *)placeholder { - return self.attributedPlaceholder.string; -} - - -- (void)setAttributedPlaceholder:(NSAttributedString *)attributedPlaceholder { - if ([_attributedPlaceholder isEqualToAttributedString:attributedPlaceholder]) { - return; - } - - _attributedPlaceholder = attributedPlaceholder; - - [self setNeedsDisplay]; -} - - -- (void)setContentInset:(UIEdgeInsets)contentInset { - [super setContentInset:contentInset]; - [self setNeedsDisplay]; -} - - -- (void)setFont:(UIFont *)font { - [super setFont:font]; - [self setNeedsDisplay]; -} - - -- (void)setTextAlignment:(NSTextAlignment)textAlignment { - [super setTextAlignment:textAlignment]; - [self setNeedsDisplay]; -} - - -#pragma mark - NSObject - -- (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidChangeNotification object:self]; -} - - -#pragma mark - UIView - -- (id)initWithCoder:(NSCoder *)aDecoder { - if ((self = [super initWithCoder:aDecoder])) { - [self initialize]; - } - return self; -} - - -- (id)initWithFrame:(CGRect)frame { - if ((self = [super initWithFrame:frame])) { - [self initialize]; - } - return self; -} - - -- (void)drawRect:(CGRect)rect { - [super drawRect:rect]; - - // Draw placeholder if necessary - if (self.text.length == 0 && self.attributedPlaceholder) { - CGRect placeholderRect = [self placeholderRectForBounds:self.bounds]; - [self.attributedPlaceholder drawInRect:placeholderRect]; - } -} - - -- (void)layoutSubviews { - [super layoutSubviews]; - - // Redraw placeholder text when the layout changes if necessary - if (self.attributedPlaceholder && self.text.length == 0) { - [self setNeedsDisplay]; - } -} - - -#pragma mark - Placeholder - -- (CGRect)placeholderRectForBounds:(CGRect)bounds { - CGRect rect = UIEdgeInsetsInsetRect(bounds, self.contentInset); - - if ([self respondsToSelector:@selector(textContainer)]) { - rect = UIEdgeInsetsInsetRect(rect, self.textContainerInset); - CGFloat padding = self.textContainer.lineFragmentPadding; - rect.origin.x += padding; - rect.size.width -= padding * 2.0f; - } else { - if (self.contentInset.left == 0.0f) { - rect.origin.x += 8.0f; - } - rect.origin.y += 8.0f; - } - - return rect; -} - - -#pragma mark - Private - -- (void)initialize { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:self]; -} - - -- (void)textChanged:(NSNotification *)notification { - [self setNeedsDisplay]; -} - -@end diff --git a/SAMTextView/SAMTextView.swift b/SAMTextView/SAMTextView.swift new file mode 100644 index 0000000..17e7ea7 --- /dev/null +++ b/SAMTextView/SAMTextView.swift @@ -0,0 +1,165 @@ +// +// SAMTextView.swift +// SAMTextView +// +// Created by Sam Soffes on 8/18/10. +// Copyright 2010-2014 Sam Soffes. All rights reserved. +// + +import UIKit + +/** + UITextView subclass that adds placeholder support like UITextField has. + */ +public class SAMTextView : UITextView { + public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + initialize() + } + + override init(frame: CGRect, textContainer: NSTextContainer?) { + super.init(frame: frame, textContainer: textContainer) + initialize() + } + + /** + The string that is displayed when there is no other text in the text view. This property reads and writes the + attributed variant. + + The default value is `nil`. + */ + public var placeholder: String? { + get { + return attributedPlaceholder?.string + } + set(newPlaceholder) { + guard let newPlaceholder = newPlaceholder else { return } + guard newPlaceholder != attributedPlaceholder?.string else { return } + + var attributes = [String : AnyObject]() + if isFirstResponder() { + attributes = typingAttributes + } else { + attributes[NSFontAttributeName] = font + attributes[NSForegroundColorAttributeName] = UIColor(white: 0.702, alpha: 1) + + if textAlignment != NSTextAlignment.Left { + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = textAlignment + attributes[NSParagraphStyleAttributeName] = paragraph + } + } + attributedPlaceholder = NSAttributedString(string: newPlaceholder, attributes: attributes) + } + } + + /** + The attributed string that is displayed when there is no other text in the text view. + + The default value is `nil`. + */ + var attributedPlaceholder: NSAttributedString? { + didSet { + setNeedsDisplay() + } + } + + public override var text: String! { + didSet { + setNeedsDisplay() + } + } + + public override var attributedText: NSAttributedString! { + didSet { + setNeedsDisplay() + } + } + + public override var contentInset: UIEdgeInsets { + didSet { + setNeedsDisplay() + } + } + + public override var font: UIFont? { + didSet { + setNeedsDisplay() + } + } + + public override var textAlignment: NSTextAlignment { + didSet { + setNeedsDisplay() + } + } + + public override func insertText(text: String) { + super.insertText(text) + setNeedsDisplay() + } + + // MARK: - Deinit + + deinit { + NSNotificationCenter.defaultCenter().removeObserver(self, name: UITextViewTextDidChangeNotification, object: self) + } + + // MARK: - UIView + + public override func drawRect(rect: CGRect) { + super.drawRect(rect) + + // Draw placeholder if necessary + if text.characters.count == 0 && attributedPlaceholder != nil { + let placeholderRect = placeholderRectForBounds(bounds) + attributedPlaceholder?.drawInRect(placeholderRect) + } + } + + public override func layoutSubviews() { + super.layoutSubviews() + + // Redraw placeholder text when the layout changes if necessary + if attributedPlaceholder != nil && text.characters.count == 0 { + setNeedsDisplay() + } + } + + // MARK: - Private + + private func textChanged() { + setNeedsDisplay() + } + + private func initialize() { + NSNotificationCenter.defaultCenter().addObserverForName(UITextViewTextDidChangeNotification, object: self, queue: nil) { [unowned self] notification in + self.textChanged() + } + } + + /** + Returns the drawing rectangle for the text views’s placeholder text. + + - parameter bounds: The bounding rectangle of the receiver. + + - returns: The computed drawing rectangle for the placeholder text. + */ + private func placeholderRectForBounds(bounds: CGRect) -> CGRect { + var rect = UIEdgeInsetsInsetRect(bounds, contentInset) + + if respondsToSelector("textContainer") { + rect = UIEdgeInsetsInsetRect(rect, textContainerInset) + let padding = textContainer.lineFragmentPadding + rect.origin.x += padding + rect.size.width -= padding * 2.0 + } else { + if contentInset.left == 0.0 { + rect.origin.x += 8.0 + } + rect.origin.y += 8.0 + } + + return rect + } +}