一 键盘处理相关属性
- 建一分类,通用加载xib文件
+ (instancetype)viewFromXib
{
return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self) owner:nil options:nil] lastObject];
}
- 键盘辅助控件,控件放到键盘上,跟随键盘弹出取消
self.textView.inputAccessoryView = toolbar;
- 键盘属性
UIView *keyboard = [[UIView alloc] init];
self.textView.inputView = keyboard;
- 取消覆盖键盘,可以切换键盘,系统键盘与自定义view切换
// 使用系统自带的键盘
if (self.textView.inputView) {
self.textView.inputView = nil;
} else {
UIView *keyboard = [[UIView alloc] init];
self.textView.inputView = keyboard;
}
[self.textView resignFirstResponder]; // 退出
[self.textView becomeFirstResponder];
}
- 键盘辅助控件,工具条随着键盘退出而消失,不能满足需求;
- 想要工具条一直存在,就要成为控制器的一部分,添加到控制器view上变成子控件;
- 不能加到textView上,因为textView能滚动,一定要加在textView后面,盖住textView。
二 显示隐藏工具条
- 监听键盘隐藏消失Transform
// 通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
// 移除通知
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- 工具条平移的距离
#pragma mark - 监听
- (void)keyboardWillChangeFrame:(NSNotification *)note
{
CGFloat duration = [note.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
[UIView animateWithDuration:duration animations:^{
// 工具条平移的距离 == 键盘最终的Y值 - 屏幕高度
CGFloat ty = [note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].origin.y - XMGScreenH;
self.toolbar.transform = CGAffineTransformMakeTranslation(0, ty);
}];
}
- 一进来键盘就是弹出状态
[self.textView becomeFirstResponder];
- 添加标签的+号按钮
- (void)awakeFromNib
{
UIButton *addButton = [UIButton buttonWithType:UIButtonTypeCustom];
[addButton setImage:[UIImage imageNamed:@"tag_add_icon"] forState:UIControlStateNormal];
[addButton sizeToFit];
[addButton addTarget:self action:@selector(addClick) forControlEvents:UIControlEventTouchUpInside];
[self.topView addSubview:addButton];
}
- A控制器 –modal–> B控制器
- 可以通过A.presentedViewController == B
- 可以通过B.presentingViewController == A
- (void)addClick
{
XMGAddTagViewController *addTag = [[XMGAddTagViewController alloc] init];
XMGNavigationController *nav = [[XMGNavigationController alloc] initWithRootViewController:addTag];
// 拿到"窗口根控制器"曾经modal出来的“发表文字”所在的导航控制器
UIViewController *vc = self.window.rootViewController.presentedViewController;
[vc presentViewController:nav animated:YES completion:nil];
}
- 键盘弹出,viewWillAppear(界面即将出现就弹出键盘)和viewDidAppear
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.textView becomeFirstResponder];
}
三 文本框占位文字和光标处理
- 用viewDidLoad方法初始化TextField
- 设置TextField
- (void)setupTextField
{
UITextField *textField = [[UITextField alloc] init];
textField.x = XMGCommonSmallMargin;
textField.width = self.view.width - 2 * textField.x;
textField.height = XMGTagH;
textField.y = XMGNavBarMaxY + XMGCommonSmallMargin;
// 设置占位文字
textField.placeholderColor = [UIColor grayColor];
textField.placeholder = @"多个标签用逗号或者换行隔开";
// 必须在设置完占位文字内容以后,再通过placeholderLabel.textColor设置占位文字颜色
// [textField setValue:[UIColor redColor] forKeyPath:XMGPlaceholderColorKey];
[self.view addSubview:textField];
[textField becomeFirstResponder];
// 刷新的前提:这个控件已经被添加到父控件
[textField layoutIfNeeded];
}
- 占位文字颜色设置经常用,可以新建分类
- (void)setPlaceholderColor:(UIColor *)placeholderColor
{
BOOL change = NO;
// 保证有占位文字
if (self.placeholder == nil) { // 没有占位文字
self.placeholder = @" ";
change = YES;
}
// 设置占位文字颜色
[self setValue:placeholderColor forKeyPath:XMGPlaceholderColorKey];
// 恢复原状
if (change) {
self.placeholder = nil;
}
}
四 block知识
定义变量
- 普通变量:变量类型 变量名字 = 变量的值;
- block变量: void (^myBlock)() = ^{
返回值 (^变量名字)(形参类型列表) = ^(形参列表) {
// ….
};
定义属性
- 普通属性:
@property (nonatomic, xxx) 变量类型 变量名字; - block属性:@property (nonatomic, copy) int (^sumBlock)(int, int);
@property (nonatomic, xxx) 返回值 (^变量名字)(形参类型列表);
- 普通属性:
定义方法参数
- (void)方法名:(参数类型)参数名字
- (void)test2:(int (^)(int, int))sumBlock
{
// ...
}
- 定义block返回值
- (返回值类型)方法名
- (int (^)(int, int))test3
{
// ...
}
五 监听文本框文字改变
- 加一个父控件,里面放所有标签按钮、文本框等内容
- (void)setupTextField
{
UITextField *textField = [[UITextField alloc] init];
textField.x = XMGCommonSmallMargin;
textField.width = self.view.width - 2 * textField.x;
textField.height = XMGTagH;
textField.y = XMGNavBarMaxY + XMGCommonSmallMargin;
// 设置占位文字
textField.placeholderColor = [UIColor grayColor];
textField.placeholder = @"多个标签用逗号或者换行隔开";
// 必须在设置完占位文字内容以后,再通过placeholderLabel.textColor设置占位文字颜色
// [textField setValue:[UIColor redColor] forKeyPath:XMGPlaceholderColorKey];
[self.view addSubview:textField];
[textField becomeFirstResponder];
// 刷新的前提:这个控件已经被添加到父控件
[textField layoutIfNeeded];
}
- 代理监听标签文字改变
#pragma mark - <UITextFieldDelegate>
/** 监听用户的输入
是否应该用string替换range范围内的字符串
@param range 光标的范围(或者文字的选中范围)
@param string 用户此时输入的文字
@return YES:允许利用string替换range范围内的字符串, NO:不允许....
*/
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
return YES;
}
- 上面监听方法不完整,只能监听键盘正常输入,上面内容监听不到
- 想要完整监听换textDidChange方法
/**
监听textField的文字改变
*/
- (void)textDidChange
{
// 提醒按钮
if (self.textField.hasText) {
self.tipButton.hidden = NO; // 显示回来
self.tipButton.y = CGRectGetMaxY(self.textField.frame) + XMGCommonSmallMargin;
[self.tipButton setTitle:[NSString stringWithFormat:@"添加标签:%@", self.textField.text] forState:UIControlStateNormal];
} else {
self.tipButton.hidden = YES;
}
}
- 每次输入都会创建新按钮,搞个懒加载
- (UIButton *)tipButton
{
if (!_tipButton) {
// 创建一个提醒按钮
UIButton *tipButton = [UIButton buttonWithType:UIButtonTypeCustom];
[tipButton addTarget:self action:@selector(tipClick) forControlEvents:UIControlEventTouchUpInside];
tipButton.width = self.contentView.width;
tipButton.height = XMGTagH;
tipButton.x = 0;
tipButton.backgroundColor = XMGTagBgColor;
tipButton.titleLabel.font = [UIFont systemFontOfSize:14];
// tipButton.titleLabel.textAlignment = NSTextAlignmentLeft;
// tipButton.titleLabel.backgroundColor = XMGRandomColor;
tipButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
tipButton.contentEdgeInsets = UIEdgeInsetsMake(0, XMGCommonSmallMargin, 0, 0);
[self.contentView addSubview:tipButton];
_tipButton = tipButton;
}
return _tipButton;
}
六 添加标签按钮
- 调整Label居左
tipButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
- 自定义标签按钮
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
self.backgroundColor = XMGTagBgColor;
[self setImage:[UIImage imageNamed:@"chose_tag_close_icon"] forState:UIControlStateNormal];
self.titleLabel.font = [UIFont systemFontOfSize:14];
}
return self;
}
- 实现按钮比较小,sizetofit根据文字内容调,重写setTitle
- 按钮间距调整
- (void)setTitle:(NSString *)title forState:(UIControlState)state
{
[super setTitle:title forState:state];
// 自动计算
[self sizeToFit];
// 微调
self.height = XMGTagH;
self.width += 3 * XMGCommonSmallMargin;
}
- image图片与文字重新布局
- (void)layoutSubviews
{
[super layoutSubviews];
self.titleLabel.x = XMGCommonSmallMargin;
self.imageView.x = CGRectGetMaxX(self.titleLabel.frame) + XMGCommonSmallMargin;
}
- 点击提醒按钮
/**
点击了提醒按钮
*/
- (void)tipClick
{
// 如果没有文字,直接返回
if (self.textField.hasText == NO) return;
// 创建一个标签按钮
XMGTagButton *newTagButton = [XMGTagButton buttonWithType:UIButtonTypeCustom];
[newTagButton setTitle:self.textField.text forState:UIControlStateNormal];
[self.contentView addSubview:newTagButton];
// 设置位置
// 最后一个标签按钮
UIButton *lastTagutton = self.tagButtons.lastObject;
if (lastTagutton) { // 不是第一个标签
// 左边的总宽度
CGFloat leftWidth = CGRectGetMaxX(lastTagutton.frame) + XMGCommonSmallMargin;
// 右边剩下的宽度
CGFloat rightWidth = self.contentView.width - leftWidth;
if (rightWidth >= newTagButton.width) { // 跟最后一个按钮处在同一行
newTagButton.x = leftWidth;
newTagButton.y = lastTagutton.y;
} else { // 下一行
newTagButton.x = 0;
newTagButton.y = CGRectGetMaxY(lastTagutton.frame) + XMGCommonSmallMargin;
}
} else { // 第一个标签按钮
newTagButton.x = 0;
newTagButton.y = 0;
}
// 添加到数组中
[self.tagButtons addObject:newTagButton];
}
- 排布文本框
// 排布文本框
CGFloat leftWidth = CGRectGetMaxX(newTagButton.frame) + XMGCommonSmallMargin;
self.textField.y = newTagButton.y;
self.textField.text = nil;
// 隐藏提醒按钮
self.tipButton.hidden = YES;
- 判断文本框位置
- 如果间距小于100,文本框换行
CGFloat rightWidth = self.contentView.width - leftWidth;
if (rightWidth >= 100) { // 跟新添加的标签按钮处在同一行
self.textField.x = leftWidth;
self.textField.y = newTagButton.y;
} else { // 换行
self.textField.x = 0;
self.textField.y = CGRectGetMaxY(newTagButton.frame) + XMGCommonSmallMargin;
}
- 监听键盘return点击
#pragma mark - <UITextFieldDelegate>
/**
点击右下角return按钮就会调用这个方法
*/
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[self tipClick];
return YES;
}
Text Field有Return Key属性,指定键盘右下键按键属性
- 默认Default,根据键盘类型不同值不同(换行【中文】、return)
监听变化
/**
监听textField的文字改变
*/
- (void)textDidChange
{
// 提醒按钮
if (self.textField.hasText) {
NSString *text = self.textField.text;
NSString *lastChar = [text substringFromIndex:text.length - 1];
if ([lastChar isEqualToString:@","]
|| [lastChar isEqualToString:@","]) { // 最后一个输入的字符是逗号
// 去掉文本框最后一个逗号
self.textField.text = [text substringToIndex:text.length - 1];
// 点击提醒按钮
[self tipClick];
} else { // 最后一个输入的字符不是逗号
CGFloat textW = [text sizeWithAttributes:@{NSFontAttributeName : self.textField.font}].width;
// 排布文本框
XMGTagButton *lastTagButton = self.tagButtons.lastObject;
CGFloat leftWidth = CGRectGetMaxX(lastTagButton.frame) + XMGCommonSmallMargin;
CGFloat rightWidth = self.contentView.width - leftWidth;
if (rightWidth >= textW) { // 跟新添加的标签按钮处在同一行
self.textField.x = leftWidth;
self.textField.y = lastTagButton.y;
} else { // 换行
self.textField.x = 0;
self.textField.y = CGRectGetMaxY(lastTagButton.frame) + XMGCommonSmallMargin;
}
self.tipButton.hidden = NO;
self.tipButton.y = CGRectGetMaxY(self.textField.frame) + XMGCommonSmallMargin;
[self.tipButton setTitle:[NSString stringWithFormat:@"添加标签:%@", text] forState:UIControlStateNormal];
}
} else {
self.tipButton.hidden = YES;
}
}
七 删除标签
- 监听删除点击
[newTagButton addTarget:self action:@selector(tagClick) forControlEvents:UIControlEventTouchUpInside];
- 点击哪个标签删除哪个标签(索引)
- 判断按钮索引,判断第一个按钮与其他位置按钮删除
/**
点击了标签按钮
*/
- (void)tagClick:(XMGTagButton *)clickedTagButton
{
// 即将被删除的标签按钮的索引
NSUInteger index = [self.tagButtons indexOfObject:clickedTagButton];
// 删除按钮
[clickedTagButton removeFromSuperview];
[self.tagButtons removeObject:clickedTagButton]; // 数组
// 处理后面的标签按钮
for (NSUInteger i = index; i < self.tagButtons.count; i++) {
XMGTagButton *tagButton = self.tagButtons[i];
// 如果i不为0,就参照上一个标签按钮
XMGTagButton *previousTagButton = (i == 0) ? nil : self.tagButtons[i - 1];
[self setupTagButtonFrame:tagButton referenceTagButton:previousTagButton];
}
// 排布文本框
[self setupTextFieldFrame];
}
文本框需要跟随删除按钮上移,删除完按钮标签,注意需要重新排布文本框。
#pragma mark - 设置控件的frame
/**
* 设置标签按钮的frame
* @param tagButton 需要设置frame的标签按钮
* @param referenceTagButton 计算tagButton的frame时参照的标签按钮
*/
- (void)setupTagButtonFrame:(XMGTagButton *)tagButton referenceTagButton:(XMGTagButton *)referenceTagButton
{
// 没有参照按钮(tagButton是第一个标签按钮)
if (referenceTagButton == nil) {
tagButton.x = 0;
tagButton.y = 0;
return;
}
// tagButton不是第一个标签按钮
CGFloat leftWidth = CGRectGetMaxX(referenceTagButton.frame) + XMGCommonSmallMargin;
CGFloat rightWidth = self.contentView.width - leftWidth;
if (rightWidth >= tagButton.width) { // 跟上一个标签按钮处在同一行
tagButton.x = leftWidth;
tagButton.y = referenceTagButton.y;
} else { // 下一行
tagButton.x = 0;
tagButton.y = CGRectGetMaxY(referenceTagButton.frame) + XMGCommonSmallMargin;
}
}
八 键盘删除键点击
- 监听删除键点击
/**
* 监听键盘内部的删除键点击
*/
- (void)deleteBackward
{
// 执行需要做的操作
!self.deleteBackwardOperation ? : self.deleteBackwardOperation();
[super deleteBackward];
}
注意:代码调用顺序,防止删除最后一个字时标签会跟随一起删除!
删除最后一个按钮标签,需要把控制器的删除功能代码传到TagTextField.m中,用block方法
定义block
/** 点击删除键需要执行的操作 */ @property (nonatomic, copy) void (^deleteBackwardOperation)();
需要执行的操作
self.deleteBackwardOperation();
block存储代码
// 设置点击删除键需要执行的操作
textField.deleteBackwardOperation = ^{
// 判断文本框是否有文字
if (weakSelf.textField.hasText) return;
// 点击了最后一个标签按钮(删掉最后一个标签按钮)
[weakSelf tagClick:weakSelf.tagButtons.lastObject];
};
- 控制标签数量
if (self.tagButtons.count == 5) {
[SVProgressHUD showErrorWithStatus:@"最多添加5个标签" maskType:SVProgressHUDMaskTypeBlack];
return;