Github:Demo链接
使用方法
-
将Demo的两个文件拖入项目中,导入头文件
2.使用需要改变高度的textView调用以下方法并设置最小和最大高度, 0为不限制, 调用了之后,即实现高度自适应了
[textView autoChangeWithMinHeight:0 maxHeight:0 changeBlock:^(CGFloat height){
NSLog(@"高度变化了=%f",height);
}];
实现思路
1.UITextView 继承UIScrollView ,文本变化时,contentSize会更改
2.根据contentSize的变化,去设置UITextView的高度
3.使用KVO去监听contentSize的变化,以改变view高度
具体实现过程
- 当textView实例调用autoChangeHeightWithBlock 该方法时,通过KVO,让textView自身观察自身的属性contentSize的变化。因类别不能直接声明属性,所以将回调通过context传递给kvo的通知方法。
- (void)autoChangeWithMinHeight:(CGFloat)minHeight maxHeight:(CGFloat)maxHeight changeBlock:(XWChangeHeightBlock)changeHeightBlock{
NSMutableArray *objects = [NSMutableArray arrayWithObjects:@(maxHeight),@(minHeight), nil];
if( changeHeightBlock ){
[objects addObject:changeHeightBlock];
}
//__bridge_retained 持有objects 避免传过去的Objects 已经被释放
[self registerForKVOWithContext:(__bridge_retained void*)objects];
}
- (void)registerForKVOWithContext:(void*)context {
//若已经添加过KVO,则不再添加
if( [[NSUserDefaults standardUserDefaults] boolForKey:self.markKey] ){
NSLog(@"已添加过观察者,本次添加会被忽略");
return;
}
[self addObserver:self forKeyPath:self.keyPath options:(NSKeyValueObservingOptionNew) context:context];
//添加一个标记,用来标记已经添加过观察者,为了移除观察者
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:self.markKey];
}
2.在KVO 的通知方法中,重新设置textView的高度,并调用回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
//取得新的contentSize的值
NSValue *sizeValue =
[self getNewValueWithObject:object change:change];
if( !sizeValue ) return;
//取得传递过来的minHeight, maxHeight, 回调block
CGFloat maxHeight = 0, minHeight = 0;
XWChangeHeightBlock changeBlock =
[self getChangeBlockWithMaxHeight:&maxHeight minHeight:&minHeight context:context];
//计算textview的新高度
CGFloat newHeight =
[self getNewHeightWithValue:sizeValue minHeight:minHeight maxHeight:maxHeight];
//设置textview的高度,设置成功则回调,未成功则不回调
if( [self changeTextViewFrameWithNewHeight:newHeight] && changeBlock ){
changeBlock(newHeight);
}
}
3.在dealloc 中移除观察者
- (void)dealloc{
//移除观察者
[self unregisterFromKVO];
}
- (void)unregisterFromKVO {
if( [[NSUserDefaults standardUserDefaults] boolForKey:self.markKey] ){
[self removeObserver:self forKeyPath:self.keyPath];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:self.markKey];
}
}
4.为了保证markKey的唯一性,使用了textView的内存地址作为key值
- (NSString*)keyPath{
return @"contentSize";
}
- (NSString*)markKey{
NSString *key = [NSString stringWithFormat:@"%p",(self)];
return key;
}
总结
当移除观察者的时候,如果本身未添加,则会崩溃,但我又没有找到判断是否已添加某观察者办法,所以使用了NSUserDefaults 去缓存是否已添加过观察者。OC类型转C类型时,最好使用__bridge_retained 进行持有 ,不然可能存在在使用过程中被释放的情况。