The most complete UITextView of iOS in history implements N methods of placeHolder placeholder text

Keywords: Attribute iOS less

Preface

In iOS development, UITextField and UITextView are the most commonly used text acceptance class and text display class controls. Both UITextField and UITextView input text and can also monitor text changes. The difference is that UITextField inherits from the abstract class UIControl. UITextView inherits from the entity class UIScrollView. This leads to UITextView being able to display content in multiple lines and scroll like UIScrollView. UITextField can only display a single line of content. From this point of view, UITextView is better than UITextField in functionality.
However, as we all know, UITextField has a placeholder attribute, which can set the placeholder text of UITextField to prompt users to enter relevant information. However, UITextView is less fortunate, and apple has not provided UITextView with a placeholder-like property for developers to use. In development, we often encounter controls that can not only occupy space, but also display in multiple lines and scroll. Simple UITextField or UITextView can not meet the needs of this product. For example, most app s on the market now have a user feedback entry, as shown in the following figure (1). Now I will summarize the methods I can think of to let more developers know that there are so many ways to implement the placeholder text of UITextView.


Chart (1)

Method 1

1. Use the text attribute of UITextView as a placeholder.
2. Clear "placeholder" in the proxy method of starting editing.
3. Set "placeholder" according to the condition in the proxy method of ending editing.

Features: This method is characterized by the user clicking on textView, placeholder placeholder text will disappear immediately, the official placeholder is when the system monitors the user input text placeholder will disappear.

// Create textView
UITextView *textView =[[UITextViewalloc]initWithFrame:CGRectMake(20,70,SCREEN.width-40,100)];
textView.backgroundColor= [UIColor whiteColor];
textView.text = @"I am placeholder";
textView.textColor = [UIColor grayColor];
textView.delegate = self;
[self.view addSubview:textView];

#pragma mark - UITextViewDelegate
- (void)textViewDidEndEditing:(UITextView *)textView
{
    if(textView.text.length < 1){
        textView.text = @"I am placeholder";
        textView.textColor = [UIColor grayColor];
    }
}
- (void)textViewDidBeginEditing:(UITextView *)textView
{
    if([textView.text isEqualToString:@"I am placeholder"]){
        textView.text=@"";
        textView.textColor=[UIColor blackColor];
    }
}

Method two

1. Create textView
2. Add a UILabel child control to textView as a placeholder
3. Display/hide UILabel in the proxy method of text change

Features: This method can also implement functions similar to placeholder. Comparing with the first method, the second method can change the dynamic monitoring text, instead of clearing the placeholder immediately when the keyboard pops up, only when the user starts to input the text. Placement holder will disappear. Similarly, when the user empties the text, the placeholder reappears.

#import "WSViewController.h"

@interface WSViewController () <UITextViewDelegate>

@property(nonatomic, weak)UITextView *textView;

@property(nonatomic, weak)UILabel *placeHolder;

@end

@implementation WSViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    [self setupTextView];

}

// Add textView
- (void)setupTextView
{
    UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(10, 74, SCREEN_WIDTH - 2 * 10, 200)];
    textView.frame = CGRectMake(10, 74, SCREEN_WIDTH - 2 * 10, 200);

    [self.view addSubview:textView];
    self.textView = textView;

    textView.contentInset = UIEdgeInsetsMake(-64, 0, 0, 0);

    textView.delegate = self;
    [self setupPlaceHolder];


    //Add a view to the pop-up keyboard to place the one button to exit the keyboard
    UIToolbar * topView = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, 320, 30)];
    [topView setBarStyle:UIBarStyleDefault];
    UIBarButtonItem * btnSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self action:nil];
    UIBarButtonItem * doneButton = [[UIBarButtonItem alloc] initWithTitle:@"complete" style:UIBarButtonItemStyleDone target:self action:@selector(dismissKeyBoard)];
    NSArray * buttonsArray = [NSArray arrayWithObjects:btnSpace, doneButton, nil];

    [topView setItems:buttonsArray];
    [textView setInputAccessoryView:topView];

}

// Add a UILabel child control to textView
- (void)setupPlaceHolder
{
    UILabel *placeHolder = [[UILabel alloc] initWithFrame:CGRectMake(15, -2, SCREEN_WIDTH - 2 * 15, 200)];
    self.placeHolder = placeHolder;

    placeHolder.text = @"I am placeholder";
    placeHolder.textColor = [UIColor lightGrayColor];
    placeHolder.numberOfLines = 0;
    placeHolder.contentMode = UIViewContentModeTop;
    [self.textView addSubview:placeHolder];
}

