//
|
// UITextView+WZB.m
|
// WZBTextView-demo
|
//
|
// Created by normal on 2016/11/14.
|
// Copyright © 2016年 WZB. All rights reserved.
|
//
|
|
#import "UITextView+WZB.h"
|
#import <objc/runtime.h>
|
|
// 占位文字
|
static const void *WZBPlaceholderViewKey = &WZBPlaceholderViewKey;
|
// 占位文字颜色
|
static const void *WZBPlaceholderColorKey = &WZBPlaceholderColorKey;
|
// 最大高度
|
static const void *WZBTextViewMaxHeightKey = &WZBTextViewMaxHeightKey;
|
// 最小高度
|
static const void *WZBTextViewMinHeightKey = &WZBTextViewMinHeightKey;
|
// 高度变化的block
|
static const void *WZBTextViewHeightDidChangedBlockKey = &WZBTextViewHeightDidChangedBlockKey;
|
// 存储添加的图片
|
static const void *WZBTextViewImageArrayKey = &WZBTextViewImageArrayKey;
|
// 存储最后一次改变高度后的值
|
static const void *WZBTextViewLastHeightKey = &WZBTextViewLastHeightKey;
|
|
@interface UITextView ()
|
|
// 存储添加的图片
|
@property (nonatomic, strong) NSMutableArray *wzb_imageArray;
|
// 存储最后一次改变高度后的值
|
@property (nonatomic, assign) CGFloat lastHeight;
|
|
@end
|
|
@implementation UITextView (WZB)
|
|
#pragma mark - Swizzle Dealloc
|
+ (void)load {
|
// 交换dealoc
|
Method dealoc = class_getInstanceMethod(self.class, NSSelectorFromString(@"dealloc"));
|
Method myDealloc = class_getInstanceMethod(self.class, @selector(myDealloc));
|
method_exchangeImplementations(dealoc, myDealloc);
|
}
|
|
- (void)myDealloc {
|
// 移除监听
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
UITextView *placeholderView = objc_getAssociatedObject(self, WZBPlaceholderViewKey);
|
|
// 如果有值才去调用,这步很重要
|
if (placeholderView) {
|
NSArray *propertys = @[@"frame", @"bounds", @"font", @"text", @"textAlignment", @"textContainerInset"];
|
for (NSString *property in propertys) {
|
@try {
|
[self removeObserver:self forKeyPath:property];
|
} @catch (NSException *exception) {}
|
}
|
}
|
[self myDealloc];
|
}
|
|
#pragma mark - set && get
|
- (UITextView *)wzb_placeholderView {
|
|
// 为了让占位文字和textView的实际文字位置能够完全一致,这里用UITextView
|
UITextView *placeholderView = objc_getAssociatedObject(self, WZBPlaceholderViewKey);
|
|
if (!placeholderView) {
|
|
// 初始化数组
|
self.wzb_imageArray = [NSMutableArray array];
|
|
placeholderView = [[UITextView alloc] init];
|
// 动态添加属性的本质是: 让对象的某个属性与值产生关联
|
objc_setAssociatedObject(self, WZBPlaceholderViewKey, placeholderView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
placeholderView = placeholderView;
|
|
// 设置基本属性
|
placeholderView.scrollEnabled = placeholderView.userInteractionEnabled = NO;
|
// self.scrollEnabled = placeholderView.scrollEnabled = placeholderView.showsHorizontalScrollIndicator = placeholderView.showsVerticalScrollIndicator = placeholderView.userInteractionEnabled = NO;
|
placeholderView.textColor = [UIColor lightGrayColor];
|
placeholderView.backgroundColor = [UIColor clearColor];
|
[self refreshPlaceholderView];
|
[self addSubview:placeholderView];
|
|
// 监听文字改变
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textViewTextChange) name:UITextViewTextDidChangeNotification object:self];
|
|
// 这些属性改变时,都要作出一定的改变,尽管已经监听了TextDidChange的通知,也要监听text属性,因为通知监听不到setText:
|
NSArray *propertys = @[@"frame", @"bounds", @"font", @"text", @"textAlignment", @"textContainerInset"];
|
|
// 监听属性
|
for (NSString *property in propertys) {
|
[self addObserver:self forKeyPath:property options:NSKeyValueObservingOptionNew context:nil];
|
}
|
|
}
|
return placeholderView;
|
}
|
|
- (void)setWzb_placeholder:(NSString *)placeholder
|
{
|
// 为placeholder赋值
|
[self wzb_placeholderView].text = placeholder;
|
}
|
|
- (NSString *)wzb_placeholder
|
{
|
// 如果有placeholder值才去调用,这步很重要
|
if (self.placeholderExist) {
|
return [self wzb_placeholderView].text;
|
}
|
return nil;
|
}
|
|
- (void)setWzb_placeholderColor:(UIColor *)wzb_placeholderColor
|
{
|
// 如果有placeholder值才去调用,这步很重要
|
if (!self.placeholderExist) {
|
NSLog(@"请先设置placeholder值!");
|
} else {
|
self.wzb_placeholderView.textColor = wzb_placeholderColor;
|
|
// 动态添加属性的本质是: 让对象的某个属性与值产生关联
|
objc_setAssociatedObject(self, WZBPlaceholderColorKey, wzb_placeholderColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
}
|
}
|
|
- (UIColor *)wzb_placeholderColor
|
{
|
return objc_getAssociatedObject(self, WZBPlaceholderColorKey);
|
}
|
|
- (void)setWzb_maxHeight:(CGFloat)wzb_maxHeight
|
{
|
CGFloat max = wzb_maxHeight;
|
|
// 如果传入的最大高度小于textView本身的高度,则让最大高度等于本身高度
|
if (wzb_maxHeight < self.frame.size.height) {
|
max = self.frame.size.height;
|
}
|
|
objc_setAssociatedObject(self, WZBTextViewMaxHeightKey, [NSString stringWithFormat:@"%lf", max], OBJC_ASSOCIATION_COPY_NONATOMIC);
|
}
|
|
- (CGFloat)wzb_maxHeight
|
{
|
return [objc_getAssociatedObject(self, WZBTextViewMaxHeightKey) doubleValue];
|
}
|
|
- (void)setWzb_minHeight:(CGFloat)wzb_minHeight
|
{
|
objc_setAssociatedObject(self, WZBTextViewMinHeightKey, [NSString stringWithFormat:@"%lf", wzb_minHeight], OBJC_ASSOCIATION_COPY_NONATOMIC);
|
}
|
|
- (CGFloat)wzb_minHeight
|
{
|
return [objc_getAssociatedObject(self, WZBTextViewMinHeightKey) doubleValue];
|
}
|
|
- (void)setWzb_textViewHeightDidChanged:(textViewHeightDidChangedBlock)wzb_textViewHeightDidChanged
|
{
|
objc_setAssociatedObject(self, WZBTextViewHeightDidChangedBlockKey, wzb_textViewHeightDidChanged, OBJC_ASSOCIATION_COPY_NONATOMIC);
|
}
|
|
- (textViewHeightDidChangedBlock)wzb_textViewHeightDidChanged
|
{
|
void(^textViewHeightDidChanged)(CGFloat currentHeight) = objc_getAssociatedObject(self, WZBTextViewHeightDidChangedBlockKey);
|
return textViewHeightDidChanged;
|
}
|
|
- (NSArray *)wzb_getImages
|
{
|
return self.wzb_imageArray;
|
}
|
|
- (void)setLastHeight:(CGFloat)lastHeight {
|
objc_setAssociatedObject(self, WZBTextViewLastHeightKey, [NSString stringWithFormat:@"%lf", lastHeight], OBJC_ASSOCIATION_COPY_NONATOMIC);
|
}
|
|
- (CGFloat)lastHeight {
|
return [objc_getAssociatedObject(self, WZBTextViewLastHeightKey) doubleValue];
|
}
|
|
- (void)setWzb_imageArray:(NSMutableArray *)wzb_imageArray {
|
objc_setAssociatedObject(self, WZBTextViewImageArrayKey, wzb_imageArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
}
|
|
- (NSMutableArray *)wzb_imageArray {
|
return objc_getAssociatedObject(self, WZBTextViewImageArrayKey);
|
}
|
|
- (void)wzb_autoHeightWithMaxHeight:(CGFloat)maxHeight
|
{
|
[self wzb_autoHeightWithMaxHeight:maxHeight textViewHeightDidChanged:nil];
|
}
|
// 是否启用自动高度,默认为NO
|
static bool autoHeight = NO;
|
- (void)wzb_autoHeightWithMaxHeight:(CGFloat)maxHeight textViewHeightDidChanged:(textViewHeightDidChangedBlock)textViewHeightDidChanged
|
{
|
autoHeight = YES;
|
[self wzb_placeholderView];
|
self.wzb_maxHeight = maxHeight;
|
if (textViewHeightDidChanged) self.wzb_textViewHeightDidChanged = textViewHeightDidChanged;
|
}
|
|
#pragma mark - addImage
|
/* 添加一张图片 */
|
- (void)wzb_addImage:(UIImage *)image
|
{
|
[self wzb_addImage:image size:CGSizeZero];
|
}
|
|
/* 添加一张图片 image:要添加的图片 size:图片大小 */
|
- (void)wzb_addImage:(UIImage *)image size:(CGSize)size
|
{
|
[self wzb_insertImage:image size:size index:self.attributedText.length > 0 ? self.attributedText.length : 0];
|
}
|
|
/* 插入一张图片 image:要添加的图片 size:图片大小 index:插入的位置 */
|
- (void)wzb_insertImage:(UIImage *)image size:(CGSize)size index:(NSInteger)index
|
{
|
[self wzb_addImage:image size:size index:index multiple:-1];
|
}
|
|
/* 添加一张图片 image:要添加的图片 multiple:放大/缩小的倍数 */
|
- (void)wzb_addImage:(UIImage *)image multiple:(CGFloat)multiple
|
{
|
[self wzb_addImage:image size:CGSizeZero index:self.attributedText.length > 0 ? self.attributedText.length : 0 multiple:multiple];
|
}
|
|
/* 插入一张图片 image:要添加的图片 multiple:放大/缩小的倍数 index:插入的位置 */
|
- (void)wzb_insertImage:(UIImage *)image multiple:(CGFloat)multiple index:(NSInteger)index
|
{
|
[self wzb_addImage:image size:CGSizeZero index:index multiple:multiple];
|
}
|
|
/* 插入一张图片 image:要添加的图片 size:图片大小 index:插入的位置 multiple:放大/缩小的倍数 */
|
- (void)wzb_addImage:(UIImage *)image size:(CGSize)size index:(NSInteger)index multiple:(CGFloat)multiple {
|
if (image) [self.wzb_imageArray addObject:image];
|
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText];
|
NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
|
textAttachment.image = image;
|
CGRect bounds = textAttachment.bounds;
|
if (!CGSizeEqualToSize(size, CGSizeZero)) {
|
bounds.size = size;
|
textAttachment.bounds = bounds;
|
} else if (multiple <= 0) {
|
CGFloat oldWidth = textAttachment.image.size.width;
|
CGFloat scaleFactor = oldWidth / (self.frame.size.width - 10);
|
textAttachment.image = [UIImage imageWithCGImage:textAttachment.image.CGImage scale:scaleFactor orientation:UIImageOrientationUp];
|
} else {
|
bounds.size = image.size;
|
textAttachment.bounds = bounds;
|
}
|
|
NSAttributedString *attrStringWithImage = [NSAttributedString attributedStringWithAttachment:textAttachment];
|
[attributedString replaceCharactersInRange:NSMakeRange(index, 0) withAttributedString:attrStringWithImage];
|
self.attributedText = attributedString;
|
[self textViewTextChange];
|
[self refreshPlaceholderView];
|
}
|
|
|
#pragma mark - KVO监听属性改变
|
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
|
[self refreshPlaceholderView];
|
if ([keyPath isEqualToString:@"text"]) [self textViewTextChange];
|
}
|
|
// 刷新PlaceholderView
|
- (void)refreshPlaceholderView {
|
|
UITextView *placeholderView = objc_getAssociatedObject(self, WZBPlaceholderViewKey);
|
|
// 如果有值才去调用,这步很重要
|
if (placeholderView) {
|
self.wzb_placeholderView.frame = self.bounds;
|
if (self.wzb_maxHeight < self.bounds.size.height) self.wzb_maxHeight = self.bounds.size.height;
|
self.wzb_placeholderView.font = self.font;
|
self.wzb_placeholderView.textAlignment = self.textAlignment;
|
self.wzb_placeholderView.textContainerInset = self.textContainerInset;
|
self.wzb_placeholderView.hidden = (self.text.length > 0 && self.text);
|
}
|
}
|
|
// 处理文字改变
|
- (void)textViewTextChange {
|
UITextView *placeholderView = objc_getAssociatedObject(self, WZBPlaceholderViewKey);
|
|
// 如果有值才去调用,这步很重要
|
if (placeholderView) {
|
self.wzb_placeholderView.hidden = (self.text.length > 0 && self.text);
|
}
|
// 如果没有启用自动高度,不执行以下方法
|
if (!autoHeight) return;
|
if (self.wzb_maxHeight >= self.bounds.size.height) {
|
|
// 计算高度
|
NSInteger currentHeight = ceil([self sizeThatFits:CGSizeMake(self.bounds.size.width, MAXFLOAT)].height);
|
|
// 如果高度有变化,调用block
|
if (currentHeight != self.lastHeight) {
|
// 是否可以滚动
|
self.scrollEnabled = currentHeight >= self.wzb_maxHeight;
|
CGFloat currentTextViewHeight = currentHeight >= self.wzb_maxHeight ? self.wzb_maxHeight : currentHeight;
|
// 改变textView的高度
|
if (currentTextViewHeight >= self.wzb_minHeight) {
|
CGRect frame = self.frame;
|
frame.size.height = currentTextViewHeight;
|
self.frame = frame;
|
// 调用block
|
if (self.wzb_textViewHeightDidChanged) self.wzb_textViewHeightDidChanged(currentTextViewHeight);
|
// 记录当前高度
|
self.lastHeight = currentTextViewHeight;
|
}
|
}
|
}
|
|
if (!self.isFirstResponder) [self becomeFirstResponder];
|
}
|
|
// 判断是否有placeholder值,这步很重要
|
- (BOOL)placeholderExist {
|
|
// 获取对应属性的值
|
UITextView *placeholderView = objc_getAssociatedObject(self, WZBPlaceholderViewKey);
|
|
// 如果有placeholder值
|
if (placeholderView) return YES;
|
|
return NO;
|
}
|
|
@end
|