Placeholder in UITextView - ios

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()
}
}

Related

iOS 13 Custom UISearchBar _searchField crash

with the new iOS 13, i got a crash trying to change the UISearchBar textField properties using valueForKey:#"_searchField"
Now seems that Apple has changed something.
I've created a subclass of UIView with the following custom method and now it seems to work!
- (UIView *)findSubview:(NSString *)name resursion:(BOOL)resursion
{
Class class = NSClassFromString(name);
for (UIView *subview in self.subviews) {
if ([subview isKindOfClass:class]) {
return subview;
}
}
if (resursion) {
for (UIView *subview in self.subviews) {
UIView *tempView = [subview findSubview:name resursion:resursion];
if (tempView) {
return tempView;
}
}
}
return nil;
}
You can simply call this method this way to change UITextField properties:
UITextField *textField = (UITextField*)[self findSubview:#"UITextField" resursion:YES];
Obviously this is an Objective-c snippet and if anyone knows how to write the same code in swift can add it to the answers.
Happy coding!
I'm not sure if it would help, but UISearchBar has a new searchTextField property allowing you to access its UISearchTextField and, in turn, its UITextField:
let searchBar = UISearchBar()
var searchField : UITextField
if #available(iOS 13.0, *) {
searchField = searchBar.searchTextField
} else {
searchField = //Your original method
}
You can do it by using below extension
extension UISearchBar {
func getAllSubview<T : UIView>(type : T.Type) -> [T]{
var all = [T]()
func getSubview(view: UIView) {
if let aView = view as? T{
all.append(aView)
}
guard view.subviews.count>0 else { return }
view.subviews.forEach{ getSubview(view: $0) }
}
getSubview(view: self)
return all
}
}
Use like:
self.searchBar.getAllSubview(type: UITextField.self).first
Output:
<UISearchBarTextField: 0x7fc68d850a00; frame = (0 0; 0 0); text = ''; opaque = NO; layer = <CALayer: 0x600000d29aa0>>
My project is in Objective c and i need to support XCode10 as well so, After two days of headache below line saved my day :
txfSearchField = [_searchBar valueForKey:#"searchField"];
Just need to Remove _ from the old code!!!
In Swift also you can use the same.
Hope it will help someone!

Adding different color while editting text in UITextView irrespective of cursor position in Swift

I have a textview where I pre populate it with some required comments for eg: "Hi this is John" would show up in textview with blue color. Now what I need is, whatever new editing is done in textview it would come up in black. So, if I edit the text to "Hi this is Steve". Only "Steve" should be in black and rest should be in blue. I know I will have to use NSAttributedString, but how do I go about it?
Right now I do this on my view load
descriptionTextView.text = myString
descriptionTextView.textColor = UIColor.blue
And this on texteditting
func textViewDidBeginEditing(_ textView: UITextView) {
descriptionTextView.textColor = UIColor.black
}
But this will change everything to black. Any ideas?
You can achieve this by using UITextViewDelegate
Set delegate for UITextView and make a global variable of NSRange as:
IBOutlet UITextView *textVw;
NSRange textRange;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIColor *color = [UIColor blueColor]; // set initial needed color
NSString *string = #"Hi this is John"; // set initial string to color
NSDictionary *attrs = #{ NSForegroundColorAttributeName : color };
NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:string attributes:attrs];
textVw.attributedText = attrStr;
//Set delegate of UITextView
[textVw setDelegate:self];
}
#pragma UITextViewDelegate Methods.
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
//Check string is written or backspace (with prediction on).
if (text.length > 1) {
//Make Range for black text in textview.
textRange = NSMakeRange(range.location, text.length + 1);
return YES;
}
//Check string is written or backspace.
if (text.length > 0 && ![text isEqualToString:#" "]) {
//Make Range for black text in textview.
textRange = NSMakeRange(range.location, 1);
return YES;
}
return YES;
}
-(void)textViewDidChange:(UITextView *)textView {
//Check there are some value is written or not.
if (textRange.length > 0) {
//Check textview is rest with initial value or not.
if ([textVw.text caseInsensitiveCompare:#"Hi this is John"] == NSOrderedSame) {
//Rest value so make string as blue.
UIColor *color = [UIColor blueColor]; // set initial needed color
NSString *string = #"Hi this is John"; // set initial string to color
NSDictionary *attrs = #{ NSForegroundColorAttributeName : color };
NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:string attributes:attrs];
textVw.attributedText = attrStr;
} else {
NSMutableAttributedString *text =
[[NSMutableAttributedString alloc]
initWithAttributedString: textVw.attributedText];
[text addAttribute:NSForegroundColorAttributeName
value:[UIColor blackColor]
range:textRange];
//Add new Attributed value to textView with black color.
[textVw setAttributedText: text];
}
//After assign value to textfiled make textRange varibale as default value for safe handler.
textRange = NSMakeRange(0, 0);
}
}
- (void)textViewDidChangeSelection:(UITextView *)textView
{
//Check there are some value is written or not.
if (textRange.length > 1) {
//For maintain cursor position with prediction on.
[textView setSelectedRange:NSMakeRange(textRange.location + textRange.length , 0)];
} else if (textRange.length > 0) {
//For maintain cursor position.
[textView setSelectedRange:NSMakeRange(textRange.location + 1, 0)];
}
}
Try this :
class AViewController: UIViewController, UITextViewDelegate {
#IBOutlet var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
textView.delegate = self;
textView.text = ""
textView.textColor = UIColor.blue
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
let colorAttr = [NSForegroundColorAttributeName: UIColor.black, NSFontAttributeName: UIFont.systemFont(ofSize: 15)]
let attributedString = NSMutableAttributedString(string: text, attributes: colorAttr)
let combination = NSMutableAttributedString()
combination.append(textView.attributedText!)
combination.append(attributedString)
textView.attributedText = combination
return true
}
Basically shouldChangeTextIn is always called when new character is tapped. Using NSMutableAttributedString you can easily append old attributedString with black color with newly string.
PS : You neeed to conform to UITextViewDelegate protocol.