#pragma mark - UITextViewDelegate
- (void)textViewDidChange:(UITextView *)textView
{
    if (!textView.text.length) {
    self.placeHolder.alpha = 1;
    } else {
        self.placeHolder.alpha = 0;
    }
}

//turn off keyboard
-(void) dismissKeyBoard{
    [self.textView resignFirstResponder];
}

@end

Similarly, we can replace UILabel as placeholder text with UITextField or UITextView. We can also implement textView with placeholder, which is not detailed in the next step.

Method three

1. Customize UITextView
2. Add placeholder and placeholderColor attributes to UITextView
3. Rewriting initWithFrame Method
4. Adding notifications to monitor text changes
5. Rewriting drawRect: Method
6. set Method for Rewriting Related Attributes

Features: Compared with the above two methods, this method has better portability and expansibility. This method is not only willing to set default text by the placeholder attribute we added, but also can set default text color by the placeholder Color we added. In the future, we just need to write such a custom UITextView, and we can do it once and for all.

#import <UIKit/UIKit.h>

@interface WSPlaceholderTextView : UITextView
/** Placeholder character */
@property (nonatomic, copy) NSString *placeholder;
/** Space-occupying text color */
@property (nonatomic, strong) UIColor *placeholderColor;
@end

#import "WSPlaceholderTextView.h"

@implementation WSPlaceholderTextView

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        // Setting default fonts
        self.font = [UIFont systemFontOfSize:15];

        // Set default color
        self.placeholderColor = [UIColor grayColor];

        // Use notifications to monitor text changes
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChange:) name:UITextViewTextDidChangeNotification object:self];
    }
    return self;
}

- (void)textDidChange:(NSNotification *)note
{
    // DraRect: method will be called again
    [self setNeedsDisplay];
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

/**
 * Every time drawRect: method is called, the previous drawing is erased.
 */
- (void)drawRect:(CGRect)rect
{
    // If there is text, return directly, without drawing placeholder text
    if (self.hasText) return;

    // attribute
    NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
    attrs[NSFontAttributeName] = self.font;
    attrs[NSForegroundColorAttributeName] = self.placeholderColor;

    // Draw text
    rect.origin.x = 5;
    rect.origin.y = 8;
    rect.size.width -= 2 * rect.origin.x;
    [self.placeholder drawInRect:rect withAttributes:attrs];
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    [self setNeedsDisplay];
}

#pragma mark - setter
- (void)setPlaceholder:(NSString *)placeholder
{
    _placeholder = [placeholder copy];

    [self setNeedsDisplay];
}

- (void)setPlaceholderColor:(UIColor *)placeholderColor
{
    _placeholderColor = placeholderColor;

    [self setNeedsDisplay];
}

- (void)setFont:(UIFont *)font
{
    [super setFont:font];

    [self setNeedsDisplay];
}

- (void)setText:(NSString *)text
{
    [super setText:text];

    [self setNeedsDisplay];
}

- (void)setAttributedText:(NSAttributedString *)attributedText
{
    [super setAttributedText:attributedText];

    [self setNeedsDisplay];
}
@end

Method four

1. Customize UITextView
2. Add placeholder and placeholderColor attributes to UITextView
3. Rewriting initWithFrame Method
4. Rewriting drawRect: Method
5. set Method for Rewriting Related Attributes

Features: This method is similar to Method 3, except that it does not use notifications to monitor text changes. It needs to be used in conjunction with textViewDidChanged: the proxy method for text changes.

#import <UIKit/UIKit.h>

@interface WSTextView : UITextView
/** Placeholder character */
@property (nonatomic,copy) NSString *placeholder;
/** Space-occupying text color */
@property (nonatomic,strong) UIColor *placeholderColor;
@end

#import "WSTextView.h"

@implementation WSTextView
- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        self.font = [UIFont systemFontOfSize:15];
        self.placeholderColor = [UIColor lightGrayColor];
        self.placeholder = @"Please enter the content";
    }
    return self;
}

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    // Drawing code
    NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
    attrs[NSFontAttributeName] = self.font;
    attrs[NSForegroundColorAttributeName] = self.placeholderColor;

    [self.placeholder drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height) withAttributes:attrs];
}

