【iOS】UITextView
文章目录
- 前言
- UITextView
- 基本概念与继承关系
- 常用属性
- 文本相关属性
- 外观样式属性
- 编辑相关属性
- 常用方法
- 代理方法(通过遵循UITextViewDelegate协议实现)
- 使用UITextView对长评论进行折叠
- 按字数进行折叠
- 按照行数进行折叠
前言
在知乎日报项目的仿写过程中,需要实现一个评论区的长评论折叠,我刚开始想的是用UILabel(文本展现)和UIButton(展开和收起按钮)来实现,后来听学长说可以学习一下UITextView,然后用UITextView来进行实现,特有这篇学习笔记。
UITextView
UITextView是 iOS 开发中一个功能强大且常用的用户界面组件,用于展示和编辑多行文本内容
基本概念与继承关系
UITextView属于UIKit框架,它继承自UIScrollView。这一继承关系赋予了它滚动显示的能力,当文本内容超出其可见区域时,用户能够通过滑动操作查看全部文本,方便处理长文本。
常用属性
文本相关属性
text属性:类型为NSString,用于获取或设置UITextView中显示的纯文本内容。
UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(50, 100, 200, 150)];
textView.text = @"这是一段普通文本";
attributedText属性:类型为NSAttributedString,通过该属性可以展示富文本,也就是能够为文本的不同部分设置多样化的格式,比如不同的字体、字号、颜色、加粗、倾斜、下划线、删除线等样式,以及段落的对齐方式、缩进等格式。示例如下:
//将 “富文本示例” 的前三个字设置为红色,后两个字设置为加粗且字号为 18 的字体样式。
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@"富文本示例"];
[attributedString addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0, 3)];
[attributedString addAttribute:NSFontAttributeName value:[UIFont boldSystemFontOfSize:18] range:NSMakeRange(3, 2)];
textView.attributedText = attributedString;
外观样式属性
font属性:用于指定文本整体的字体和字号,类型为UIFont。例如,可以设置为系统默认字体并指定具体字号:
textView.font = [UIFont systemFontOfSize:16];
除了系统自带的字体,还可以加载并使用自定义字体,只要将字体文件添加到项目资源中,并通过字体名称来应用相应字体,比如:
textView.font = [UIFont fontWithName:@"CustomFontName" size:16];
textColor属性:类型为UIColor,用来控制文本的颜色,如设置为黑色:
textView.textColor = [UIColor blackColor];
textAlignment属性:设定文本在UITextView内的对齐方式,其取值包括NSTextAlignmentLeft(左对齐)、NSTextAlignmentCenter(居中对齐)、NSTextAlignmentRight(右对齐)以及NSTextAlignmentJustified(两端对齐)等。示例如下:
textView.textAlignment = NSTextAlignmentCenter;
编辑相关属性
selectable属性:这是一个布尔类型(BOOL)的属性,用于决定文本是否可被选中。当设置为YES时,用户长按文本视图,会弹出复制、粘贴等操作的菜单,方便对文本进行相应处理;设置为NO时,则无法进行选择操作。
editable属性:同样是布尔类型,控制UITextView是否允许用户进行编辑操作。若设置为YES(默认值),用户可以在文本视图中输入、修改、删除文本等;若设置为NO,则文本视图仅用于展示文本,不能进行编辑。
例如:
textView.selectable = YES;
textView.editable = NO;
dataDetectorTypes属性:用于指定UITextView自动检测的数据类型,比如电话号码、网址、电子邮件地址等。当文本中包含这些类型的数据时,系统会自动将其识别为可点击的链接形式,方便用户直接进行相应操作(如拨打电话、打开网页、发送邮件等)。例如,设置检测所有常见类型的数据:
textView.dataDetectorTypes = UIDataDetectorTypeAll;
contentInset属性:类型为UIEdgeInsets,用于设置文本内容与UITextView边界之间的内边距,类似网页中的内边距概念,可以让文本在视图内的布局更加美观舒适,避免文本紧贴边框等情况,示例如下:
textView.contentInset = UIEdgeInsetsMake(10, 10, 10, 10);
常用方法
becomeFirstResponder方法:调用该方法可使UITextView成为当前的第一响应者,也就是获取键盘焦点,此时键盘会弹出,用户可以开始输入文本,示例如下:
[textView becomeFirstResponder];
resignFirstResponder方法:与上一个方法相反,它用于让UITextView放弃第一响应者状态,从而隐藏键盘,常用于用户完成文本输入或其他操作后收起键盘的场景,比如:
[textView resignFirstResponder];
scrollRangeToVisible:方法:传入一个NSRange参数,该方法可以将指定范围的文本滚动到UITextView的可视区域内,方便确保用户能够看到特定位置的文本内容,例如,将当前光标所在的文本范围滚动到可见区域:
NSRange range = textView.selectedRange;
[textView scrollRangeToVisible:range];
setContentOffset:animated:方法:通过指定CGPoint类型的偏移量,可以手动控制文本视图内容的滚动位置,并且可以选择是否以动画形式进行滚动。比如,将文本视图向下滚动一定距离(假设垂直滚动方向的偏移量为正方向):
[textView setContentOffset:CGPointMake(0, 50) animated:YES];
代理方法(通过遵循UITextViewDelegate协议实现)
textViewDidBeginEditing:方法:当用户开始编辑UITextView(比如点击文本视图进入可编辑状态,获取键盘焦点)时,该代理方法会被触发。可以在这个方法里执行一些准备工作,例如显示提示信息、改变视图的某些外观属性或者进行相关数据的初始化等操作,示例如下:
- (void)textViewDidBeginEditing:(UITextView *)textView {
NSLog(@"开始编辑文本");
// 可以在这里添加其他操作,如改变背景颜色等
}
textViewDidEndEditing:方法:在用户结束对UITextView的编辑(例如点击键盘上的完成按钮、切换到其他控件导致失去键盘焦点等情况)时,此方法会被调用。常用于保存编辑后的文本数据、验证输入内容的合法性或者恢复视图的初始状态等操作。
- (void)textViewDidEndEditing:(UITextView *)textView {
NSLog(@"结束编辑文本");
NSString *inputText = textView.text;
// 可以在这里对inputText进行验证或保存操作
}
textViewDidChange:方法:只要UITextView中的文本内容发生改变(不管是用户输入新字符、删除字符、粘贴内容还是进行其他编辑操作都会触发),该代理方法就会被调用。利用这个特性,可以实时监控文本的更新情况,进而实现一些实时响应的功能,比如实时统计字符数量、根据输入内容动态调整视图样式等,示例如下:
- (void)textViewDidChange:(UITextView *)textView {
NSLog(@"文本内容已改变");
int characterCount = (int)textView.text.length;
// 可以根据characterCount进行相关操作,如显示剩余字符数等
}
textViewDidChangeSelection:方法:当UITextView中用户选中的文本范围发生变化时(例如通过拖动光标、长按选择等操作来改变选中的文本部分),此代理方法会被触发。常用于处理与文本选中相关的逻辑,比如根据选中内容提供特定的操作菜单、实时显示选中文字的相关信息等,示例如下:
- (void)textViewDidChangeSelection:(UITextView *)textView {
NSLog(@"文本选中范围已改变");
// 可以在这里添加针对选中范围变化的操作逻辑
}
textView:shouldChangeTextInRange:replacementText:方法:在用户即将对文本进行修改(输入、删除、替换等操作)之前,该方法会被调用,可以在此处对用户的操作进行拦截和验证,决定是否允许此次文本修改操作。返回YES表示允许修改,返回NO则禁止修改。例如,限制文本长度不超过一定数量:
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)replacementText {
if (textView.text.length + (replacementText.length - range.length) > 100) {
return NO;
}
return YES;
}
使用UITextView对长评论进行折叠
用UITextView来实现长评论折叠和展开有两种限制方式,一种是按显示字数来进行折叠,一种是按照行数来折叠。主要用到了UITextView的textContainer.lineBreakMode属性。而这两种截取方式的不同主要在于截取部分文本进行展示的选取方法。
//设置文本截断方式,超出显示区域就截断显示
textView.textContainer.lineBreakMode = NSLineBreakByTruncatingTail;
按字数进行折叠
按字数进行折叠时,使用NSString的***substringToIndex:***方法设置短文本:
//先截取部分文本展示
NSString *partialText = [longString substringToIndex:50];
效果展示:
完整代码:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//创建UITextView
UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(50, 100, 300, 300)];
//设置为不可编辑状态,仅用于展示文本
textView.editable = NO;
textView.backgroundColor = [UIColor whiteColor];
//设置文本截断方式,超出显示区域就截断显示
textView.textContainer.lineBreakMode = NSLineBreakByTruncatingTail;
[self.view addSubview:textView];
//创建展开按钮
self.expandButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.expandButton setTitle:@"展开" forState:UIControlStateNormal];
[self.expandButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
self.expandButton.frame = CGRectMake(250, 260, 100, 30);
self.expandButton.titleLabel.font = [UIFont systemFontOfSize:15];
[self.expandButton addTarget:self action:@selector(expandText:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.expandButton];
NSString *longString = @"这是一段很长很长的文本内容,此处省略很多很多字,用于演示长文本展开功能,它可以包含很多重要的信息,或者有趣的故事等等,总之就是很长很长,长到需要有展开功能才能完整展示给用户看。";
//保存完整文本,以便后续展开
self.fullText = longString;
//先截取部分文本展示
self.partialText = [longString substringToIndex:50];
textView.text = self.partialText;
textView.font = [UIFont systemFontOfSize:20];
//保存textView的引用,方便在其他方法中操作
self.textView = textView;
//创建收起按钮(初始时隐藏)
self.collapseButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.collapseButton setTitle:@"收起" forState:UIControlStateNormal];
[self.collapseButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
self.collapseButton.frame = CGRectMake(250, 260, 100, 30);
self.collapseButton.titleLabel.font = [UIFont systemFontOfSize:15];
[self.collapseButton addTarget:self action:@selector(collapseText:) forControlEvents:UIControlEventTouchUpInside];
//初始时隐藏收起按钮
self.collapseButton.hidden = YES;
[self.view addSubview:self.collapseButton];
}
- (void)expandText:(UIButton *)button {
//动态展开长评论
[UIView animateWithDuration:0.3 animations:^{
self.textView.text = self.fullText;
} completion:^(BOOL finished) {
button.hidden = YES;
self.collapseButton.hidden = NO;
}];
// self.textView.text = self.fullText;
// button.hidden = YES;
}
- (void)collapseText:(UIButton *)button {
[UIView animateWithDuration:0.1 animations:^{
self.textView.text = self.partialText;
} completion:^(BOOL finished) {
button.hidden = YES;
self.expandButton.hidden = NO;
}];
}
按照行数进行折叠
1.获取文本字体和行高:
首先获取UITextView当前设置的字体,用于后续计算文本的尺寸。然后通过sizeThatFits方法结合文本视图的宽度来估算每行文本的高度(lineHeight),这里通过与文本视图的contentSize高度做除法来更准确地获取行高。
2.确定最大高度和初始化范围:
根据期望展示的行数(传入的lines参数)和计算出的行高,确定最大允许的文本高度(maxHeight)。同时初始化一个NSRange变量range,从文本的起始位置(索引为 0)开始,用于后续逐步查找符合行数要求的文本范围。
3.循环查找符合行数的文本范围:
通过一个循环,不断查找下一个字符序列的范围(使用rangeOfComposedCharacterSequenceAtIndex方法,这样可以正确处理复杂字符,如表情符号等),每次循环都检查当前已确定的文本范围对应的文本高度是否超过了最大允许高度(通过boundingRectWithSize方法来计算文本范围对应的矩形尺寸),一旦超过或者达到期望的行数,就停止循环。
4.提取部分文本:
最后,根据找到的符合行数要求的文本范围的起始位置,使用substringToIndex方法提取出部分文本内容,作为初始展示的文本返回。
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController <UITextViewDelegate>
@property (nonatomic, strong) UITextView *textView;
@property (nonatomic, strong) NSString *fullText;
@property (nonatomic, strong) UIButton *expandButton;
@property (nonatomic, strong) UIButton *collapseButton;
@property (nonatomic, assign) NSInteger visibleLines; // 初始可见行数
@end
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 创建UITextView并设置相关属性
self.textView = [[UITextView alloc] initWithFrame:CGRectMake(50, 100, 200, 150)];
self.textView.delegate = self;
self.textView.editable = NO;
self.textView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:self.textView];
// 创建“展开”按钮
self.expandButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.expandButton setTitle:@"展开" forState:UIControlStateNormal];
[self.expandButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
self.expandButton.frame = CGRectMake(100, 260, 80, 30);
[self.expandButton addTarget:self action:@selector(expandText:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.expandButton];
// 创建“收起”按钮(初始时隐藏)
self.collapseButton = [UIButton buttonWithType:UIButtonTypeSystem];
[self.collapseButton setTitle:@"收起" forState:UIControlStateNormal];
[self.collapseButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
self.collapseButton.frame = CGRectMake(100, 260, 80, 30);
[self.collapseButton addTarget:self action:@selector(collapseText:) forControlEvents:UIControlEventTouchUpInside];
self.collapseButton.hidden = YES;
[self.view addSubview:self.collapseButton];
// 模拟长文本
self.fullText = @"这是一段很长很长的文本内容,此处省略很多很多字,用于演示长文本展开功能,它可以包含很多重要的信息,或者有趣的故事等等,总之就是很长很长,长到需要有展开功能才能完整展示给用户看。";
// 根据行数确定初始展示的部分文本内容(这里假设初始展示3行,可根据实际调整)
self.visibleLines = 3;
NSString *partialText = [self getPartialTextByLines:self.visibleLines];
self.textView.text = partialText;
}
// 展开文本的方法
- (void)expandText:(UIButton *)button {
[UIView animateWithDuration:0.3 animations:^{
self.textView.text = self.fullText;
} completion:^(BOOL finished) {
button.hidden = YES;
self.collapseButton.hidden = NO;
}];
}
// 收起文本的方法
- (void)collapseText:(UIButton *)button {
NSString *partialText = [self getPartialTextByLines:self.visibleLines];
[UIView animateWithDuration:0.3 animations:^{
self.textView.text = partialText;
} completion:^(BOOL finished) {
button.hidden = YES;
self.expandButton.hidden = NO;
}];
}
// 根据行数获取部分文本内容的辅助方法
- (NSString *)getPartialTextByLines:(NSInteger)lines {
UIFont *textViewFont = self.textView.font;
CGFloat lineHeight = [self.textView sizeThatFits:CGSizeMake(self.textView.frame.size.width, MAXFLOAT)].height / self.textView.contentSize.height;
CGFloat maxHeight = lineHeight * lines;
NSRange range = [self.fullText rangeOfComposedCharacterSequenceAtIndex:0];
NSInteger currentLine = 1;
while (currentLine < lines && range.location!= NSNotFound) {
range = [self.fullText rangeOfComposedCharacterSequenceAtIndex:NSMaxRange(range)];
CGRect rect = [self.fullText boundingRectWithSize:CGSizeMake(self.textView.frame.size.width, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : textViewFont} context:nil];
if (rect.size.height > maxHeight) {
break;
}
currentLine++;
}
return [self.fullText substringToIndex:range.location];
}
@end