How to change UITableViewRowAction title color?

Am using UITableViewRowAction in "editActionsForRowAtIndexPath" method. I can change the backgroundcolor of UITableViewRowAction, but am not able to change the title color. But I would like to change the color of the UITableViewRowAction. Any inputs on this regard will be appreciable.
There is one way that can achieve what you are looking for. But it is little tricky though.
Example of result:
The idea of this trick is that you can actually modify background color. This means that you can set UIColor's +colorWithPatternImage: and set an bitmap image that match desired styling. This can be achieved either by creating image using graphic editor or by rendering it using for example Core Graphics. The only problem with this solution is, that you have to mimic original title length with you specific text attributes to make it work properly and also you must set title for table view row action as string of white spaces so that table view cell will prepare enough space for you "custom action button". Creating static png assets in photoshop may be inappropriate if you use variable cell rows.
This is category for NSString that creates string of empty spaces that will create space for your custom button and second will generate bitmap image that will be placed as background pattern image. For parameters you must set text attributes for original title, that is basically #{NSFontAttributeName: [UIFont systemFontOfSize:18]}, than your desired text attributes. Maybe there is better way to achieve this :)
#implementation NSString (WhiteSpace)
- (NSString *)whitespaceReplacementWithSystemAttributes:(NSDictionary *)systemAttributes newAttributes:(NSDictionary *)newAttributes
{
NSString *stringTitle = self;
NSMutableString *stringTitleWS = [[NSMutableString alloc] initWithString:#""];
CGFloat diff = 0;
CGSize stringTitleSize = [stringTitle sizeWithAttributes:newAttributes];
CGSize stringTitleWSSize;
NSDictionary *originalAttributes = systemAttributes;
do {
[stringTitleWS appendString:#" "];
stringTitleWSSize = [stringTitleWS sizeWithAttributes:originalAttributes];
diff = (stringTitleSize.width - stringTitleWSSize.width);
if (diff <= 1.5) {
break;
}
}
while (diff > 0);
return [stringTitleWS copy];
}
#end
Second important part is code that renders bitmap that can be used as pattern image for UITableViewRowAction's backgroundColor.
- (UIImage *)imageForTableViewRowActionWithTitle:(NSString *)title textAttributes:(NSDictionary *)attributes backgroundColor:(UIColor *)color cellHeight:(CGFloat)cellHeight
{
NSString *titleString = title;
NSDictionary *originalAttributes = #{NSFontAttributeName: [UIFont systemFontOfSize:18]};
CGSize originalSize = [titleString sizeWithAttributes:originalAttributes];
CGSize newSize = CGSizeMake(originalSize.width + kSystemTextPadding + kSystemTextPadding, originalSize.height);
CGRect drawingRect = CGRectIntegral(CGRectMake(0, 0, newSize.width, cellHeight));
UIGraphicsBeginImageContextWithOptions(drawingRect.size, YES, [UIScreen mainScreen].nativeScale);
CGContextRef contextRef = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(contextRef, color.CGColor);
CGContextFillRect(contextRef, drawingRect);
UILabel *label = [[UILabel alloc] initWithFrame:drawingRect];
label.textAlignment = NSTextAlignmentCenter;
label.attributedText = [[NSAttributedString alloc] initWithString:title attributes:attributes];
[label drawTextInRect:drawingRect];
//This is other way how to render string
// CGSize size = [titleString sizeWithAttributes:attributes];
// CGFloat x = (drawingRect.size.width - size.width)/2;
// CGFloat y = (drawingRect.size.height - size.height)/2;
// drawingRect.origin = CGPointMake(x, y);
// [titleString drawInRect:drawingRect withAttributes:attributes];
UIImage *returningImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return returningImage;
}
And then when you are creating your row action you can do something like this:
NSDictionary *systemAttributes = #{ NSFontAttributeName: [UIFont systemFontOfSize:18] };
NSDictionary *newAttributes = #{NSFontAttributeName: [UIFont fontWithName:#"Any font" size:15]};
NSString *actionTitle = #"Delete";
NSString *titleWhiteSpaced = [actionTitle whitespaceReplacementWithSystemAttributes:systemTextFontAttributes newAttributes:newAttributes];
UITableViewRowAction *rowAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:titleWhiteSpaced handle:NULL];
UIImage *patternImage = [self imageForTableViewRowActionWithTitle:actionTitle textAttributes:newAttributes backgroundColor:[UIColor redColor] cellHeight:50];
rowAction.backgroundColor = [UIColor colorWithPatternImage:patternImage];
This solution is obviously hacky but you can achieve desired results without using private APIs and in future if something breaks, this will not break your app. Only button will look inappropriate.
Example of result:
This code has of course a lot of space for improvements, any suggestions are appreciated.
I'm afraid that there's no way to change the title color of the UITableViewRowAction.
The only things you can change on the action are:
backgroundColor
style (destructive (red backgroundcolor, ...)
title
For more info, please refer to the Apple Doc UITableViewRowAction
Swift
No need to mess with UIButton.appearance...
Put this in your cell's class and change UITableViewCellActionButton according to your needs.
override func layoutSubviews() {
super.layoutSubviews()
for subview in self.subviews {
for subview2 in subview.subviews {
if (String(subview2).rangeOfString("UITableViewCellActionButton") != nil) {
for view in subview2.subviews {
if (String(view).rangeOfString("UIButtonLabel") != nil) {
if let label = view as? UILabel {
label.textColor = YOUR COLOUR
}
}
}
}
}
}
}
So there still is no public api to change textColor or font to the cells action in the iOS 13 days.
Working solution for Swift 5.3, iOS 14
Hacks from answers in the thread have very unreliable results but I have managed to get my version of the hack working.
1. Getting the label
Here is my simplified way of accessing the actions UILabel:
extension UITableViewCell {
var cellActionButtonLabel: UILabel? {
superview?.subviews
.filter { String(describing: $0).range(of: "UISwipeActionPullView") != nil }
.flatMap { $0.subviews }
.filter { String(describing: $0).range(of: "UISwipeActionStandardButton") != nil }
.flatMap { $0.subviews }
.compactMap { $0 as? UILabel }.first
}
}
2. Updating the label on layout changes
Next, overriding layoutSubviews() in my UITableViewCell subclass wasn't enough so additionally I had to override layoutIfNeeded() for the hack to work.
Note that it's important to override both of them!
override func layoutSubviews() {
super.layoutSubviews()
cellActionButtonLabel?.textColor = .black // you color goes here
}
override func layoutIfNeeded() {
super.layoutIfNeeded()
cellActionButtonLabel?.textColor = .black // you color goes here
}
3. Triggering extra layout refresh
The last piece of the puzzle is to schedule an additional refresh of the color, so we cover all of those cases where for some reason above functions would not get called. The best place for doing so is UITableViewDelegate method ..., willBeginEditingRowAt: ...
func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
tableView.cellForRow(at: indexPath)?.layoutIfNeeded()
}
}
A delayed layout refresh trigger does the trick of letting the UIKit configure the cell first, so we can jump right after with the customisation. 0.01 was just enough for me but i can probably vary from case to case.
4. Profit
Now you can customise the label anyway you want! Change the text, its color, font or add a subview!
It goes without saying that this can break -anytime- if Apple will decide to change their private implementation.
There is indeed a way to change the title color of the UITableViewRowAction. It's a button. You can use the UIButton appearance proxy:
[[UIButton appearance] setTitleColor:[UIColor orangeColor] forState:UIControlStateNormal];
Lee Andrew's answer in Swift 3 / Swift 4:
class MyCell: UITableViewCell {
override func layoutSubviews() {
super.layoutSubviews()
for subview in self.subviews {
for sub in subview.subviews {
if String(describing: sub).range(of: "UITableViewCellActionButton") != nil {
for view in sub.subviews {
if String(describing: view).range(of: "UIButtonLabel") != nil {
if let label = view as? UILabel {
label.textColor = UIColor.black
}
}
}
}
}
}
}
}
iOS 11 solution:
Create UITableViewCell extension like this:
extension UITableViewCell {
/// Returns label of cell action button.
///
/// Use this property to set cell action button label color.
var cellActionButtonLabel: UILabel? {
for subview in self.superview?.subviews ?? [] {
if String(describing: subview).range(of: "UISwipeActionPullView") != nil {
for view in subview.subviews {
if String(describing: view).range(of: "UISwipeActionStandardButton") != nil {
for sub in view.subviews {
if let label = sub as? UILabel {
return label
}
}
}
}
}
}
return nil
}
}
And write this in your UITableViewCell
override func layoutSubviews() {
super.layoutSubviews()
cellActionButtonLabel?.textColor = .red
}
But there is a bug - if you pull the cell fast this code sometimes doesn't change the color.
You can add these two functions in your UITableViewCell subclass and call the setActionButtonsTitleColor function to set action buttons' title color.
func setActionButtonsTitleColor(color: UIColor) {
let actionButtons: [UIButton] = self.getActionButtons()
for actionButton in actionButtons {
actionButton.setTitleColor(color, for: .normal)
}
}
func getActionButtons() -> [UIButton] {
let actionButtons: [UIButton] = self.subviews.map {
(view: UIView) -> [UIView] in
return view.subviews
}
.joined()
.filter {
(view: UIView) -> Bool in
return String(describing: view).contains("UITableViewCellActionButton")
}.flatMap {
(view: UIView) -> UIButton? in
return view as? UIButton
}
return actionButtons
}
Thanks #Witek for sharing.
Your solution works but it's not stable. So, I try to update your code, and now it's working very well.
Put this code below in your UITableViewCell
override func layoutSubviews() {
super.layoutSubviews()
if let button = actionButton {
button.setTitleColor(.black, for: .normal)
}
}
override func layoutIfNeeded() {
super.layoutIfNeeded()
if let button = actionButton {
button.setTitleColor(.black, for: .normal)
}
}
var actionButton: UIButton? {
superview?.subviews
.filter({ String(describing: $0).range(of: "UISwipeActionPullView") != nil })
.flatMap({ $0.subviews })
.filter({ String(describing: $0).range(of: "UISwipeActionStandardButton") != nil })
.compactMap { $0 as? UIButton }.first
}
-(NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewRowAction *editAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:#"edit" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath)
{
// Action something here
}];
editAction.backgroundColor = [UIColor whiteColor];
[[UIButton appearance] setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
return #[editAction];
IF someone is still looking for an alternative solution:
You can use the swipecellkit pod
https://github.com/SwipeCellKit/SwipeCellKit
This lets you customize the label color, image color and even the entire background while perserving the actual look and feel of the native implementation.

UISearchBar: changing background color of input field

I'm trying to change the background color of input field of UISearchBar.It's the rounded view where you input text to search, the default color is white. I would like to change it to gray
I tried:
for (UIView *subView in searchBar.subviews) {
if ([subView isKindOfClass:NSClassFromString(#"UITextField")]) {
UITextField *textField = (UITextField *)subView;
[textField setBackgroundColor:[UIColor grayColor]];
}
But it doesn't work :(
I also tried to insert an image view to TextField but it seems the rounded view is separate from TextField. So, any clues?
=)
for (UIView *subView in _searchBar.subviews) {
for(id field in subView.subviews){
if ([field isKindOfClass:[UITextField class]]) {
UITextField *textField = (UITextField *)field;
[textField setBackgroundColor:[UIColor grayColor]];
}
}
}
Look at the function :
[searchBar setSearchFieldBackgroundImage:[UIImage imageNamed:#"search_bar"] forState:UIControlStateNormal];
In Swift 2 and iOS 9 you can call:
UITextField.appearanceWhenContainedInInstancesOfClasses([UISearchBar.self]).backgroundColor = UIColor.darkGrey()
Swift 3:
UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).backgroundColor = UIColor.darkGrey()
Solution in Swift 3:
if let txfSearchField = searchController.searchBar.value(forKey: "_searchField") as? UITextField {
txfSearchField.borderStyle = .none
txfSearchField.backgroundColor = .lightGray
}
Take the extension worked in swift4:
extension UISearchBar {
var input : UITextField? {
return findInputInSubviews(of: self)
}
func findInputInSubviews(of view: UIView) -> UITextField? {
guard view.subviews.count > 0 else { return nil }
for v in view.subviews {
if v.isKind(of: UITextField.self) {
return v as? UITextField
}
let sv = findInputInSubviews(of: v)
if sv != nil { return sv }
}
return nil
}
}
usage:
searchBar?.input?.layer.borderColor = color.cgColor

How to change the color of UIPickerView Selector

I am having a UIPicker, I want to change the color of the selector. Is it possible to change the color of the selector?
Maybe it's not fully fits for answer to this question, in iOS 7 and later you can customize color by this way:
In the delegate methods
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
add following
[[pickerView.subviews objectAtIndex:1] setBackgroundColor:NEEDED_COLOR];
[[pickerView.subviews objectAtIndex:2] setBackgroundColor:NEEDED_COLOR];
UPDATE
Previous code works, but so-so. Here simple subclasses for UIPickerView
Swift:
class RDPickerView: UIPickerView
{
#IBInspectable var selectorColor: UIColor? = nil
override func didAddSubview(subview: UIView) {
super.didAddSubview(subview)
if let color = selectorColor
{
if subview.bounds.height <= 1.0
{
subview.backgroundColor = color
}
}
}
}
Objective-C:
#interface RDPickerView : UIPickerView
#property (strong, nonatomic) IBInspectable UIColor *selectorColor;
#end
#implementation RDPickerView
- (void)didAddSubview:(UIView *)subview
{
[subview didAddSubview:subview];
if (self.selectorColor)
{
if (subview.bounds.size.height <= 1.0)
{
subview.backgroundColor = self.selectorColor;
}
}
}
#end
and you can set selector color directly in storyboard
Thanks to Ross Barbish - "With iOS 9.2 and XCode 7.2 released 12/8/2015, the height of this selection view is 0.666666666666667".
UPDATE:
It's fix for issue with iOS 10, not good but works. :/
class RDPickerView: UIPickerView
{
#IBInspectable var selectorColor: UIColor? = nil
override func didAddSubview(_ subview: UIView) {
super.didAddSubview(subview)
guard let color = selectorColor else {
return
}
if subview.bounds.height <= 1.0
{
subview.backgroundColor = color
}
}
override func didMoveToWindow() {
super.didMoveToWindow()
guard let color = selectorColor else {
return
}
for subview in subviews {
if subview.bounds.height <= 1.0
{
subview.backgroundColor = color
}
}
}
}
Thanks Dmitry Klochkov, I'll try to find some better solution.
Here's an improvement to vsilux's answer, in form of a simple category to UIPickerView, without the need to subclass UIPickerView.
(Up-to-date answer as of November 2, 2018)
Swift 4, Xcode 10:
#IBInspectable var selectorColor: UIColor? {
get {
return objc_getAssociatedObject(self, &selectorColorAssociationKey) as? UIColor
}
set(newValue) {
objc_setAssociatedObject(self, &selectorColorAssociationKey, newValue,
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
open override func didAddSubview(_ subview: UIView) {
super.didAddSubview(subview)
if let color = selectorColor {
if subview.bounds.height < 1.0 {
subview.backgroundColor = color
}
}
}
I suppose you're dealing with the iPhone SDK? There may be some other frameworks which uses this name, so maybe you can add your tags to include uikit, cocoa-touch or something.
Anyway, you can set showsSelectionIndicator of the UIPickerView instance to NO, so it hides the selector. Then you can create a new view with the adjusted selection style, and add it to the superview above the UIPickerView.
// Some sample code, but you can do this in IB if you want to
_pickerView = [[UIPickerView alloc] init];
_pickerView.showsSelectionIndicator = NO;
[_pickerView sizeToFit];
[self.view addSubview:_pickerView];
UIImage *selectorImage = [UIImage imageNamed:#"selectorImage.png"]; // You have to make it strechable, probably
UIView *customSelector = [[UIImageView alloc] initWithImage:selectorImage];
customSelector.frame = CGRectZero; // Whatever rect to match the UIImagePicker
[self.view addSubview:customSelector];
[customSelector release];
Hacking the UI Element itself will take much more work, and this has to work as well.
You can change just the selection indicator's background color as well.
Just add a UIView above the selection indicator (it will become your overlay view), set it's alpha value low(depends on you, but I like my overlay to look transparent), give it a background color and you are good to go.
Consider this,
var overlayOnPicker:UIView = UIView(frame: CGRectMake(1, 68, mypicker.frame.width, 26))
// Adds a layer on the selection indicator
And do put the CGRect's X value=1
(Remember, it's a frame, so it will be placed according to the superview's coordinate system)
overlayOnPicker.backgroundColor = UIColor.whiteColor()
overlayOnPicker.alpha = 0.2
myDatePicker.addSubview(overlayOnPicker)
// You have to add the overlayOnPicker view as a subview to the Date Picker.
// myDatePicker is the UIDatePicker that I declared earlier in my code
You can also subclass UIPickerView and add following customisations:
import UIKit
class CustomPickerView: UIPickerView {
init() {
super.init(frame: .zero)
}
override func layoutSubviews() {
super.layoutSubviews()
subviews.forEach { $0.backgroundColor = .clear }
}
override init(frame: CGRect) {
fatalError("init(coder:) has not been implemented")
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Resources