// You need to redraw when laying out child controls
- (void)layoutSubviews
{
    [super layoutSubviews];
    [self setNeedsDisplay];

}
// When setting attributes, you need to redraw, so you need to override the set method of the relevant attributes.
- (void)setPlaceholder:(NSString *)placeholder
{
    _placeholder = placeholder;
    [self setNeedsDisplay];
}

- (void)setPlaceholderColor:(UIColor *)placeholderColor
{
    _placeholderColor = placeholderColor;
    [self setNeedsDisplay];

}

- (void)setFont:(UIFont *)font
{
    [super setFont:font];
    [self setNeedsDisplay];
}

- (void)setText:(NSString *)text
{
    [super setText:text];
    if (text.length) { // Because it is in the proxy method of text change to determine whether to display placeholder, and the proxy method of text change will not be invoked by the way of setting text by code, then it is judged whether to display placeholder according to whether text is not empty.
        self.placeholder = @"";
    }
    [self setNeedsDisplay];
}

- (void)setAttributedText:(NSAttributedString *)attributedText
{
    [super setAttributedText:attributedText];
    if (attributedText.length) {
        self.placeholder = @"";
    }
    [self setNeedsDisplay];
}
@end

// The proxy method of text change with UITextView is needed in application

#import "ViewController.h"
#import "WSTextView.h"

@interface ViewController ()<UITextViewDelegate>

// @property(nonatomic,weak) WSTextView *textView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    WSTextView *textView = [[WSTextView alloc] initWithFrame:CGRectMake(10, 20, self.view.frame.size.width, 30)];
    textView.placeholder = @"ws";
    textView.delegate = self;
    [self.view addSubview:textView];
    // textView.text = @ "Try calling the proxy method for text change"; // The proxy method for text change is not invoked
    textView.attributedText = [[NSAttributedString alloc] initWithString:@"Rich text"];

    // self.textView = textView;
}

#pragma mark - UITextViewDelegate
- (void)textViewDidChange:(WSTextView *)textView // Here, the proxy method parameter type is changed directly to the customized WSTextView type. In order to use the customized placeholder attribute, this step is omitted by giving the controller WSTextView type attribute.
{
    if (textView.hasText) { // textView.text.length
        textView.placeholder = @"";

    } else {
        textView.placeholder = @"ws";

    }
}
@end

Method five

Through runtime, we find that there is a private member variable named "_placeHolderLabel" in UITextView. As you know, Objective-C has no absolute private variables, because we can access private variables through KVC.

Features: This method is more ingenious than the four methods mentioned above. Although Apple officially does not provide us with placeholder-like properties, we traverse a private variable of placeHolderLabel at runtime. This method is simple and easy to understand, and the amount of code is small. It is recommended that you use this method.

#import "ViewController.h"
#import <objc/runtime.h>
#import <objc/message.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];


  // At runtime, it is found that UITextView has a private variable called "_placeHolderLabel"
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([UITextView class], &count);

    for (int i = 0; i < count; i++) {
        Ivar ivar = ivars[i];
        const char *name = ivar_getName(ivar);
        NSString *objcName = [NSString stringWithUTF8String:name];
        NSLog(@"%d : %@",i,objcName);
    }

    [self setupTextView];

}
- (void)setupTextView
{
    UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(0, 100, [UIScreen mainScreen].bounds.size.width, 100];
    [textView setBackgroundColor:[UIColor greenColor]];
    [self.view addSubview:textView];

    // _placeholderLabel
    UILabel *placeHolderLabel = [[UILabel alloc] init];
    placeHolderLabel.text = @"Please enter the content";
    placeHolderLabel.numberOfLines = 0;
    placeHolderLabel.textColor = [UIColor lightGrayColor];
    [placeHolderLabel sizeToFit];
    [textView addSubview:placeHolderLabel];

    // same font
    textView.font = [UIFont systemFontOfSize:13.f];
    placeHolderLabel.font = [UIFont systemFontOfSize:13.f];

    [textView setValue:placeHolderLabel forKey:@"_placeholderLabel"];
}

@end


Posted by digitalflash on Mon, 20 May 2019 13:50:44 -0700