Obj-C- Detecting textView line break? - ios

When a user is typing inside of my textView, I want to know when a new line begins, as I want to change the height of my textView when this happens. That said, I know how to accomplish this if the return button is hit - but if a new line just begins automatically (e.g. a new line is started simply because a user keeps typing), this doesn't seem as simple. The code I'm using below works successfully when 'return' is tapped - but I can't seem to get it to detect any sort of line change otherwise.
Hope I worded this well enough. Thanks!
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range
replacementText:(NSString *)text
{
if ([text isEqualToString:#"\n"]) {
self.replyField.text=[NSString stringWithFormat:#"%#\n",self.replyField.text];
CGFloat fixedWidth = self.replyField.frame.size.width;
CGSize newSize = [self.replyField sizeThatFits:CGSizeMake(fixedWidth, MAXFLOAT)];
CGRect newFrame = self.replyField.frame;
newFrame.size = CGSizeMake(fmaxf(newSize.width, fixedWidth), newSize.height);
self.replyField.frame = newFrame;
CGRect newFrame1 = self.upView.frame;
NSLog(#"WHAT IS THE NEW HEIGHT %f", newFrame1.size.height);
self.upView.frame = CGRectOffset(self.upView.frame, 0, -15);
return NO;
}
// For any other character return TRUE so that the text gets added to the view
return YES;
}

Im not sure if this is gonna help at all since im also fairly new to obj-c but give it a go
-(void)textViewDidChange:(UITextView *)textView
{
CGFloat fixedWidth = textView.frame.size.width;
CGSize newSize = [textView sizeThatFits:CGSizeMake(fixedWidth, MAXFLOAT)];
CGRect newFrame = textView.frame;
newFrame.size = CGSizeMake(fmaxf(newSize.width, fixedWidth),
newSize.height);
textView.frame = newFrame;
}
Make sure you also disable scrolling
textview.scrollEnabled = NO;

To detect the change in number of lines, you can use KVO, as #rmaddy suggested with a comment.
- (void)viewDidLoad {
[textView addObserver:self forKeyPath:#"contentSize" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if (change != nil && [keyPath isEqualToString:#"contentSize"] && [object isKindOfClass:UITextView.class]) {
NSNumber *oldValue = change[NSKeyValueChangeOldKey];
NSNumber *newValue = change[NSKeyValueChangeNewKey];
//Do anything you need with the height values here
}
}
In swift
override func viewDidLoad() {
textView.addObserver(self, forKeyPath: "contentSize", options: [.old, .new], context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let change = change, keyPath == "contentSize" && object is UITextView else {
return
}
let oldHeight = (change[.oldKey] as! CGSize).height
let newHeight = (change[.newKey] as! CGSize).height
//Do anything you need with the height values here
}
deinit {
// iOS10 was crashing for me when I didn't explicitly remove
// the observer before destroying the containing object
inputTextView.removeObserver(self, forKeyPath: "contentSize", context: nil)
}

Try this:
For dynamically change UITextView height you can create property of height constraints and set/connect this to textview height constraints from storyboard.
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *txtViewHeightConstraint;
and then after add didChange for checking textview inputs
-(void)textViewDidChange:(UITextView *)textView
{
[self SetupTextField] ;
}
- (void)SetupTextField{
CGRect newFrame = self.txtInfo.frame;
newFrame.size.height = self.txtInfo.contentSize.height;
NSLog(#"NEW HEIGHT %f", newFrame1.size.height);
if (newFrame.size.height < 100.0) { // if condition is optional
self.txtViewHeightConstraint.constant = newFrame.size.height ;
}
}
check screenshot for set height constraint of textview

Related

Keyboard to move the view only if the textfield is hidden

I'm trying to get my app to move the view when the keyboard appears, and so far the results have been... mixed to say the least. I can get it to move, thing is it's either hard coded or only works partially.
I have multiple Textfields in my view, when I tap on them, sometimes depending on where my scroll is it get's hidden by the keyboard.
Now what I need my app to do is to move the view to see the textfield only if the active textfield is hidden by the keyboard.
My Hierarchy for the view goes like this :
So I have a Scroll view, and in the scroll View I have a UIView named ContentView, in the ContentView I have all my textfields and labels.
thing is, I can't hard code it since My app is universal, I need to have the keyboard move the view only if it hides the textfield. Because in a situation where the user is on an iPad, the View will likely never have to move
I used the following Stack overflow answers with no results :
Swift: Scroll View only when a TextField or Button is hidden by the Keyboard
Move view with keyboard using Swift
here's my code that actually comes from one of those answers :
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(NewPaxController.keyboardWillShow), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(NewPaxController.keyboardWillHide), name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWillShow(notification:NSNotification) {
if keyboardIsPresent == false {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
self.ContentView.frame.origin.y -= keyboardSize.height
keyboardIsPresent = true
}
}
}
func keyboardWillHide(notification:NSNotification) {
if keyboardIsPresent == true {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
self.ContentView.frame.origin.y += keyboardSize.height
keyboardIsPresent = false
}
}
}
I'm almost 100% sure all my error come from the fact the I have a ContentView... but I need it in my case. Thanks in advance for your help
You should not modify frames when keyboard shows up.
Instead you need to set the bottom inset of the scrollview scrollView.contentInset.bottom from zero to keyboard height. When the keyboard disappears, you set the inset back to zero.
This whole problem is solvable by mere 10 lines of code.
literally get the keyboard height from notification, store it ion a local variable in the class while KB is showing, and use the value to set insets in delegate callback/event handler methods.
The trick here is that setting nonzero insets will effectively scroll the scrollview together with the content for you up by and that will push the current textfield up as well.
You should first try to locate the UITextField instance by using the following code.
extension UIView {
func firstResponder() -> UIView? {
if self.isFirstResponder() {
return self
}
for subview in self.subviews {
if subview.isFirstResponder() {
return subview
}
}
return nil
}
}
In the keyboardWillShow: function decide if the textfield is visible or not if the keyboard comes up.
func keyboardWillShow(notification:NSNotification) {
if keyboardIsPresent == false {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue(),
let inputView = self.ContentView.firstResponder()
where inputView.frame.maxY > self.ContentView.frame.size.height - keyboardSize.height {
self.ContentView.frame.origin.y -= keyboardSize.height
keyboardIsPresent = true
}
}
}
Than only move the view back in the hide function if it was moved away.
Here is the code for it, It is pretty straight forward, but if you still need help with it, I will explain it further.
#pragma mark - Keyboard Observer events
-(void)keyboardWillShow:(NSNotification*)notification {
NSDictionary *info = [notification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
keyboardHeight = kbSize.height;
[self updateScrollViewPosition];
}
-(void)keyboardDidChange:(NSNotification *)notification {
NSDictionary *info = [notification userInfo];
CGSize kbSizeBegin = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
CGSize kbSizeEnd = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
if (kbSizeBegin.height != kbSizeEnd.height) {
keyboardHeight = kbSizeEnd.height;
if (activeTextField && [activeTextField isFirstResponder]) {
[self updateScrollViewPosition];
}
}
}
-(void)keyboardWillHide:(NSNotification*)notification {
keyboardHeight = 0;
activeTextField = nil;
[self resignAllTextFields];
}
#pragma mark - UITextFieldDelegate Methods
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
activeTextField = textField;
return YES;
}
- (void)textFieldDidBeginEditing:(UITextField *)textField {
activeTextField = textField;
[self updateScrollViewPosition];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
keyboardHeight = 0;
activeTextField = nil;
[textField resignFirstResponder];
return YES;
}
#pragma mark - Update Method
-(void)updateScrollViewPosition {
if (keyboardHeight > 0 && activeTextField) {
CGRect frame = activeTextField.frame;
CGFloat yPoint = scrollView.frame.origin.y+frame.origin.y+frame.size.height+8.0;
CGFloat height = self.view.frame.size.height-keyboardHeight;
CGFloat diff = yPoint-height;
if (diff > 0.0) {
[scrollView setContentOffset:CGPointMake(0, diff) animated:YES];
}
else {
CGFloat diff = scrollView.contentSize.height-scrollView.contentOffset.y;
if (diff<scrollView.frame.size.height) {
diff = scrollView.contentSize.height-scrollView.frame.size.height;
if (diff < 0) {
diff = 0.0;
}
[scrollView setContentOffset:CGPointMake(0, diff) animated:YES];
}
}
}
else {
CGFloat diff = scrollView.contentSize.height-scrollView.contentOffset.y;
if (diff<scrollView.frame.size.height) {
diff = scrollView.contentSize.height-scrollView.frame.size.height;
if (diff < 0) {
diff = 0.0;
}
[scrollView setContentOffset:CGPointMake(0, diff) animated:YES];
}
}
}
#pragma mark
Edit: A simple resignAllTextFields method as requested. containerView is the view which contains all the UITextField.
-(void)resignAllTextFields {
for (UIView *view in containerView.subviews) {
if ([view isKindOfClass:[UITextField class]]) {
UITextField *textField = (UITextField*)view;
[textField resignFirstResponder];
}
}
[self updateScrollViewPosition];
}

Progress of UIPageViewController

I would like to receive updates from the uipageviewcontroller during the page scrolling process. I want to know the transitionProgress in %. (This value should update when the user move the finger in order to get to another page). I'm interested in the animation progress from one page to another, not the progress through the total number of pages.
What I have found so far:
There is a class called UICollectionViewTransitionLayout that have the property corresponding to what I am looking for, "transitionProgress". Probably uipageviewcontroller implement this method somehow?
I can call the following method on the uipagecontroller but I only get 0 as result!
CGFloat percentComplete = [self.pageViewController.transitionCoordinator percentComplete];
in SWIFT to copy paste ;) works perfect for me
extension UIPageViewController: UIScrollViewDelegate {
public override func viewDidLoad() {
super.viewDidLoad()
for subview in view.subviews {
if let scrollView = subview as? UIScrollView {
scrollView.delegate = self
}
}
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
let point = scrollView.contentOffset
var percentComplete: CGFloat
percentComplete = abs(point.x - view.frame.size.width)/view.frame.size.width
print("percentComplete: ",percentComplete)
}
}
At last I found out a solution, even if it is probably not the best way to do it:
I first add an observer on the scrollview like this:
// Get Notified at update of scrollview progress
NSArray *views = self.pageViewController.view.subviews;
UIScrollView* sW = [views objectAtIndex:0];
[sW addObserver:self forKeyPath:#"contentOffset" options:NSKeyValueObservingOptionNew context:NULL];
And when the observer is called:
NSArray *views = self.pageViewController.view.subviews;
UIScrollView* sW = [views objectAtIndex:0];
CGPoint point = sW.contentOffset;
float percentComplete;
//iPhone 5
if([ [ UIScreen mainScreen ] bounds ].size.height == 568){
percentComplete = fabs(point.x - 568)/568;
} else{
//iphone 4
percentComplete = fabs(point.x - 480)/480;
}
NSLog(#"percentComplete: %f", percentComplete);
I'm very happy that I found this :-)
Since I thought that the functionality of scrolling would stay forever, but that the internal implementation may change to something other than a scroll view, I found the solution below (I haven't tested this very much, but still)
NSUInteger offset = 0;
UIViewController * firstVisibleViewController;
while([(firstVisibleViewController = [self viewControllerForPage:offset]).view superview] == nil) {
++offset;
}
CGRect rect = [[firstVisibleViewController.view superview] convertRect:firstVisibleViewController.view.frame fromView:self.view];
CGFloat absolutePosition = rect.origin.x / self.view.frame.size.width;
absolutePosition += (CGFloat)offset;
(self is the UIPageViewController here, and [-viewControllerForPage:] is a method that returns the view controller at the given page)
If absolutePosition is 0.0f, then the first view controller is shown, if it's equal to 1.0f, the second one is shown, etc... This can be called repeatedly in a CADisplayLink along with the delegate methods and/or UIPanGestureRecognizer to effectively know the status of the current progress of the UIPageViewController.
EDIT: Made it work for any number of view controllers
Use this -
for (UIView *v in self.pageViewController.view.subviews) {
if ([v isKindOfClass:[UIScrollView class]]) {
((UIScrollView *)v).delegate = self;
}
}
to implement this protocol : -(void)scrollViewDidScroll:(UIScrollView *)scrollView
and then use #xhist's code (modified) in this way
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGPoint point = scrollView.contentOffset;
float percentComplete;
percentComplete = fabs(point.x - self.view.frame.size.width)/self.view.frame.size.width;
NSLog(#"percentComplete: %f", percentComplete);
}
Based on Appgix solution, I'm adding this directly on my 'UIPageViewController' subclass. (Since I only need it on this one)
For Swift 3:
class MYPageViewControllerSubclass: UIPageViewController, UIScrollViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
for subView in view.subviews {
if subView is UIScrollView {
(subView as! UIScrollView).delegate = self
}
}
}
// MARK: - Scroll View Delegate
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
let point = scrollView.contentOffset
var percentComplete: CGFloat
percentComplete = fabs(point.x - view.frame.size.width)/view.frame.size.width
NSLog("percentComplete: %f", percentComplete)
}
// OTHER CODE GOES HERE...
}
While Appgix' solution seemed to work at first, I noticed that when the user pans in a UIPageViewController, lifts the finger shortly and then immediately starts dragging again while the "snap-back" animation is NOT YET finished and then lifts his finger again (which will again "snap-back"), the scrollViewDidScroll method is only called when the page view controller finished the animation.
For the progress calculation this means the second pan produces continuous values like 0.11, 0.13, 0.16 but when the scroll view snaps back the next progress value will be 1.0 which causes my other scroll view to be out of sync.
To fight this I'm now listening to the scroll view's contentOffset key, which is still updated continuously in this situation.
KVO approach for Swift 4
var myContext = 0
override func viewDidLoad() {
for view in self.view.subviews {
if view is UIScrollView {
view.addObserver(self, forKeyPath: "contentOffset", options: .new, context: &introPagingViewControllerContext)
}
}
}
// MARK: KVO
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?)
{
guard let change = change else { return }
if context != &myContext {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
if keyPath == "contentOffset" {
if let contentOffset = change[NSKeyValueChangeKey.newKey] as? CGPoint {
let screenWidth = UIScreen.main.bounds.width
let percent = abs((contentOffset.x - screenWidth) / screenWidth)
print(percent)
}
}
}

Center the text in a UITextView, vertically and horizontally [duplicate]

This question already has answers here:
Center text vertically in a UITextView
(19 answers)
Closed 7 years ago.
I have a grouped table view with one cell. In this cell I'm putting a UITextView in cellForRowAtIndexPath:, and I instantly make this first Responder.
My problem is: When I start typing, the text is left-justified, not centered horizontally and vertically as I want. This is on iOS 7.
How can I center the text?
I resolve this issue by observing the contentsize of UITextView, when there is any change in the contentSize, update the contentOffset.
Add observer as follows:
[textview addObserver:self forKeyPath:#"contentSize" options:(NSKeyValueObservingOptionNew) context:NULL];
Handle the observer action as follows:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
UITextView *txtview = object;
CGFloat topoffset = ([txtview bounds].size.height - [txtview contentSize].height * [txtview zoomScale])/2.0;
topoffset = ( topoffset < 0.0 ? 0.0 : topoffset );
txtview.contentOffset = (CGPoint){.x = 0, .y = -topoffset};
}
To make the textview text horizontally center, select the textview from .xib class and go to the library and in that set Alignment as center.
Enjoy. :)
Very good solution! This is a Swift + Interface Builder solution so that you can enable it in IB.
I'll put it as part of the LSwift library.
extension UITextView {
#IBInspectable var align_middle_vertical: Bool {
get {
return false // TODO
}
set (f) {
self.addObserver(self, forKeyPath:"contentSize", options:.New, context:nil)
}
}
override public func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject:AnyObject], context: UnsafeMutablePointer<Void>) {
if let textView = object as? UITextView {
var y: CGFloat = (textView.bounds.size.height - textView.contentSize.height * textView.zoomScale)/2.0;
if y < 0 {
y = 0
}
textView.content_y = -y
}
}
}
public extension UIScrollView {
public var content_x: CGFloat {
set(f) {
contentOffset.x = f
}
get {
return contentOffset.x
}
}
public var content_y: CGFloat {
set(f) {
contentOffset.y = f
}
get {
return contentOffset.y
}
}
}

