首先,在之前的开发中,我获取一个控件底部最下面的坐标是采用这样的方式(以label为例)
label.frame.origin.y + label.frame.size.height
后来,在翻阅前人的代码的时候,发现了一个更简便的方法
CGRectGetMaxY(label.frame)
两者的使用方法是相当的,都是获取控件的底部坐标(当然,这个坐标是相对于该控件所在的父控件的)
下面是本次的正文,直接针对技术点展开
目录
1.什么?你的UITextView没有placeholder?
1.1 用UILabel和delegate实现placeholder
2.数据变化了?快使用NSNotification告诉所有控件吧!
2.2 通过NSNotification实现对数据的实时刷新
1.什么?你的UITextView没有placeholder?
1.1 用UILabel和delegate实现placeholder
我们都知道处理用户输入的控件有两个,分别为UITextField和UITextView,其中UITextField可以通过NSAttributedString和attributedPlaceholder来直接设置placeholder(当然也可以直接用placeholder)举例:
NSAttributedString *attStr = [[NSAttributedString alloc] initWithString:@"请输入用户名" attributes: @{NSForegroundColorAttributeName:HEXRGBACOLOR(0x000000, 0.3), NSFontAttributeName:textField.font}];
textField.attributedPlaceholder = attrString2;
ps:这里的HEXRGBACOLOR是一个宏定义
#define HEXRGBACOLOR(hex,a) [UIColor colorWithRed:((float)((hex & 0xFF0000) >> 16))/255.0 green:((float)((hex & 0xFF00) >> 8))/255.0 blue:((float)(hex & 0xFF))/255.0 alpha:a]
ps2:textField就是目标控件
而UITextView是没有placeholder相关属性的,我的解决方法是,定义一个label,把label添加到textView中作为子控件。然后实现UITextView的delegate——textViewDidChange,当textView中的文字发生改变时会调用该方法。部分代码如下:
// 备注文本输入区
_remarkTV = [[UITextView alloc] initWithFrame:CGRectMake(16, CGRectGetMaxY(remarkLabel.frame) + 10.5, MRFullScreenWidth - 32, 120)];
_remarkTV.font = [UIFont systemFontOfSize:16];
_remarkTV.textColor = HEXRGBCOLOR(0x1D2129);
_remarkTV.delegate = self;
_remarkTV.layoutManager.delegate = self;
// 把键盘的回车键当成“完成“使用
_remarkTV.returnKeyType = UIReturnKeyDone;
[contentView addSubview:_remarkTV];
// 默认提示信息(由于UITextView不支持默认文本,因此自定义一个label)
_placeholder = [[UILabel alloc] initWithFrame:CGRectMake(0, 10.5, 200, 16)];
_placeholder.font = [UIFont systemFontOfSize:16];
_placeholder.textColor = HEXRGBCOLOR(0xBCBCBC);
_placeholder.backgroundColor = [UIColor clearColor];
_placeholder.text = @"(选填)请输入备注";
[_remarkTV addSubview:_placeholder];
// 当文字发生改变时
- (void)textViewDidChange:(UITextView *)textView
{
// 先隐藏备注
_placeholder.hidden = YES;
// 防止拼音输入时,文本直接获取拼音(在拼音输入时,不继续执行textViewDidChange方法)
UITextRange *selectedRange = [textView markedTextRange];
NSString *newText = [textView textInRange:selectedRange];
if (newText.length > 0)
{
return;
}
// 无输入文字
if (_remarkTV.text.length == 0)
{
_placeholder.hidden = NO;
}
// 有输入文字
else
{
_placeholder.hidden = YES;
}
}
1.2 防止在拼音输入时,TextView直接获取拼音字符
还有一个小知识点,单独拿出来说。就是在使用拼音时,如何拦截该代理方法。例如我们限定字符长度为10,那么在拼音输入时,如果不做该处理,当拼音(英文字符)+已输入的字符长度超过10时,会直接停下,导致用户的输入无法完成。有了这个方法,我们可以做一些更复杂的操作。例如在本次的需求中,要求实时监控字符数。这样我可以定义一个字符数label,然后在上面的代码的else分支中对这个字符数label的text进行修改即可。
// 防止拼音输入时,文本直接获取拼音(在拼音输入时,不继续执行textViewDidChange方法)
UITextRange *selectedRange = [textView markedTextRange];
NSString *newText = [textView textInRange:selectedRange];
1.3 调整TextView的行高
一个delegate方法就能实现:
// 行高
- (CGFloat)layoutManager:(NSLayoutManager *)layoutManager lineSpacingAfterGlyphAtIndex:(NSUInteger)glyphIndex withProposedLineFragmentRect:(CGRect)rect
{
return 行高;
}
1.4 设置UITextView自动换行
一个delegate方法加一些 魔法 就能实现:
// 换行
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
if ([text isEqualToString:@"\n"])
{
[self.view endEditing:YES];
return NO;
}
return YES;
}
贴两张效果图吧,具体的实现细节就忽略了,主要的技术点就在上面。
2.数据变化了?快使用NSNotification告诉所有控件吧!
2.1 NSNotification的一个生活例子
我们举一个不太恰当但是比较生动的例子。妈妈要在大C放学后给他做饭,但是妈妈不知道大C的放学确切时间,做饭早了晚了都不太合适。那么这时学校引入了一个通知机制,当大C放学后,老师会告诉学校,学校会给妈妈发送一条“大C放学了”的消息,然后妈妈就可以开始做饭啦。
在这个案例中,我们把妈妈和大C都看作是一个viewController,学校看作是NSNotification。当大C的状态发生变化时,我们通知妈妈。当然,通知是一种广播形式,也就是说,假如大C有个双胞胎弟弟小C,我们同样可以让妈妈通过学校监听小C的状态。同样的,孩子通常有两个家长,我们可以让学校再多发送一条信息到爸爸那里。
有了这个例子,理解NSNotification这个通知机制应该就容易多了,它是OC中的一个非常重要的机制。我们假设在多个viewController中(爸爸妈妈)都需要监听某个对象(大C小C),一旦该对象(大C小C)发生更改,先由具体的方法(老师)post消息中心(学校),消息中心(学校)就会通知那些viewController(爸爸妈妈):“这个对象更改了!(大C小C放学了!)”。然后那些viewControlller(爸爸妈妈)就可以刷新数据(做饭)了。
2.2 通过NSNotification实现对数据的实时刷新
刚刚讲了一个通俗易懂的例子,现在拿一个落地需求来简单理解NSNotification的用法。
现在有两个界面,第一个界面是复诊计划,以table的cell形式渲染,数据通过接口获取。第二个界面是复诊确认,可以选择患者复诊的日期和添加备注。当我们在复诊确认界面点击提交后,相应的复诊计划的cell的状态也应该刷新(从未完成➡️已完成),换句话说,当我们在复诊确认界面提交数据后,后端接收到了数据,返回了正确的状态码后,我们应该重新拉取复诊计划列表,重新对cell进行渲染。那么如何在当前controller中通知其他controller呢?以下为核心代码部分。
复诊计划controller
- (void)viewDidLoad
{
// ...
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshTopicVisitPlanDetailNotified:) name:KNotificationRefreshTopicVisitPlanDetail object:nil];
// ...
}
// 刷新复诊计划数据
- (void)refreshTopicVisitPlanDetailNotified:(NSNotification *)note
{
[self loadData:ELoadTypeRefresh];
}
ps:这里的loadData就是重新通过接口请求数据,然后对相应的array赋值,再执行tableView的reloadData方法。
复诊确认controller
// 发送请求
[MRRequestManager sendTopicVisitConfirm:_patModel.tpId
visitDate:_dateLabel.text
remark:_remarkTV.text
completeBlock:^(id responseObject) {
[self hideLoadingHUDView];
NSMutableDictionary *dictionary = (NSMutableDictionary *)responseObject;
NSString *code = [dictionary stringForDicKey:@"code"];
if (![code isEqualToString:KRunCodeSuccess])
{
NSString *msg = [dictionary stringForDicKey:@"msg"];
[self showWarnHUDView:msg code:code];
return;
}
[self showToastHUDView:@"复诊确认成功"];
[[NSNotificationCenter defaultCenter] postNotificationName:KNotificationRefreshTopicVisitPlanDetail object:nil];
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(KToastTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf.navigationController popViewControllerAnimated:YES];
});
}
errorBlock:^(NSError *error) {
[self hideLoadingHUDView];
[self showErrorHUDView:error.code];
}];
ps:方法中大多是一些提示信息,核心逻辑只有中间的NSNotification一句话,就是通知所有监听的name为KNotificationRefreshTopicVisitPlanDetail的方法,该去执行操作了。
在本案例中,当我们发送“复诊确认”请求成功后,调用了post方法,这一语句执行后,复诊计划controller中的NSNotification就会监听到由通知中心发来的消息,来去执行refreshTopicVisitPlanDetailNotified方法,也就是刷新复诊计划数据。
当然,这里的通知没有包含消息的传递,只是告诉页面去刷新数据。如果要传递一些消息,使用object来去传递消息即可。这个后面会涉及到,等到更深入的去做NSNotification后再做详解。