运行效果展示
-
为输入前:
输入以后
代码示例:
#import "ViewController.h"
#import "Masonry.h"
#ifndef W_H_
#define W_H_
#define WIDTH self.view.bounds.size.width
#define HEIGHT self.view.bounds.size.height
#endif
static const NSUInteger textViewHeight = 60;
static const NSUInteger kMaxTextViewNumbers = 30;
/**
* ??? 输入第九个中文字在插入表情就会存在问题
*/
@interface ViewController () <UITextViewDelegate>
@end
@implementation ViewController {
UITextView *_textView;
UILabel *_numberLabel;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self createTextViewUI];
}
#pragma mark - UI
- (void)createTextViewUI {
// 有导航栏的时候要加上,不然光标显示位置不对
self.automaticallyAdjustsScrollViewInsets = NO;
// 可是使用masonry来布局 这里懒得改了
_textView = [[UITextView alloc] init];
_textView.layer.borderWidth = 1;
_textView.delegate = self;
[self.view addSubview:_textView];
// 开始就显示键盘并且锁定光标
[_textView becomeFirstResponder];
// 距离父视图上左右15 固定高度150
[_textView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view).with.offset(79);
make.left.equalTo(self.view).with.offset(15);
make.right.equalTo(self.view).with.offset(-15);
make.height.mas_equalTo(textViewHeight);
}];
_numberLabel = [[UILabel alloc] init];
_numberLabel.text = [NSString stringWithFormat:@"0/%ld",kMaxTextViewNumbers];
[_textView addSubview:_numberLabel];
// masonry要先添加进父视图才可以布局 记得是with 而不是width 会自动根据文本来自动分配大小
// 不能以使用masonry的视图来做宽高的设置
[_numberLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(_textView).with.insets(UIEdgeInsetsMake(40, WIDTH - 79, 0, 0));
}];
}
#pragma mark - 触屏相关事件回调方法
// 在文本视图外触摸屏幕失去第一响应者
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[_textView resignFirstResponder];
}
#pragma mark - UITextViewDelegate
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
UITextRange *textSelectedRange = [textView markedTextRange];
// 获取高亮部分
UITextPosition *textPostion = [textView positionFromPosition:textSelectedRange.start offset:0];
// 获取高亮部分内容
// NSString *selectedText = [textView textInRange:textSelectedRange];
// 如果有高亮且当前数字开始位置小于最大限制时允许输入
if (textSelectedRange && textPostion) {
NSInteger startOffset = [textView offsetFromPosition:textView.beginningOfDocument toPosition:textSelectedRange.start];
NSInteger endOffset = [textView offsetFromPosition:textView.beginningOfDocument toPosition:textSelectedRange.end];
NSRange offsetRange = NSMakeRange(startOffset, endOffset - startOffset);
if (offsetRange.location < kMaxTextViewNumbers) {
return YES;
} else {
return NO;
}
}
NSString *comcatStr = [textView.text stringByReplacingCharactersInRange:range withString:text];
NSInteger canInputLength = kMaxTextViewNumbers - comcatStr.length;
if (canInputLength >= 0) {
return YES;
} else {
NSInteger length = text.length + canInputLength;
// 防止当text.length +canInputLength < 0 时,使得rg.length为一个非法最大正数出错
NSRange range = {0,MAX(length, 0)};
if (range.length > 0) {
NSString *str = @"";
BOOL asc = [text canBeConvertedToEncoding:NSASCIIStringEncoding];
if (asc) {
// asc码直接取不会错
str = [text substringWithRange:range];
} else {
__block NSInteger idx = 0;
// 截取出字符串
__block NSString *trimStr = @"";
// 使用字符串遍历 这个方法能准确知道每个emoji是占一个unicode还是两个
[text enumerateSubstringsInRange:NSMakeRange(0, [text length]) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString * _Nullable substring, NSRange substringRange, NSRange enclosingRange, BOOL * _Nonnull stop) {
NSInteger steplen = substring.length;
if (idx >= range.length) {
*stop = YES; // 取出所需要就break,提高效率
return ;
}
trimStr = [trimStr stringByAppendingString:substring];
idx = idx + steplen; // 这里变化了 使用了字符串的长度作为步长
}];
str = trimStr;
}
// rang是指从当前光标处进行替换处理(注意如果执行词句和面返回的是YES会触发didchange事件)
[textView setText:[textView.text stringByReplacingCharactersInRange:range withString:str]];
// 既然是戳出部分截取了, 那一定是最大限制了
// _numberLabel.text = [NSString stringWithFormat:@"%d/%ld",0,(long)maxTextViewNumbers];
}
return NO;
}
}
-(void)textViewDidChange:(UITextView *)textView {
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineSpacing = 5; //行间距
paragraphStyle.maximumLineHeight = 20; /**最大行高*/
paragraphStyle.firstLineHeadIndent = 15.f; /**首行缩进宽度*/
paragraphStyle.alignment = NSTextAlignmentJustified;
NSDictionary *attributes = @{
NSFontAttributeName:[UIFont systemFontOfSize:16],
NSParagraphStyleAttributeName:paragraphStyle
};
textView.attributedText = [[NSAttributedString alloc] initWithString:textView.text attributes:attributes];
// 选中范围的标记
UITextRange *textSelectedRange = [textView markedTextRange];
// 获取高亮部分
UITextPosition *textPosition = [textView positionFromPosition:textSelectedRange.start offset:0];
// 如果在变化中是高亮部分在变, 就不要计算字符了
if (textSelectedRange && textPosition) {
return;
}
// 文本内容
NSString *textContentStr = textView.text;
NSLog(@"text = %@",textView.text);
NSInteger existTextNumber = textContentStr.length;
if (existTextNumber > kMaxTextViewNumbers) {
// 截取到最大位置的字符(由于超出截取部分在should时被处理了,所以在这里为了提高效率不在判断)
NSString *str = [textContentStr substringToIndex:kMaxTextViewNumbers];
[textView setText:str];
}
// 不让现实负数
_numberLabel.text = [NSString stringWithFormat:@"%ld/%ld",MIN(kMaxTextViewNumbers,existTextNumber),kMaxTextViewNumbers];
}
@end
参考网址
github下载链接地址
UITextField
代码示例
#import "ViewController.h"
#import "Masonry.h"
#define MAX_LIMIT_NUMS 20
static const NSUInteger textFieldHeight = 30;
@interface ViewController () <UITextFieldDelegate>
@end
@implementation ViewController {
UITextField *_nameField; // 限制名字输入的文本框
}
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(textFiledEditChanged:)
name:@"UITextFieldTextDidChangeNotification"
object:_nameField];
[self createTextFieldUI];
}
- (void)createTextFieldUI {
_nameField = [[UITextField alloc] init];
_nameField.delegate = self;
_nameField.layer.borderWidth = 1;
[self.view addSubview:_nameField];
[_nameField mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view).with.offset(79);
make.left.equalTo(self.view).with.offset(15);
make.right.equalTo(self.view).with.offset(-15);
make.height.mas_equalTo(textFieldHeight);
}];
}
#pragma mark - Notification
-(void)dealloc{
[[NSNotificationCenter defaultCenter]removeObserver:self
name:@"UITextFieldTextDidChangeNotification"
object:_nameField];
}
#pragma mark UITextFieldDelegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return YES;
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
if (textField == _nameField) {
UITextRange *selectedRange = [textField markedTextRange];
//获取高亮部分
UITextPosition *pos = [textField positionFromPosition:selectedRange.start offset:0];
//获取高亮部分内容
//NSString * selectedtext = [textView textInRange:selectedRange];
//如果有高亮且当前字数开始位置小于最大限制时允许输入
if (selectedRange && pos) {
NSInteger startOffset = [textField offsetFromPosition:textField.beginningOfDocument toPosition:selectedRange.start];
NSInteger endOffset = [textField offsetFromPosition:textField.beginningOfDocument toPosition:selectedRange.end];
NSRange offsetRange = NSMakeRange(startOffset, endOffset - startOffset);
if (offsetRange.location < MAX_LIMIT_NUMS) {
return YES;
}
else
{
return NO;
}
}
NSString *comcatstr = [textField.text stringByReplacingCharactersInRange:range withString:string];
NSInteger caninputlen = MAX_LIMIT_NUMS - comcatstr.length;
if (caninputlen >= 0)
{
return YES;
}
else
{
NSInteger len = string.length + caninputlen;
//防止当text.length + caninputlen < 0时,使得rg.length为一个非法最大正数出错
NSRange rg = {0,MAX(len,0)};
if (rg.length > 0)
{
NSString *s = @"";
//判断是否只普通的字符或asc码(对于中文和表情返回NO)
BOOL asc = [string canBeConvertedToEncoding:NSASCIIStringEncoding];
if (asc) {
s = [string substringWithRange:rg];//因为是ascii码直接取就可以了不会错
}
else
{
__block NSInteger idx = 0;
__block NSString *trimString = @"";//截取出的字串
//使用字符串遍历,这个方法能准确知道每个emoji是占一个unicode还是两个
[string enumerateSubstringsInRange:NSMakeRange(0, [string length])
options:NSStringEnumerationByComposedCharacterSequences
usingBlock: ^(NSString* substring, NSRange substringRange, NSRange enclosingRange, BOOL* stop) {
if (idx >= rg.length) {
*stop = YES; //取出所需要就break,提高效率
return ;
}
trimString = [trimString stringByAppendingString:substring];
idx++;
}];
s = trimString;
}
//rang是指从当前光标处进行替换处理(注意如果执行此句后面返回的是YES会触发didchange事件)
[textField setText:[textField.text stringByReplacingCharactersInRange:range withString:s]];
//既然是超出部分截取了,哪一定是最大限制了。
// self.lbNums.text = [NSString stringWithFormat:@"%d/%ld",0,(long)MAX_LIMIT_NUMS];
}
return NO;
}
}
return YES;
}
-(void)textFiledEditChanged:(NSNotification *)obj{
UITextField *textField = (UITextField *)obj.object;
NSString *toBeString = textField.text;
NSString *lang = [[UITextInputMode currentInputMode] primaryLanguage]; // 键盘输入模式
if ([lang isEqualToString:@"zh-Hans"]) { // 简体中文输入,包括简体拼音,健体五笔,简体手写
UITextRange *selectedRange = [textField markedTextRange];
//获取高亮部分
UITextPosition *position = [textField positionFromPosition:selectedRange.start offset:0];
// 没有高亮选择的字,则对已输入的文字进行字数统计和限制
if (!position) {
if (toBeString.length > MAX_LIMIT_NUMS) {
textField.text = [toBeString substringToIndex:MAX_LIMIT_NUMS];
}
}
// 有高亮选择的字符串,则暂不对文字进行统计和限制
else{
}
}
// 中文输入法以外的直接对其统计限制即可,不考虑其他语种情况
else{
if (toBeString.length > MAX_LIMIT_NUMS) {
textField.text = [toBeString substringToIndex:MAX_LIMIT_NUMS];
}
}
}
@end