Scroll UITextView To Bottom

I am making a an app that has a UITextView and a button.
When I click the button some text will add in the UITextView.
But when clicking the button, I wan't to scroll down to the bottom of the text field so the user can see the last text added.
How to make the UITextView to scroll down to the bottom?
I tried:
int numLines = LogTextView.contentSize.height / LogTextView.font.lineHeight+1;
NSLog(#"%d",numLines);
NSUInteger length = self.LogTextView.text.length;
self.LogTextView.selectedRange = NSMakeRange(0, length);
but it will not work...
I also tried:
self.LogTextView.contentSize=CGSizeMake(length,0);
You can use the following code if you are talking about UITextView:
-(void)scrollTextViewToBottom:(UITextView *)textView {
if(textView.text.length > 0 ) {
NSRange bottom = NSMakeRange(textView.text.length -1, 1);
[textView scrollRangeToVisible:bottom];
}
}
SWIFT 4:
func scrollTextViewToBottom(textView: UITextView) {
if textView.text.count > 0 {
let location = textView.text.count - 1
let bottom = NSMakeRange(location, 1)
textView.scrollRangeToVisible(bottom)
}
}
Try this if you have problem on iOS 7 or above. See this SO answer.
- (void)scrollTextViewToBottom:(UITextView *)textView {
NSRange range = NSMakeRange(textView.text.length, 0);
[textView scrollRangeToVisible:range];
// an iOS bug, see https://stackoverflow.com/a/20989956/971070
[textView setScrollEnabled:NO];
[textView setScrollEnabled:YES];
}
With Swift 3
let bottom = self.textView.contentSize.height - self.textView.bounds.size.height
self.textView.setContentOffset(CGPoint(x: 0, y: bottom), animated: true)
Swift 5
extension UITextView {
func simple_scrollToBottom() {
let textCount: Int = text.count
guard textCount >= 1 else { return }
scrollRangeToVisible(NSRange(location: textCount - 1, length: 1))
}
}
// Usage
textView.simple_scrollToBottom()
Make a range, specifying encoding, to the last character, then scroll to that range
Something other than utf8 might be appropriate depending on your content
let range = NSMakeRange(self.textView.text.lengthOfBytes(using: .utf8), 0);
self.textView.scrollRangeToVisible(range);
You have to implement a delegate method. The code below checks whether a newline has been entered and, if so, scrolls to the bottom of the textView:
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
if ([text isEqualToString:#"\n"]) {
textView.contentOffset = CGPointMake(0.0, textView.contentSize.height);
}
return YES;
}
This works for me! :D
CGPoint bottomOffset = CGPointMake(0, self.textView.contentSize.height - self.textView.bounds.size.height);
[self.description1 setContentOffset:bottomOffset animated:YES];
As a generic approach for scrolling to bottom, it can be done on a UIScrollView.
extension UIScrollView {
func scrollToBottom() {
let contentHeight = contentSize.height - frame.size.height
let contentoffsetY = max(contentHeight, 0)
setContentOffset(CGPoint(x: 0, y: contentoffsetY), animated: true)
}
}
This will work on all descendants of UIScrollView like UITextView, UITableView etc..
textView.scrollRangeToVisible(NSRange(..<textView.text.endIndex, in: textView.text))
This solution does a couple of notable things slightly different:
Utilizes the String.Index interface (likely more performant than e.g. .count)
Uses a PartialRangeUpTo which avoids an explicit range start position, reducing the code to a clean one-liner
The Swift version of #Hong Duan answer
func scrollTextViewToBottom(textView: UITextView) {
if textView.text.count > 0 {
let location = textView.text.count - 1
let bottom = NSMakeRange(location, 1)
textView.scrollRangeToVisible(bottom)
// an iOS bug, see https://stackoverflow.com/a/20989956/971070
textView.isScrollEnabled = false
textView.isScrollEnabled = true
}
}

Placeholder in UITextView

My application uses an UITextView. Now I want the UITextView to have a placeholder similar to the one you can set for an UITextField.
How to do this?
I made a few minor modifications to bcd's solution to allow for initialization from a Xib file, text wrapping, and to maintain background color. Hopefully it will save others the trouble.
UIPlaceHolderTextView.h:
#import <Foundation/Foundation.h>
IB_DESIGNABLE
#interface UIPlaceHolderTextView : UITextView
#property (nonatomic, retain) IBInspectable NSString *placeholder;
#property (nonatomic, retain) IBInspectable UIColor *placeholderColor;
-(void)textChanged:(NSNotification*)notification;
#end
UIPlaceHolderTextView.m:
#import "UIPlaceHolderTextView.h"
#interface UIPlaceHolderTextView ()
#property (nonatomic, retain) UILabel *placeHolderLabel;
#end
#implementation UIPlaceHolderTextView
CGFloat const UI_PLACEHOLDER_TEXT_CHANGED_ANIMATION_DURATION = 0.25;
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
#if __has_feature(objc_arc)
#else
[_placeHolderLabel release]; _placeHolderLabel = nil;
[_placeholderColor release]; _placeholderColor = nil;
[_placeholder release]; _placeholder = nil;
[super dealloc];
#endif
}
- (void)awakeFromNib
{
[super awakeFromNib];
    // Use Interface Builder User Defined Runtime Attributes to set
    // placeholder and placeholderColor in Interface Builder.
if (!self.placeholder) {
[self setPlaceholder:#""];
}
if (!self.placeholderColor) {
[self setPlaceholderColor:[UIColor lightGrayColor]];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil];
}
- (id)initWithFrame:(CGRect)frame
{
if( (self = [super initWithFrame:frame]) )
{
[self setPlaceholder:#""];
[self setPlaceholderColor:[UIColor lightGrayColor]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil];
}
return self;
}
- (void)textChanged:(NSNotification *)notification
{
if([[self placeholder] length] == 0)
{
return;
}
[UIView animateWithDuration:UI_PLACEHOLDER_TEXT_CHANGED_ANIMATION_DURATION animations:^{
if([[self text] length] == 0)
{
[[self viewWithTag:999] setAlpha:1];
}
else
{
[[self viewWithTag:999] setAlpha:0];
}
}];
}
- (void)setText:(NSString *)text {
[super setText:text];
[self textChanged:nil];
}
- (void)drawRect:(CGRect)rect
{
if( [[self placeholder] length] > 0 )
{
if (_placeHolderLabel == nil )
{
_placeHolderLabel = [[UILabel alloc] initWithFrame:CGRectMake(8,8,self.bounds.size.width - 16,0)];
_placeHolderLabel.lineBreakMode = NSLineBreakByWordWrapping;
_placeHolderLabel.numberOfLines = 0;
_placeHolderLabel.font = self.font;
_placeHolderLabel.backgroundColor = [UIColor clearColor];
_placeHolderLabel.textColor = self.placeholderColor;
_placeHolderLabel.alpha = 0;
_placeHolderLabel.tag = 999;
[self addSubview:_placeHolderLabel];
}
_placeHolderLabel.text = self.placeholder;
[_placeHolderLabel sizeToFit];
[self sendSubviewToBack:_placeHolderLabel];
}
if( [[self text] length] == 0 && [[self placeholder] length] > 0 )
{
[[self viewWithTag:999] setAlpha:1];
}
[super drawRect:rect];
}
#end
Easy way, just create placeholder text in UITextView by using the following UITextViewDelegate methods:
- (void)textViewDidBeginEditing:(UITextView *)textView
{
if ([textView.text isEqualToString:#"placeholder text here..."]) {
textView.text = #"";
textView.textColor = [UIColor blackColor]; //optional
}
[textView becomeFirstResponder];
}
- (void)textViewDidEndEditing:(UITextView *)textView
{
if ([textView.text isEqualToString:#""]) {
textView.text = #"placeholder text here...";
textView.textColor = [UIColor lightGrayColor]; //optional
}
[textView resignFirstResponder];
}
just remember to set myUITextView with the exact text on creation e.g.
UITextView *myUITextView = [[UITextView alloc] init];
myUITextView.delegate = self;
myUITextView.text = #"placeholder text here...";
myUITextView.textColor = [UIColor lightGrayColor]; //optional
and make the parent class a UITextViewDelegate before including these methods e.g.
#interface MyClass () <UITextViewDelegate>
#end
Code for Swift 3.1
func textViewDidBeginEditing(_ textView: UITextView)
{
if (textView.text == "placeholder text here..." && textView.textColor == .lightGray)
{
textView.text = ""
textView.textColor = .black
}
textView.becomeFirstResponder() //Optional
}
func textViewDidEndEditing(_ textView: UITextView)
{
if (textView.text == "")
{
textView.text = "placeholder text here..."
textView.textColor = .lightGray
}
textView.resignFirstResponder()
}
just remember to set myUITextView with the exact text on creation e.g.
let myUITextView = UITextView.init()
myUITextView.delegate = self
myUITextView.text = "placeholder text here..."
myUITextView.textColor = .lightGray
and make the parent class a UITextViewDelegate before including these methods e.g.
class MyClass: UITextViewDelegate
{
}
I wasn't too happy with any of the solutions posted as they were a bit heavy. Adding views to the view isn't really ideal (especially in drawRect:). They both had leaks, which isn't acceptable either.
Here is my solution: SAMTextView
SAMTextView.h
//
// SAMTextView.h
// SAMTextView
//
// Created by Sam Soffes on 8/18/10.
// Copyright 2010-2013 Sam Soffes. All rights reserved.
//
#import <UIKit/UIKit.h>
/**
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.
The default value is `nil`.
*/
#property (nonatomic, strong) NSString *placeholder;
/**
The color of the placeholder.
The default is `[UIColor lightGrayColor]`.
*/
#property (nonatomic, strong) UIColor *placeholderTextColor;
/**
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
SAMTextView.m
//
// SAMTextView.m
// SAMTextView
//
// Created by Sam Soffes on 8/18/10.
// Copyright 2010-2013 Sam Soffes. All rights reserved.
//
#import "SAMTextView.h"
#implementation SAMTextView
#pragma mark - Accessors
#synthesize placeholder = _placeholder;
#synthesize placeholderTextColor = _placeholderTextColor;
- (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 isEqual:_placeholder]) {
return;
}
_placeholder = string;
[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];
if (self.text.length == 0 && self.placeholder) {
rect = [self placeholderRectForBounds:self.bounds];
UIFont *font = self.font ? self.font : self.typingAttributes[NSFontAttributeName];
// Draw the text
[self.placeholderTextColor set];
[self.placeholder drawInRect:rect withFont:font lineBreakMode:NSLineBreakByTruncatingTail alignment:self.textAlignment];
}
}
#pragma mark - Placeholder
- (CGRect)placeholderRectForBounds:(CGRect)bounds {
// Inset the rect
CGRect rect = UIEdgeInsetsInsetRect(bounds, self.contentInset);
if (self.typingAttributes) {
NSParagraphStyle *style = self.typingAttributes[NSParagraphStyleAttributeName];
if (style) {
rect.origin.x += style.headIndent;
rect.origin.y += style.firstLineHeadIndent;
}
}
return rect;
}
#pragma mark - Private
- (void)initialize {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textChanged:) name:UITextViewTextDidChangeNotification object:self];
self.placeholderTextColor = [UIColor colorWithWhite:0.702f alpha:1.0f];
}
- (void)textChanged:(NSNotification *)notification {
[self setNeedsDisplay];
}
#end
It's a lot simpler than the others, as it doesn't use subviews (or have leaks). Feel free to use it.
Update 11/10/11: It is now documented and supports use in Interface Builder.
Update 11/24/13: Point to new repo.
I found myself a very easy way to imitate a place-holder
in the NIB or code set your textView's textColor to lightGrayColor (most of the time)
make sure that your textView's delegate is linked to file's owner and implement UITextViewDelegate in your header file
set the default text of your text view to (example: "Foobar placeholder")
implement: (BOOL) textViewShouldBeginEditing:(UITextView *)textView
Edit:
Changed if statements to compare tags rather than text. If the user deleted their text it was possible to also accidentally delete a portion of the place holder #"Foobar placeholder".This meant if the user re-entered the textView the following delegate method, -(BOOL) textViewShouldBeginEditing:(UITextView *) textView, it would not work as expected. I tried comparing by the color of the text in the if statement but found that light grey color set in interface builder is not the same as light grey color set in code with [UIColor lightGreyColor]
- (BOOL) textViewShouldBeginEditing:(UITextView *)textView
{
if(textView.tag == 0) {
textView.text = #"";
textView.textColor = [UIColor blackColor];
textView.tag = 1;
}
return YES;
}
It is also possible to reset the placeholder text when the keyboard returns and the [textView length] == 0
EDIT:
Just to make the last part clearer - here's is how you can set the placeholder text back:
- (void)textViewDidChange:(UITextView *)textView
{
if([textView.text length] == 0)
{
textView.text = #"Foobar placeholder";
textView.textColor = [UIColor lightGrayColor];
textView.tag = 0;
}
}
What you can do is set up the text view with some initial value in the text property, and change the textColor to [UIColor grayColor] or something similar. Then, whenever the text view becomes editable, clear the text and present a cursor, and if the text field is ever empty again, put your placeholder text back. Change the color to [UIColor blackColor] as appropriate.
It's not exactly the same as the placeholder functionality in a UITextField, but it's close.
You can set the label on the UITextView by
[UITextView addSubView:lblPlaceHoldaer];
and hide it on TextViewdidChange method.
This is the simple & easy way.
Simple Swift 3 solution
Add UITextViewDelegate to your class
Set yourTextView.delegate = self
Create placeholderLabel and position it inside yourTextView
Now just animate placeholderLabel.alpha on textViewDidChange:
func textViewDidChange(_ textView: UITextView) {
let newAlpha: CGFloat = textView.text.isEmpty ? 1 : 0
if placeholderLabel.alpha != newAlpha {
UIView.animate(withDuration: 0.3) {
self.placeholderLabel.alpha = newAlpha
}
}
}
you might have to play with placeholderLabel position to set it up right, but that shouldn't be too hard
If someone needs a Solution for Swift:
Add UITextViewDelegate to your class
var placeHolderText = "Placeholder Text..."
override func viewDidLoad() {
super.viewDidLoad()
textView.delegate = self
}
func textViewShouldBeginEditing(textView: UITextView) -> Bool {
self.textView.textColor = .black
if(self.textView.text == placeHolderText) {
self.textView.text = ""
}
return true
}
func textViewDidEndEditing(textView: UITextView) {
if(textView.text == "") {
self.textView.text = placeHolderText
self.textView.textColor = .lightGray
}
}
override func viewWillAppear(animated: Bool) {
if(currentQuestion.answerDisplayValue == "") {
self.textView.text = placeHolderText
self.textView.textColor = .lightGray
} else {
self.textView.text = "xxx" // load default text / or stored
self.textView.textColor = .black
}
}
I extended KmKndy's answer, so that the placeholder remains visible until the user starts editing the UITextView rather than just taps on it. This mirrors the functionality in the Twitter and Facebook apps. My solution doesn't require you to subclass and works if the user types directly or pastes text!
- (void)textViewDidChangeSelection:(UITextView *)textView{
if ([textView.text isEqualToString:#"What's happening?"] && [textView.textColor isEqual:[UIColor lightGrayColor]])[textView setSelectedRange:NSMakeRange(0, 0)];
}
- (void)textViewDidBeginEditing:(UITextView *)textView{
[textView setSelectedRange:NSMakeRange(0, 0)];
}
- (void)textViewDidChange:(UITextView *)textView
{
if (textView.text.length != 0 && [[textView.text substringFromIndex:1] isEqualToString:#"What's happening?"] && [textView.textColor isEqual:[UIColor lightGrayColor]]){
textView.text = [textView.text substringToIndex:1];
textView.textColor = [UIColor blackColor]; //optional
}
else if(textView.text.length == 0){
textView.text = #"What's happening?";
textView.textColor = [UIColor lightGrayColor];
[textView setSelectedRange:NSMakeRange(0, 0)];
}
}
- (void)textViewDidEndEditing:(UITextView *)textView
{
if ([textView.text isEqualToString:#""]) {
textView.text = #"What's happening?";
textView.textColor = [UIColor lightGrayColor]; //optional
}
[textView resignFirstResponder];
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text{
if (textView.text.length > 1 && [textView.text isEqualToString:#"What's happening?"]) {
textView.text = #"";
textView.textColor = [UIColor blackColor];
}
return YES;
}
just remember to set myUITextView with the exact text on creation e.g.
UITextView *myUITextView = [[UITextView alloc] init];
myUITextView.delegate = self;
myUITextView.text = #"What's happening?";
myUITextView.textColor = [UIColor lightGrayColor]; //optional
and make the parent class a UITextView delegate before including these methods e.g.
#interface MyClass () <UITextViewDelegate>
#end
Below is a Swift port of "SAMTextView" ObjC code posted as one of the first handful of replies to the question. I tested it on iOS 8. I tweaked a couple of things, including the bounds offset for the placement of the placeholder text, as the original was too high and too far right (used suggestion in one of the comments to that post).
I know there are a lot of simple solutions, but I like the approach of subclassing UITextView because it's reusable and I don't have to clutter classes utilizing it with the mechanisms.
Swift 2.2:
import UIKit
class PlaceholderTextView: UITextView {
#IBInspectable var placeholderColor: UIColor = UIColor.lightGrayColor()
#IBInspectable var placeholderText: String = ""
override var font: UIFont? {
didSet {
setNeedsDisplay()
}
}
override var contentInset: UIEdgeInsets {
didSet {
setNeedsDisplay()
}
}
override var textAlignment: NSTextAlignment {
didSet {
setNeedsDisplay()
}
}
override var text: String? {
didSet {
setNeedsDisplay()
}
}
override var attributedText: NSAttributedString? {
didSet {
setNeedsDisplay()
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUp()
}
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
}
private func setUp() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(PlaceholderTextView.textChanged(_:)),
name: UITextViewTextDidChangeNotification, object: self)
}
func textChanged(notification: NSNotification) {
setNeedsDisplay()
}
func placeholderRectForBounds(bounds: CGRect) -> CGRect {
var x = contentInset.left + 4.0
var y = contentInset.top + 9.0
let w = frame.size.width - contentInset.left - contentInset.right - 16.0
let h = frame.size.height - contentInset.top - contentInset.bottom - 16.0
if let style = self.typingAttributes[NSParagraphStyleAttributeName] as? NSParagraphStyle {
x += style.headIndent
y += style.firstLineHeadIndent
}
return CGRect(x: x, y: y, width: w, height: h)
}
override func drawRect(rect: CGRect) {
if text!.isEmpty && !placeholderText.isEmpty {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = textAlignment
let attributes: [ String: AnyObject ] = [
NSFontAttributeName : font!,
NSForegroundColorAttributeName : placeholderColor,
NSParagraphStyleAttributeName : paragraphStyle]
placeholderText.drawInRect(placeholderRectForBounds(bounds), withAttributes: attributes)
}
super.drawRect(rect)
}
}
Swift 4.2:
import UIKit
class PlaceholderTextView: UITextView {
#IBInspectable var placeholderColor: UIColor = UIColor.lightGray
#IBInspectable var placeholderText: String = ""
override var font: UIFont? {
didSet {
setNeedsDisplay()
}
}
override var contentInset: UIEdgeInsets {
didSet {
setNeedsDisplay()
}
}
override var textAlignment: NSTextAlignment {
didSet {
setNeedsDisplay()
}
}
override var text: String? {
didSet {
setNeedsDisplay()
}
}
override var attributedText: NSAttributedString? {
didSet {
setNeedsDisplay()
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUp()
}
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
}
private func setUp() {
NotificationCenter.default.addObserver(self,
selector: #selector(self.textChanged(notification:)),
name: Notification.Name("UITextViewTextDidChangeNotification"),
object: nil)
}
#objc func textChanged(notification: NSNotification) {
setNeedsDisplay()
}
func placeholderRectForBounds(bounds: CGRect) -> CGRect {
var x = contentInset.left + 4.0
var y = contentInset.top + 9.0
let w = frame.size.width - contentInset.left - contentInset.right - 16.0
let h = frame.size.height - contentInset.top - contentInset.bottom - 16.0
if let style = self.typingAttributes[NSAttributedString.Key.paragraphStyle] as? NSParagraphStyle {
x += style.headIndent
y += style.firstLineHeadIndent
}
return CGRect(x: x, y: y, width: w, height: h)
}
override func draw(_ rect: CGRect) {
if text!.isEmpty && !placeholderText.isEmpty {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = textAlignment
let attributes: [NSAttributedString.Key: Any] = [
NSAttributedString.Key(rawValue: NSAttributedString.Key.font.rawValue) : font!,
NSAttributedString.Key(rawValue: NSAttributedString.Key.foregroundColor.rawValue) : placeholderColor,
NSAttributedString.Key(rawValue: NSAttributedString.Key.paragraphStyle.rawValue) : paragraphStyle]
placeholderText.draw(in: placeholderRectForBounds(bounds: bounds), withAttributes: attributes)
}
super.draw(rect)
}
}
I recommend to use SZTextView.
https://github.com/glaszig/SZTextView
Add your default UITextView from storyboard and then change its custom class to SZTextView like below
Then you will see two new option in the Attribute Inspector
Hi you can use IQTextView available in IQKeyboard Manager it's simple to use and integrate just set class of your textview to IQTextView and you can use its property for setting placeholder label with color you want.
You can download the library from IQKeyboardManager
or you can install it from cocoapods.
this is how I did it:
UITextView2.h
#import <UIKit/UIKit.h>
#interface UITextView2 : UITextView <UITextViewDelegate> {
NSString *placeholder;
UIColor *placeholderColor;
}
#property(nonatomic, retain) NSString *placeholder;
#property(nonatomic, retain) UIColor *placeholderColor;
-(void)textChanged:(NSNotification*)notif;
#end
UITextView2.m
#implementation UITextView2
#synthesize placeholder, placeholderColor;
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self setPlaceholder:#""];
[self setPlaceholderColor:[UIColor lightGrayColor]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil];
}
return self;
}
-(void)textChanged:(NSNotification*)notif {
if ([[self placeholder] length]==0)
return;
if ([[self text] length]==0) {
[[self viewWithTag:999] setAlpha:1];
} else {
[[self viewWithTag:999] setAlpha:0];
}
}
- (void)drawRect:(CGRect)rect {
if ([[self placeholder] length]>0) {
UILabel *l = [[UILabel alloc] initWithFrame:CGRectMake(8, 8, 0, 0)];
[l setFont:self.font];
[l setTextColor:self.placeholderColor];
[l setText:self.placeholder];
[l setAlpha:0];
[l setTag:999];
[self addSubview:l];
[l sizeToFit];
[self sendSubviewToBack:l];
[l release];
}
if ([[self text] length]==0 && [[self placeholder] length]>0) {
[[self viewWithTag:999] setAlpha:1];
}
[super drawRect:rect];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
#end
Here's a way easier solution that behaves exactly like UITextField's placeholder but doesn't require drawing custom views, or resigning first responder.
- (void) textViewDidChange:(UITextView *)textView{
if (textView.text.length == 0){
textView.textColor = [UIColor lightGrayColor];
textView.text = placeholderText;
[textView setSelectedRange:NSMakeRange(0, 0)];
isPlaceholder = YES;
} else if (isPlaceholder && ![textView.text isEqualToString:placeholderText]) {
textView.text = [textView.text substringToIndex:1];
textView.textColor = [UIColor blackColor];
isPlaceholder = NO;
}
}
(the second check in the else if statement is for the case where nothing is entered and the user presses backspace)
Just set your class as a UITextViewDelegate. In viewDidLoad you should initialize like
- (void) viewDidLoad{
// initialize placeholder text
placeholderText = #"some placeholder";
isPlaceholder = YES;
self.someTextView.text = placeholderText;
self.someTextView.textColor = [UIColor lightGrayColor];
[self.someTextView setSelectedRange:NSMakeRange(0, 0)];
// assign UITextViewDelegate
self.someTextView.delegate = self;
}
Sorry to add another answer, But I just pulled something like this off and this created the closest-to-UITextField kind of placeholder.
Hope this helps someone.
-(void)textViewDidChange:(UITextView *)textView{
if(textView.textColor == [UIColor lightGrayColor]){
textView.textColor = [UIColor blackColor]; // look at the comment section in this answer
textView.text = [textView.text substringToIndex: 0];// look at the comment section in this answer
}else if(textView.text.length == 0){
textView.text = #"This is some placeholder text.";
textView.textColor = [UIColor lightGrayColor];
textView.selectedRange = NSMakeRange(0, 0);
}
}
-(void)textViewDidChangeSelection:(UITextView *)textView{
if(textView.textColor == [UIColor lightGrayColor] && (textView.selectedRange.location != 0 || textView.selectedRange.length != 0)){
textView.selectedRange = NSMakeRange(0, 0);
}
}
Simple way to use this within some line of code:
Take one label up to UITextView in .nib
connecting this label to your code ,
After it.
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text{
if (range.location>0 || text.length!=0) {
placeholderLabel1.hidden = YES;
}else{
placeholderLabel1.hidden = NO;
}
return YES;
}
I've modified Sam Soffes' implementation to work with iOS7:
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
if (_shouldDrawPlaceholder)
{
UIEdgeInsets insets = self.textContainerInset;
CGRect placeholderRect = CGRectMake(
insets.left + self.textContainer.lineFragmentPadding,
insets.top,
self.frame.size.width - insets.left - insets.right,
self.frame.size.height - insets.top - insets.bottom);
[_placeholderText drawWithRect:placeholderRect
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine
attributes:self.placeholderAttributes
context:nil];
}
}
- (NSDictionary *)placeholderAttributes
{
if (_placeholderAttributes == nil)
{
_placeholderAttributes = #
{
NSFontAttributeName : self.font,
NSForegroundColorAttributeName : self.placeholderColor
};
}
return _placeholderAttributes;
}
Remember to set _placeholderAttribues = nil in methods that might change the font and other thigns that might affect them. You might also want to skip "lazy" making of the attributes dictionary if that doesn't bug you.
EDIT:
Remember to call setNeedsDisplay in a overridden version of setBounds if you like the placeholder to look good after autolayout animations and the like.
This mimics UITextField's placeholder perfectly, where the place holder text stays until you actually type something.
private let placeholder = "Type here"
#IBOutlet weak var textView: UITextView! {
didSet {
textView.textColor = UIColor.lightGray
textView.text = placeholder
textView.selectedRange = NSRange(location: 0, length: 0)
}
}
extension ViewController: UITextViewDelegate {
func textViewDidChangeSelection(_ textView: UITextView) {
// Move cursor to beginning on first tap
if textView.text == placeholder {
textView.selectedRange = NSRange(location: 0, length: 0)
}
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if textView.text == placeholder && !text.isEmpty {
textView.text = nil
textView.textColor = UIColor.black
textView.selectedRange = NSRange(location: 0, length: 0)
}
return true
}
func textViewDidChange(_ textView: UITextView) {
if textView.text.isEmpty {
textView.textColor = UIColor.lightGray
textView.text = placeholder
}
}
}
You could also create a new class TextViewWithPlaceholder as a subclass of UITextView.
(This code is kind of rough -- but I think it's on the right track.)
#interface TextViewWithPlaceholder : UITextView
{
NSString *placeholderText; // make a property
UIColor *placeholderColor; // make a property
UIColor *normalTextColor; // cache text color here whenever you switch to the placeholderColor
}
- (void) setTextColor: (UIColor*) color
{
normalTextColor = color;
[super setTextColor: color];
}
- (void) updateForTextChange
{
if ([self.text length] == 0)
{
normalTextColor = self.textColor;
self.textColor = placeholderColor;
self.text = placeholderText;
}
else
{
self.textColor = normalTextColor;
}
}
In your delegate, add this:
- (void)textViewDidChange:(UITextView *)textView
{
if ([textView respondsToSelector: #selector(updateForTextChange)])
{
[textView updateForTextChange];
}
}
I made my own version of the subclass of 'UITextView'. I liked Sam Soffes's idea of using the notifications, but I didn't liked the drawRect: overwrite. Seems overkill to me. I think I made a very clean implementation.
You can look at my subclass here. A demo project is also included.
This thread has had plenty of answers, but here's the version I prefer.
It extends the existing UITextView class so is easily reuseable, and it doesn't intercept the events like textViewDidChange (which might break user's code, if they were already intercepting these events elsewhere).
Using my code (shown below), you can easily add a placeholder to any of your UITextViews like this:
self.textViewComments.placeholder = #"(Enter some comments here.)";
When you set this new placeholder value, it quietly adds a UILabel on top of your UITextView, then hide/shows it as necessary:
Okay, to make these changes, add a "UITextViewHelper.h" file containing this code:
// UITextViewHelper.h
// Created by Michael Gledhill on 13/02/15.
#import <Foundation/Foundation.h>
#interface UITextView (UITextViewHelper)
#property (nonatomic, strong) NSString* placeholder;
#property (nonatomic, strong) UILabel* placeholderLabel;
#property (nonatomic, strong) NSString* textValue;
-(void)checkIfNeedToDisplayPlaceholder;
#end
...and a UITextViewHelper.m file containing this:
// UITextViewHelper.m
// Created by Michael Gledhill on 13/02/15.
//
// This UITextView category allows us to easily display a PlaceHolder string in our UITextView.
// The downside is that, your code needs to set the "textValue" rather than the "text" value to safely set the UITextView's text.
//
#import "UITextViewHelper.h"
#import <objc/runtime.h>
#implementation UITextView (UITextViewHelper)
#define UI_PLACEHOLDER_TEXT_COLOR [UIColor colorWithRed:170.0/255.0 green:170.0/255.0 blue:170.0/255.0 alpha:1.0]
#dynamic placeholder;
#dynamic placeholderLabel;
#dynamic textValue;
-(void)setTextValue:(NSString *)textValue
{
// Change the text of our UITextView, and check whether we need to display the placeholder.
self.text = textValue;
[self checkIfNeedToDisplayPlaceholder];
}
-(NSString*)textValue
{
return self.text;
}
-(void)checkIfNeedToDisplayPlaceholder
{
// If our UITextView is empty, display our Placeholder label (if we have one)
if (self.placeholderLabel == nil)
return;
self.placeholderLabel.hidden = (![self.text isEqualToString:#""]);
}
-(void)onTap
{
// When the user taps in our UITextView, we'll see if we need to remove the placeholder text.
[self checkIfNeedToDisplayPlaceholder];
// Make the onscreen keyboard appear.
[self becomeFirstResponder];
}
-(void)keyPressed:(NSNotification*)notification
{
// The user has just typed a character in our UITextView (or pressed the delete key).
// Do we need to display our Placeholder label ?
[self checkIfNeedToDisplayPlaceholder];
}
#pragma mark - Add a "placeHolder" string to the UITextView class
NSString const *kKeyPlaceHolder = #"kKeyPlaceHolder";
-(void)setPlaceholder:(NSString *)_placeholder
{
// Sets our "placeholder" text string, creates a new UILabel to contain it, and modifies our UITextView to cope with
// showing/hiding the UILabel when needed.
objc_setAssociatedObject(self, &kKeyPlaceHolder, (id)_placeholder, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
self.placeholderLabel = [[UILabel alloc] initWithFrame:self.frame];
self.placeholderLabel.numberOfLines = 1;
self.placeholderLabel.text = _placeholder;
self.placeholderLabel.textColor = UI_PLACEHOLDER_TEXT_COLOR;
self.placeholderLabel.backgroundColor = [UIColor clearColor];
self.placeholderLabel.userInteractionEnabled = true;
self.placeholderLabel.font = self.font;
[self addSubview:self.placeholderLabel];
[self.placeholderLabel sizeToFit];
// Whenever the user taps within the UITextView, we'll give the textview the focus, and hide the placeholder if necessary.
[self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(onTap)]];
// Whenever the user types something in the UITextView, we'll see if we need to hide/show the placeholder label.
[[NSNotificationCenter defaultCenter] addObserver:self selector: #selector(keyPressed:) name:UITextViewTextDidChangeNotification object:nil];
[self checkIfNeedToDisplayPlaceholder];
}
-(NSString*)placeholder
{
// Returns our "placeholder" text string
return objc_getAssociatedObject(self, &kKeyPlaceHolder);
}
#pragma mark - Add a "UILabel" to this UITextView class
NSString const *kKeyLabel = #"kKeyLabel";
-(void)setPlaceholderLabel:(UILabel *)placeholderLabel
{
// Stores our new UILabel (which contains our placeholder string)
objc_setAssociatedObject(self, &kKeyLabel, (id)placeholderLabel, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[[NSNotificationCenter defaultCenter] addObserver:self selector: #selector(keyPressed:) name:UITextViewTextDidChangeNotification object:nil];
[self checkIfNeedToDisplayPlaceholder];
}
-(UILabel*)placeholderLabel
{
// Returns our new UILabel
return objc_getAssociatedObject(self, &kKeyLabel);
}
#end
Yup, it's a lot of code, but once you've added it to your project and included the .h file...
#import "UITextViewHelper.h"
...you can easily use placeholders in UITextViews.
There's one gotcha though.
If you do this:
self.textViewComments.placeholder = #"(Enter some comments here.)";
self.textViewComments.text = #"Ooooh, hello there";
...the placeholder will appear on top of the text. When you set the text value, none of the regular notifications gets called, so I couldn't work out how to call my function to decide whether to show/hide the placeholder.
The solution is to set the textValue rather than text:
self.textViewComments.placeholder = #"(Enter some comments here.)";
self.textViewComments.textValue = #"Ooooh, hello there";
Alternatively, you can set the text value, then call checkIfNeedToDisplayPlaceholder.
self.textViewComments.text = #"Ooooh, hello there";
[self.textViewComments checkIfNeedToDisplayPlaceholder];
I like solutions like this, as they "fill the gap" between what Apple provides us with, and what we (as developers) actually need in our apps. You write this code once, add it to your library of "helper" .m/.h files, and, over time, the SDK actually starts becoming less frustrating.
(I wrote a similar helper for adding a "clear" button to my UITextViews, another thing which annoyingly exists in UITextField but not in UITextView...)
First take a label in .h file.
Here I take
UILabel * lbl;
Then in .m under viewDidLoad declare it
lbl = [[UILabel alloc] initWithFrame:CGRectMake(8.0, 0.0,250, 34.0)];
lbl.font=[UIFont systemFontOfSize:14.0];
[lbl setText:#"Write a message..."];
[lbl setBackgroundColor:[UIColor clearColor]];
[lbl setTextColor:[UIColor lightGrayColor]];
[textview addSubview:lbl];
textview is my TextView.
Now declare
-(void)textViewDidChange:(UITextView *)textView {
if (![textView hasText]){
lbl.hidden = NO;
}
else{
lbl.hidden = YES;
}
}
And your Textview placeholder is ready !
I recommend use pod 'UITextView+Placeholder'
pod 'UITextView+Placeholder'
on your code
#import "UITextView+Placeholder.h"
////
UITextView *textView = [[UITextView alloc] init];
textView.placeholder = #"How are you?";
textView.placeholderColor = [UIColor lightGrayColor];
Based on some of the great suggestions here already, I was able to put together the following lightweight, Interface-Builder-compatible subclass of UITextView, which:
Includes configurable placeholder text, styled just like that of UITextField.
Doesn't require any additional subviews or constraints.
Doesn't require any delegation or other behaviour from the ViewController.
Doesn't require any notifications.
Keeps that text fully separated from any outside classes looking at the field's text property.
Improvement suggestions are welcome.
Edit 1: Updated to reset placeholder formatting if actual text is set programmatically.
Edit 2: The placeholder text color can now be retrieved programmatically.
Swift v5:
import UIKit
#IBDesignable class TextViewWithPlaceholder: UITextView {
override var text: String! { // Ensures that the placeholder text is never returned as the field's text
get {
if showingPlaceholder {
return "" // When showing the placeholder, there's no real text to return
} else { return super.text }
}
set {
if showingPlaceholder {
removePlaceholderFormatting() // If the placeholder text is what's being changed, it's no longer the placeholder
}
super.text = newValue
}
}
#IBInspectable var placeholderText: String = ""
#IBInspectable var placeholderTextColor: UIColor = .placeholderText
private var showingPlaceholder: Bool = true // Keeps track of whether the field is currently showing a placeholder
override func didMoveToWindow() {
super.didMoveToWindow()
if text.isEmpty {
showPlaceholderText() // Load up the placeholder text when first appearing, but not if coming back to a view where text was already entered
}
}
override public func becomeFirstResponder() -> Bool {
// If the current text is the placeholder, remove it
if showingPlaceholder {
text = nil
removePlaceholderFormatting()
}
return super.becomeFirstResponder()
}
override public func resignFirstResponder() -> Bool {
// If there's no text, put the placeholder back
if text.isEmpty {
showPlaceholderText()
}
return super.resignFirstResponder()
}
private func showPlaceholderText() {
text = placeholderText
showingPlaceholder = true
textColor = placeholderTextColor
}
private func removePlaceholderFormatting() {
showingPlaceholder = false
textColor = nil // Put the text back to the default, unmodified color
}
}
- (void)textViewDidChange:(UITextView *)textView
{
placeholderLabel.hidden = YES;
}
put a label over the textview.
It is not possible to create placeholder in UITextView but you can generate effect like place holder by this.
- (void)viewDidLoad{
commentTxtView.text = #"Comment";
commentTxtView.textColor = [UIColor lightGrayColor];
commentTxtView.delegate = self;
}
- (BOOL) textViewShouldBeginEditing:(UITextView *)textView
{
commentTxtView.text = #"";
commentTxtView.textColor = [UIColor blackColor];
return YES;
}
-(void) textViewDidChange:(UITextView *)textView
{
if(commentTxtView.text.length == 0){
commentTxtView.textColor = [UIColor lightGrayColor];
commentTxtView.text = #"Comment";
[commentTxtView resignFirstResponder];
}
}
OR you can add label in textview just like
lbl = [[UILabel alloc] initWithFrame:CGRectMake(10.0, 0.0,textView.frame.size.width - 10.0, 34.0)];
[lbl setText:kDescriptionPlaceholder];
[lbl setBackgroundColor:[UIColor clearColor]];
[lbl setTextColor:[UIColor lightGrayColor]];
textView.delegate = self;
[textView addSubview:lbl];
and set
- (void)textViewDidEndEditing:(UITextView *)theTextView
{
if (![textView hasText]) {
lbl.hidden = NO;
}
}
- (void) textViewDidChange:(UITextView *)textView
{
if(![textView hasText]) {
lbl.hidden = NO;
}
else{
lbl.hidden = YES;
}
}
Here's yet another way to do it, one that reproduces the slight indentation of UITextField's placeholder:
Drag a UITextField right under the UITextView so that their top left corners are aligned. Add your placeholder text to the text field.
In viewDidLoad, add:
[tView setDelegate:self];
tView.contentInset = UIEdgeInsetsMake(-8,-8,0,0);
tView.backgroundColor = [UIColor clearColor];
Then add:
- (void)textViewDidChange:(UITextView *)textView {
if (textView.text.length == 0) {
textView.backgroundColor = [UIColor clearColor];
} else {
textView.backgroundColor = [UIColor whiteColor];
}
}
Lets make it easy
Create one UILabel and place it on your text view(Give the text as Placeholder-set color gray-you can do all this in your xib)
Now in you header file declare the UILabel and also the the textviewDelegate
Now you can simply hide the label when you click on the textview
complete code below
header
#interface ViewController :UIViewController<UITextViewDelegate>{
}
#property (nonatomic,strong) IBOutlet UILabel *PlceHolder_label;
#property (nonatomic,strong) IBOutlet UITextView *TextView;
#end
implementation
#implementation UploadFoodImageViewController
#synthesize PlceHolder_label,TextView;
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView{
if([textView isEqual:TextView]){
[PlceHolder_label setHidden:YES];
[self.tabScrlVw setContentOffset:CGPointMake(0,150) animated:YES];
}
return YES;
}
#end
Dont forget to connect the textView and UILabel to filesowner from xib
Take a look at UTPlaceholderTextView.
This is a convenient subclass of UITextView that supports placeholder similiar to that of UITextField. Main peculiarities:
Does not use subviews
Does not override drawRect:
Placeholder could be of arbitrary length, and rendered just the same way as usual text
I read through all of these, but came up with a very short, Swift 3, solution that has worked in all of my tests. It could stand a little more generality, but the process is simple. Here's the entire thing which I call "TextViewWithPlaceholder".
import UIKit
class TextViewWithPlaceholder: UITextView {
public var placeholder: String?
public var placeholderColor = UIColor.lightGray
private var placeholderLabel: UILabel?
// Set up notification listener when created from a XIB or storyboard.
// You can also set up init() functions if you plan on creating
// these programmatically.
override func awakeFromNib() {
super.awakeFromNib()
NotificationCenter.default.addObserver(self,
selector: #selector(TextViewWithPlaceholder.textDidChangeHandler(notification:)),
name: .UITextViewTextDidChange,
object: self)
placeholderLabel = UILabel()
placeholderLabel?.alpha = 0.85
placeholderLabel?.textColor = placeholderColor
}
// By using layoutSubviews, you can size and position the placeholder
// more accurately. I chose to hard-code the size of the placeholder
// but you can combine this with other techniques shown in previous replies.
override func layoutSubviews() {
super.layoutSubviews()
placeholderLabel?.textColor = placeholderColor
placeholderLabel?.text = placeholder
placeholderLabel?.frame = CGRect(x: 6, y: 4, width: self.bounds.size.width-16, height: 24)
if text.isEmpty {
addSubview(placeholderLabel!)
bringSubview(toFront: placeholderLabel!)
} else {
placeholderLabel?.removeFromSuperview()
}
}
// Whenever the text changes, just trigger a new layout pass.
func textDidChangeHandler(notification: Notification) {
layoutSubviews()
}
}

Resources